summaryrefslogtreecommitdiffstats
path: root/netwerk
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /netwerk
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk')
-rw-r--r--netwerk/base/ARefBase.h31
-rw-r--r--netwerk/base/ArrayBufferInputStream.cpp125
-rw-r--r--netwerk/base/ArrayBufferInputStream.h40
-rw-r--r--netwerk/base/AutoClose.h62
-rw-r--r--netwerk/base/BackgroundFileSaver.cpp1120
-rw-r--r--netwerk/base/BackgroundFileSaver.h396
-rw-r--r--netwerk/base/CaptivePortalService.cpp396
-rw-r--r--netwerk/base/CaptivePortalService.h75
-rw-r--r--netwerk/base/Dashboard.cpp1186
-rw-r--r--netwerk/base/Dashboard.h91
-rw-r--r--netwerk/base/DashboardTypes.h179
-rw-r--r--netwerk/base/DefaultURI.cpp534
-rw-r--r--netwerk/base/DefaultURI.h59
-rw-r--r--netwerk/base/EventTokenBucket.cpp407
-rw-r--r--netwerk/base/EventTokenBucket.h155
-rw-r--r--netwerk/base/FuzzyLayer.cpp340
-rw-r--r--netwerk/base/FuzzyLayer.h29
-rw-r--r--netwerk/base/FuzzySecurityInfo.cpp369
-rw-r--r--netwerk/base/FuzzySecurityInfo.h44
-rw-r--r--netwerk/base/IOActivityMonitor.cpp475
-rw-r--r--netwerk/base/IOActivityMonitor.h79
-rw-r--r--netwerk/base/IPv6Utils.h50
-rw-r--r--netwerk/base/LoadContextInfo.cpp169
-rw-r--r--netwerk/base/LoadContextInfo.h55
-rw-r--r--netwerk/base/LoadInfo.cpp1871
-rw-r--r--netwerk/base/LoadInfo.h342
-rw-r--r--netwerk/base/LoadTainting.h38
-rw-r--r--netwerk/base/MemoryDownloader.cpp72
-rw-r--r--netwerk/base/MemoryDownloader.h59
-rw-r--r--netwerk/base/NetUtil.jsm453
-rw-r--r--netwerk/base/NetworkConnectivityService.cpp526
-rw-r--r--netwerk/base/NetworkConnectivityService.h72
-rw-r--r--netwerk/base/NetworkDataCountLayer.cpp138
-rw-r--r--netwerk/base/NetworkDataCountLayer.h26
-rw-r--r--netwerk/base/NetworkInfoServiceCocoa.cpp100
-rw-r--r--netwerk/base/NetworkInfoServiceImpl.h18
-rw-r--r--netwerk/base/NetworkInfoServiceLinux.cpp96
-rw-r--r--netwerk/base/NetworkInfoServiceWindows.cpp58
-rw-r--r--netwerk/base/PartiallySeekableInputStream.cpp437
-rw-r--r--netwerk/base/PartiallySeekableInputStream.h91
-rw-r--r--netwerk/base/PollableEvent.cpp401
-rw-r--r--netwerk/base/PollableEvent.h67
-rw-r--r--netwerk/base/Predictor.cpp2445
-rw-r--r--netwerk/base/Predictor.h458
-rw-r--r--netwerk/base/PrivateBrowsingChannel.h118
-rw-r--r--netwerk/base/ProxyAutoConfig.cpp1048
-rw-r--r--netwerk/base/ProxyAutoConfig.h107
-rw-r--r--netwerk/base/RedirectChannelRegistrar.cpp89
-rw-r--r--netwerk/base/RedirectChannelRegistrar.h50
-rw-r--r--netwerk/base/RequestContextService.cpp610
-rw-r--r--netwerk/base/RequestContextService.h44
-rw-r--r--netwerk/base/SSLTokensCache.cpp414
-rw-r--r--netwerk/base/SSLTokensCache.h87
-rw-r--r--netwerk/base/ShutdownLayer.cpp76
-rw-r--r--netwerk/base/ShutdownLayer.h23
-rw-r--r--netwerk/base/SimpleBuffer.cpp87
-rw-r--r--netwerk/base/SimpleBuffer.h55
-rw-r--r--netwerk/base/SimpleChannel.cpp118
-rw-r--r--netwerk/base/SimpleChannel.h147
-rw-r--r--netwerk/base/SimpleChannelParent.cpp97
-rw-r--r--netwerk/base/SimpleChannelParent.h40
-rw-r--r--netwerk/base/TCPFastOpen.h45
-rw-r--r--netwerk/base/TCPFastOpenLayer.cpp527
-rw-r--r--netwerk/base/TCPFastOpenLayer.h94
-rw-r--r--netwerk/base/TLSServerSocket.cpp448
-rw-r--r--netwerk/base/TLSServerSocket.h77
-rw-r--r--netwerk/base/TRRLoadInfo.cpp682
-rw-r--r--netwerk/base/TRRLoadInfo.h52
-rw-r--r--netwerk/base/ThrottleQueue.cpp399
-rw-r--r--netwerk/base/ThrottleQueue.h65
-rw-r--r--netwerk/base/Tickler.cpp245
-rw-r--r--netwerk/base/Tickler.h132
-rw-r--r--netwerk/base/http-sfv/Cargo.toml14
-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.rs870
-rw-r--r--netwerk/base/moz.build331
-rw-r--r--netwerk/base/mozIThirdPartyUtil.idl232
-rw-r--r--netwerk/base/mozurl/.gitignore2
-rw-r--r--netwerk/base/mozurl/Cargo.toml11
-rw-r--r--netwerk/base/mozurl/MozURL.cpp12
-rw-r--r--netwerk/base/mozurl/MozURL.h231
-rw-r--r--netwerk/base/mozurl/cbindgen.toml61
-rw-r--r--netwerk/base/mozurl/moz.build22
-rw-r--r--netwerk/base/mozurl/src/lib.rs563
-rw-r--r--netwerk/base/netCore.h14
-rw-r--r--netwerk/base/nsASocketHandler.h93
-rw-r--r--netwerk/base/nsAsyncRedirectVerifyHelper.cpp285
-rw-r--r--netwerk/base/nsAsyncRedirectVerifyHelper.h121
-rw-r--r--netwerk/base/nsAsyncStreamCopier.cpp392
-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.cpp51
-rw-r--r--netwerk/base/nsBase64Encoder.h33
-rw-r--r--netwerk/base/nsBaseChannel.cpp978
-rw-r--r--netwerk/base/nsBaseChannel.h306
-rw-r--r--netwerk/base/nsBaseContentStream.cpp124
-rw-r--r--netwerk/base/nsBaseContentStream.h79
-rw-r--r--netwerk/base/nsBufferedStreams.cpp1191
-rw-r--r--netwerk/base/nsBufferedStreams.h167
-rw-r--r--netwerk/base/nsDNSPrefetch.cpp162
-rw-r--r--netwerk/base/nsDNSPrefetch.h68
-rw-r--r--netwerk/base/nsDirectoryIndexStream.cpp321
-rw-r--r--netwerk/base/nsDirectoryIndexStream.h46
-rw-r--r--netwerk/base/nsDownloader.cpp97
-rw-r--r--netwerk/base/nsDownloader.h36
-rw-r--r--netwerk/base/nsFileStreams.cpp927
-rw-r--r--netwerk/base/nsFileStreams.h288
-rw-r--r--netwerk/base/nsIApplicationCache.idl203
-rw-r--r--netwerk/base/nsIApplicationCacheChannel.idl57
-rw-r--r--netwerk/base/nsIApplicationCacheContainer.idl19
-rw-r--r--netwerk/base/nsIApplicationCacheService.idl108
-rw-r--r--netwerk/base/nsIArrayBufferInputStream.idl25
-rw-r--r--netwerk/base/nsIAsyncStreamCopier.idl64
-rw-r--r--netwerk/base/nsIAsyncStreamCopier2.idl59
-rw-r--r--netwerk/base/nsIAsyncVerifyRedirectCallback.idl19
-rw-r--r--netwerk/base/nsIAuthInformation.idl118
-rw-r--r--netwerk/base/nsIAuthModule.idl146
-rw-r--r--netwerk/base/nsIAuthPrompt.idl80
-rw-r--r--netwerk/base/nsIAuthPrompt2.idl103
-rw-r--r--netwerk/base/nsIAuthPromptAdapterFactory.idl22
-rw-r--r--netwerk/base/nsIAuthPromptCallback.idl44
-rw-r--r--netwerk/base/nsIAuthPromptProvider.idl34
-rw-r--r--netwerk/base/nsIBackgroundFileSaver.idl183
-rw-r--r--netwerk/base/nsIBufferedStreams.idl47
-rw-r--r--netwerk/base/nsIByteRangeRequest.idl27
-rw-r--r--netwerk/base/nsICacheInfoChannel.idl176
-rw-r--r--netwerk/base/nsICachingChannel.idl127
-rw-r--r--netwerk/base/nsICancelable.idl24
-rw-r--r--netwerk/base/nsICaptivePortalService.idl68
-rw-r--r--netwerk/base/nsIChannel.idl398
-rw-r--r--netwerk/base/nsIChannelEventSink.idl101
-rw-r--r--netwerk/base/nsIChildChannel.idl34
-rw-r--r--netwerk/base/nsIClassOfService.idl93
-rw-r--r--netwerk/base/nsIClassifiedChannel.idl195
-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/nsIDeprecationWarner.idl23
-rw-r--r--netwerk/base/nsIDownloader.idl51
-rw-r--r--netwerk/base/nsIEncodedChannel.idl55
-rw-r--r--netwerk/base/nsIExternalProtocolHandler.idl17
-rw-r--r--netwerk/base/nsIFileStreams.idl238
-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.idl31
-rw-r--r--netwerk/base/nsIHttpPushListener.idl36
-rw-r--r--netwerk/base/nsIIOService.idl300
-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/nsILoadContextInfo.idl86
-rw-r--r--netwerk/base/nsILoadGroup.idl105
-rw-r--r--netwerk/base/nsILoadGroupChild.idl42
-rw-r--r--netwerk/base/nsILoadInfo.idl1351
-rw-r--r--netwerk/base/nsIMIMEInputStream.idl48
-rw-r--r--netwerk/base/nsIMultiPartChannel.idl49
-rw-r--r--netwerk/base/nsINestedURI.idl75
-rw-r--r--netwerk/base/nsINetAddr.idl88
-rw-r--r--netwerk/base/nsINetUtil.idl215
-rw-r--r--netwerk/base/nsINetworkConnectivityService.idl44
-rw-r--r--netwerk/base/nsINetworkInfoService.idl56
-rw-r--r--netwerk/base/nsINetworkInterceptController.idl264
-rw-r--r--netwerk/base/nsINetworkLinkService.idl140
-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.cpp2039
-rw-r--r--netwerk/base/nsIOService.h274
-rw-r--r--netwerk/base/nsIParentChannel.idl81
-rw-r--r--netwerk/base/nsIParentRedirectingChannel.idl68
-rw-r--r--netwerk/base/nsIPermission.idl89
-rw-r--r--netwerk/base/nsIPermissionManager.idl223
-rw-r--r--netwerk/base/nsIPrivateBrowsingChannel.idl58
-rw-r--r--netwerk/base/nsIProgressEventSink.idl72
-rw-r--r--netwerk/base/nsIPrompt.idl102
-rw-r--r--netwerk/base/nsIProtocolHandler.idl312
-rw-r--r--netwerk/base/nsIProtocolProxyCallback.idl42
-rw-r--r--netwerk/base/nsIProtocolProxyFilter.idl97
-rw-r--r--netwerk/base/nsIProtocolProxyService.idl296
-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.idl100
-rw-r--r--netwerk/base/nsIRandomGenerator.idl24
-rw-r--r--netwerk/base/nsIRedirectChannelRegistrar.idl72
-rw-r--r--netwerk/base/nsIRedirectHistoryEntry.idl34
-rw-r--r--netwerk/base/nsIRedirectResultListener.idl22
-rw-r--r--netwerk/base/nsIRequest.idl291
-rw-r--r--netwerk/base/nsIRequestContext.idl158
-rw-r--r--netwerk/base/nsIRequestObserver.idl37
-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.idl262
-rw-r--r--netwerk/base/nsISimpleStreamListener.idl25
-rw-r--r--netwerk/base/nsISocketFilter.idl53
-rw-r--r--netwerk/base/nsISocketTransport.idl349
-rw-r--r--netwerk/base/nsISocketTransportService.idl157
-rw-r--r--netwerk/base/nsISpeculativeConnect.idl71
-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.idl33
-rw-r--r--netwerk/base/nsIThrottledInputChannel.idl87
-rw-r--r--netwerk/base/nsITimedChannel.idl122
-rw-r--r--netwerk/base/nsITraceableChannel.idl37
-rw-r--r--netwerk/base/nsITransport.idl163
-rw-r--r--netwerk/base/nsIUDPSocket.idl347
-rw-r--r--netwerk/base/nsIURI.idl322
-rw-r--r--netwerk/base/nsIURIMutator.idl557
-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.idl69
-rw-r--r--netwerk/base/nsIncrementalDownload.cpp832
-rw-r--r--netwerk/base/nsIncrementalStreamLoader.cpp188
-rw-r--r--netwerk/base/nsIncrementalStreamLoader.h54
-rw-r--r--netwerk/base/nsInputStreamChannel.cpp102
-rw-r--r--netwerk/base/nsInputStreamChannel.h43
-rw-r--r--netwerk/base/nsInputStreamPump.cpp739
-rw-r--r--netwerk/base/nsInputStreamPump.h110
-rw-r--r--netwerk/base/nsLoadGroup.cpp1079
-rw-r--r--netwerk/base/nsLoadGroup.h107
-rw-r--r--netwerk/base/nsMIMEInputStream.cpp536
-rw-r--r--netwerk/base/nsMIMEInputStream.h25
-rw-r--r--netwerk/base/nsMediaFragmentURIParser.cpp353
-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.cpp3268
-rw-r--r--netwerk/base/nsNetUtil.h984
-rw-r--r--netwerk/base/nsNetworkInfoService.cpp94
-rw-r--r--netwerk/base/nsNetworkInfoService.h40
-rw-r--r--netwerk/base/nsPACMan.cpp964
-rw-r--r--netwerk/base/nsPACMan.h295
-rw-r--r--netwerk/base/nsPISocketTransportService.idl59
-rw-r--r--netwerk/base/nsPreloadedStream.cpp122
-rw-r--r--netwerk/base/nsPreloadedStream.h52
-rw-r--r--netwerk/base/nsProtocolProxyService.cpp2376
-rw-r--r--netwerk/base/nsProtocolProxyService.h414
-rw-r--r--netwerk/base/nsProxyInfo.cpp196
-rw-r--r--netwerk/base/nsProxyInfo.h102
-rw-r--r--netwerk/base/nsReadLine.h131
-rw-r--r--netwerk/base/nsRedirectHistoryEntry.cpp43
-rw-r--r--netwerk/base/nsRedirectHistoryEntry.h35
-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.cpp550
-rw-r--r--netwerk/base/nsServerSocket.h66
-rw-r--r--netwerk/base/nsSimpleNestedURI.cpp227
-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.cpp744
-rw-r--r--netwerk/base/nsSimpleURI.h143
-rw-r--r--netwerk/base/nsSocketTransport2.cpp3610
-rw-r--r--netwerk/base/nsSocketTransport2.h486
-rw-r--r--netwerk/base/nsSocketTransportService2.cpp1892
-rw-r--r--netwerk/base/nsSocketTransportService2.h353
-rw-r--r--netwerk/base/nsStandardURL.cpp3717
-rw-r--r--netwerk/base/nsStandardURL.h550
-rw-r--r--netwerk/base/nsStreamListenerTee.cpp157
-rw-r--r--netwerk/base/nsStreamListenerTee.h46
-rw-r--r--netwerk/base/nsStreamListenerWrapper.cpp39
-rw-r--r--netwerk/base/nsStreamListenerWrapper.h43
-rw-r--r--netwerk/base/nsStreamLoader.cpp140
-rw-r--r--netwerk/base/nsStreamLoader.h58
-rw-r--r--netwerk/base/nsStreamTransportService.cpp408
-rw-r--r--netwerk/base/nsStreamTransportService.h47
-rw-r--r--netwerk/base/nsSyncStreamListener.cpp167
-rw-r--r--netwerk/base/nsSyncStreamListener.h42
-rw-r--r--netwerk/base/nsTransportUtils.cpp126
-rw-r--r--netwerk/base/nsTransportUtils.h25
-rw-r--r--netwerk/base/nsUDPSocket.cpp1455
-rw-r--r--netwerk/base/nsUDPSocket.h113
-rw-r--r--netwerk/base/nsURIHashKey.h71
-rw-r--r--netwerk/base/nsURLHelper.cpp1135
-rw-r--r--netwerk/base/nsURLHelper.h355
-rw-r--r--netwerk/base/nsURLHelperOSX.cpp205
-rw-r--r--netwerk/base/nsURLHelperUnix.cpp105
-rw-r--r--netwerk/base/nsURLHelperWin.cpp111
-rw-r--r--netwerk/base/nsURLParsers.cpp634
-rw-r--r--netwerk/base/nsURLParsers.h119
-rw-r--r--netwerk/base/rust-helper/Cargo.toml9
-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.conf672
-rw-r--r--netwerk/build/moz.build51
-rw-r--r--netwerk/build/nsNetCID.h854
-rw-r--r--netwerk/build/nsNetModule.cpp352
-rw-r--r--netwerk/build/nsNetModule.h47
-rw-r--r--netwerk/cache/moz.build50
-rw-r--r--netwerk/cache/nsApplicationCache.h33
-rw-r--r--netwerk/cache/nsApplicationCacheService.cpp192
-rw-r--r--netwerk/cache/nsApplicationCacheService.h27
-rw-r--r--netwerk/cache/nsCache.cpp70
-rw-r--r--netwerk/cache/nsCache.h39
-rw-r--r--netwerk/cache/nsCacheDevice.h63
-rw-r--r--netwerk/cache/nsCacheEntry.cpp415
-rw-r--r--netwerk/cache/nsCacheEntry.h285
-rw-r--r--netwerk/cache/nsCacheEntryDescriptor.cpp1307
-rw-r--r--netwerk/cache/nsCacheEntryDescriptor.h220
-rw-r--r--netwerk/cache/nsCacheMetaData.cpp151
-rw-r--r--netwerk/cache/nsCacheMetaData.h45
-rw-r--r--netwerk/cache/nsCacheRequest.h146
-rw-r--r--netwerk/cache/nsCacheService.cpp1910
-rw-r--r--netwerk/cache/nsCacheService.h331
-rw-r--r--netwerk/cache/nsCacheSession.cpp121
-rw-r--r--netwerk/cache/nsCacheSession.h67
-rw-r--r--netwerk/cache/nsCacheUtils.cpp78
-rw-r--r--netwerk/cache/nsCacheUtils.h44
-rw-r--r--netwerk/cache/nsDeleteDir.cpp362
-rw-r--r--netwerk/cache/nsDeleteDir.h79
-rw-r--r--netwerk/cache/nsDiskCache.h72
-rw-r--r--netwerk/cache/nsDiskCacheDeviceSQL.cpp2608
-rw-r--r--netwerk/cache/nsDiskCacheDeviceSQL.h263
-rw-r--r--netwerk/cache/nsICache.idl142
-rw-r--r--netwerk/cache/nsICacheEntryDescriptor.idl164
-rw-r--r--netwerk/cache/nsICacheListener.idl32
-rw-r--r--netwerk/cache/nsICacheService.idl98
-rw-r--r--netwerk/cache/nsICacheSession.idl88
-rw-r--r--netwerk/cache/nsICacheVisitor.idl123
-rw-r--r--netwerk/cache2/AppCacheStorage.cpp185
-rw-r--r--netwerk/cache2/AppCacheStorage.h35
-rw-r--r--netwerk/cache2/CacheEntry.cpp1913
-rw-r--r--netwerk/cache2/CacheEntry.h573
-rw-r--r--netwerk/cache2/CacheFile.cpp2596
-rw-r--r--netwerk/cache2/CacheFile.h264
-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.h99
-rw-r--r--netwerk/cache2/CacheFileIOManager.cpp4225
-rw-r--r--netwerk/cache2/CacheFileIOManager.h479
-rw-r--r--netwerk/cache2/CacheFileInputStream.cpp719
-rw-r--r--netwerk/cache2/CacheFileInputStream.h80
-rw-r--r--netwerk/cache2/CacheFileMetadata.cpp1049
-rw-r--r--netwerk/cache2/CacheFileMetadata.h237
-rw-r--r--netwerk/cache2/CacheFileOutputStream.cpp466
-rw-r--r--netwerk/cache2/CacheFileOutputStream.h70
-rw-r--r--netwerk/cache2/CacheFileUtils.cpp669
-rw-r--r--netwerk/cache2/CacheFileUtils.h224
-rw-r--r--netwerk/cache2/CacheHashUtils.cpp235
-rw-r--r--netwerk/cache2/CacheHashUtils.h67
-rw-r--r--netwerk/cache2/CacheIOThread.cpp641
-rw-r--r--netwerk/cache2/CacheIOThread.h147
-rw-r--r--netwerk/cache2/CacheIndex.cpp3903
-rw-r--r--netwerk/cache2/CacheIndex.h1277
-rw-r--r--netwerk/cache2/CacheIndexContextIterator.cpp33
-rw-r--r--netwerk/cache2/CacheIndexContextIterator.h31
-rw-r--r--netwerk/cache2/CacheIndexIterator.cpp102
-rw-r--r--netwerk/cache2/CacheIndexIterator.h57
-rw-r--r--netwerk/cache2/CacheLog.cpp22
-rw-r--r--netwerk/cache2/CacheLog.h20
-rw-r--r--netwerk/cache2/CacheObserver.cpp257
-rw-r--r--netwerk/cache2/CacheObserver.h109
-rw-r--r--netwerk/cache2/CacheStorage.cpp253
-rw-r--r--netwerk/cache2/CacheStorage.h72
-rw-r--r--netwerk/cache2/CacheStorageService.cpp2376
-rw-r--r--netwerk/cache2/CacheStorageService.h437
-rw-r--r--netwerk/cache2/OldWrappers.cpp1037
-rw-r--r--netwerk/cache2/OldWrappers.h268
-rw-r--r--netwerk/cache2/moz.build62
-rw-r--r--netwerk/cache2/nsICacheEntry.idl364
-rw-r--r--netwerk/cache2/nsICacheEntryDoomCallback.idl15
-rw-r--r--netwerk/cache2/nsICacheEntryOpenCallback.idl91
-rw-r--r--netwerk/cache2/nsICacheStorage.idl145
-rw-r--r--netwerk/cache2/nsICacheStorageService.idl155
-rw-r--r--netwerk/cache2/nsICacheStorageVisitor.idl35
-rw-r--r--netwerk/cache2/nsICacheTesting.idl20
-rw-r--r--netwerk/cookie/Cookie.cpp219
-rw-r--r--netwerk/cookie/Cookie.h145
-rw-r--r--netwerk/cookie/CookieCommons.cpp698
-rw-r--r--netwerk/cookie/CookieCommons.h143
-rw-r--r--netwerk/cookie/CookieJarSettings.cpp619
-rw-r--r--netwerk/cookie/CookieJarSettings.h193
-rw-r--r--netwerk/cookie/CookieKey.h61
-rw-r--r--netwerk/cookie/CookieLogging.cpp184
-rw-r--r--netwerk/cookie/CookieLogging.h65
-rw-r--r--netwerk/cookie/CookiePersistentStorage.cpp2017
-rw-r--r--netwerk/cookie/CookiePersistentStorage.h158
-rw-r--r--netwerk/cookie/CookiePrivateStorage.cpp44
-rw-r--r--netwerk/cookie/CookiePrivateStorage.h47
-rw-r--r--netwerk/cookie/CookieService.cpp2374
-rw-r--r--netwerk/cookie/CookieService.h160
-rw-r--r--netwerk/cookie/CookieServiceChild.cpp634
-rw-r--r--netwerk/cookie/CookieServiceChild.h87
-rw-r--r--netwerk/cookie/CookieServiceParent.cpp182
-rw-r--r--netwerk/cookie/CookieServiceParent.h71
-rw-r--r--netwerk/cookie/CookieStorage.cpp887
-rw-r--r--netwerk/cookie/CookieStorage.h200
-rw-r--r--netwerk/cookie/CookieXPCShellUtils.jsm60
-rw-r--r--netwerk/cookie/PCookieService.ipdl72
-rw-r--r--netwerk/cookie/moz.build76
-rw-r--r--netwerk/cookie/nsICookie.idl139
-rw-r--r--netwerk/cookie/nsICookieJarSettings.idl59
-rw-r--r--netwerk/cookie/nsICookieManager.idl254
-rw-r--r--netwerk/cookie/nsICookiePermission.idl35
-rw-r--r--netwerk/cookie/nsICookieService.idl168
-rw-r--r--netwerk/cookie/test/browser/browser.ini19
-rw-r--r--netwerk/cookie/test/browser/browser_broadcastChannel.js80
-rw-r--r--netwerk/cookie/test/browser/browser_cookies.js53
-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_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.sjs9
-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.sjs124
-rw-r--r--netwerk/cookie/test/mochitest/cookiesHelper.js67
-rw-r--r--netwerk/cookie/test/mochitest/empty.html1
-rw-r--r--netwerk/cookie/test/mochitest/mochitest.ini17
-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.js107
-rw-r--r--netwerk/cookie/test/unit/test_bug1155169.js93
-rw-r--r--netwerk/cookie/test/unit/test_bug1321912.js101
-rw-r--r--netwerk/cookie/test/unit/test_bug643051.js41
-rw-r--r--netwerk/cookie/test/unit/test_eviction.js212
-rw-r--r--netwerk/cookie/test/unit/test_getCookieSince.js72
-rw-r--r--netwerk/cookie/test/unit/test_parser_0001.js33
-rw-r--r--netwerk/cookie/test/unit/test_parser_0019.js33
-rw-r--r--netwerk/cookie/test/unit/test_rawSameSite.js126
-rw-r--r--netwerk/cookie/test/unit/test_schemeMap.js287
-rw-r--r--netwerk/cookie/test/unit/xpcshell.ini13
-rw-r--r--netwerk/dns/ChildDNSService.cpp450
-rw-r--r--netwerk/dns/ChildDNSService.h69
-rw-r--r--netwerk/dns/DNS.cpp382
-rw-r--r--netwerk/dns/DNS.h247
-rw-r--r--netwerk/dns/DNSByTypeRecord.h238
-rw-r--r--netwerk/dns/DNSListenerProxy.cpp37
-rw-r--r--netwerk/dns/DNSListenerProxy.h62
-rw-r--r--netwerk/dns/DNSPacket.cpp975
-rw-r--r--netwerk/dns/DNSPacket.h71
-rw-r--r--netwerk/dns/DNSRequestBase.h148
-rw-r--r--netwerk/dns/DNSRequestChild.cpp536
-rw-r--r--netwerk/dns/DNSRequestChild.h41
-rw-r--r--netwerk/dns/DNSRequestParent.cpp178
-rw-r--r--netwerk/dns/DNSRequestParent.h44
-rw-r--r--netwerk/dns/DNSResolverInfo.cpp19
-rw-r--r--netwerk/dns/DNSResolverInfo.h34
-rw-r--r--netwerk/dns/GetAddrInfo.cpp465
-rw-r--r--netwerk/dns/GetAddrInfo.h87
-rw-r--r--netwerk/dns/HTTPSSVC.cpp433
-rw-r--r--netwerk/dns/HTTPSSVC.h153
-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.cpp41
-rw-r--r--netwerk/dns/NativeDNSResolverOverrideChild.h38
-rw-r--r--netwerk/dns/NativeDNSResolverOverrideParent.cpp103
-rw-r--r--netwerk/dns/NativeDNSResolverOverrideParent.h32
-rw-r--r--netwerk/dns/PDNSRequest.ipdl39
-rw-r--r--netwerk/dns/PDNSRequestParams.ipdlh38
-rw-r--r--netwerk/dns/PNativeDNSResolverOverride.ipdl25
-rw-r--r--netwerk/dns/PTRRService.ipdl27
-rw-r--r--netwerk/dns/PublicSuffixList.jsm106
-rw-r--r--netwerk/dns/TRR.cpp965
-rw-r--r--netwerk/dns/TRR.h161
-rw-r--r--netwerk/dns/TRRQuery.cpp288
-rw-r--r--netwerk/dns/TRRQuery.h90
-rw-r--r--netwerk/dns/TRRService.cpp944
-rw-r--r--netwerk/dns/TRRService.h152
-rw-r--r--netwerk/dns/TRRServiceBase.cpp144
-rw-r--r--netwerk/dns/TRRServiceBase.h51
-rw-r--r--netwerk/dns/TRRServiceChild.cpp59
-rw-r--r--netwerk/dns/TRRServiceChild.h44
-rw-r--r--netwerk/dns/TRRServiceParent.cpp155
-rw-r--r--netwerk/dns/TRRServiceParent.h43
-rw-r--r--netwerk/dns/effective_tld_names.dat13597
-rw-r--r--netwerk/dns/mdns/libmdns/DNSServiceDiscovery.jsm219
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp719
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderOperator.h133
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp215
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderReply.h130
-rw-r--r--netwerk/dns/mdns/libmdns/MulticastDNSAndroid.jsm262
-rw-r--r--netwerk/dns/mdns/libmdns/components.conf24
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DNSPacket.jsm304
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DNSRecord.jsm71
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DNSResourceRecord.jsm221
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DNSTypes.jsm99
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DataReader.jsm134
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/DataWriter.jsm97
-rw-r--r--netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm985
-rw-r--r--netwerk/dns/mdns/libmdns/moz.build51
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp228
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h47
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp170
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceInfo.h49
-rw-r--r--netwerk/dns/mdns/moz.build13
-rw-r--r--netwerk/dns/mdns/nsIDNSServiceDiscovery.idl219
-rw-r--r--netwerk/dns/moz.build107
-rw-r--r--netwerk/dns/nsDNSService2.cpp1453
-rw-r--r--netwerk/dns/nsDNSService2.h109
-rw-r--r--netwerk/dns/nsEffectiveTLDService.cpp492
-rw-r--r--netwerk/dns/nsEffectiveTLDService.h92
-rw-r--r--netwerk/dns/nsHostResolver.cpp2325
-rw-r--r--netwerk/dns/nsHostResolver.h654
-rw-r--r--netwerk/dns/nsIDNKitInterface.h195
-rw-r--r--netwerk/dns/nsIDNSByTypeRecord.idl126
-rw-r--r--netwerk/dns/nsIDNSListener.idl34
-rw-r--r--netwerk/dns/nsIDNSRecord.idl136
-rw-r--r--netwerk/dns/nsIDNSResolverInfo.idl11
-rw-r--r--netwerk/dns/nsIDNSService.idl341
-rw-r--r--netwerk/dns/nsIDNService.cpp925
-rw-r--r--netwerk/dns/nsIDNService.h205
-rw-r--r--netwerk/dns/nsIEffectiveTLDService.idl158
-rw-r--r--netwerk/dns/nsIIDNService.idl58
-rw-r--r--netwerk/dns/nsINativeDNSResolverOverride.idl29
-rw-r--r--netwerk/dns/nsPIDNSService.idl34
-rw-r--r--netwerk/dns/prepare_tlds.py152
-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.js176
-rw-r--r--netwerk/dns/tests/unit/test_nsEffectiveTLDService_Reload_DAFSA.js34
-rw-r--r--netwerk/dns/tests/unit/test_nsEffectiveTLDService_getKnownPublicSuffix.js46
-rw-r--r--netwerk/dns/tests/unit/xpcshell.ini12
-rw-r--r--netwerk/ipc/ChannelEventQueue.cpp218
-rw-r--r--netwerk/ipc/ChannelEventQueue.h354
-rw-r--r--netwerk/ipc/DocumentChannel.cpp468
-rw-r--r--netwerk/ipc/DocumentChannel.h119
-rw-r--r--netwerk/ipc/DocumentChannelChild.cpp427
-rw-r--r--netwerk/ipc/DocumentChannelChild.h72
-rw-r--r--netwerk/ipc/DocumentChannelParent.cpp145
-rw-r--r--netwerk/ipc/DocumentChannelParent.h64
-rw-r--r--netwerk/ipc/DocumentLoadListener.cpp2544
-rw-r--r--netwerk/ipc/DocumentLoadListener.h575
-rw-r--r--netwerk/ipc/InputChannelThrottleQueueChild.cpp30
-rw-r--r--netwerk/ipc/InputChannelThrottleQueueChild.h33
-rw-r--r--netwerk/ipc/InputChannelThrottleQueueParent.cpp128
-rw-r--r--netwerk/ipc/InputChannelThrottleQueueParent.h51
-rw-r--r--netwerk/ipc/NeckoChannelParams.ipdlh546
-rw-r--r--netwerk/ipc/NeckoChild.cpp360
-rw-r--r--netwerk/ipc/NeckoChild.h96
-rw-r--r--netwerk/ipc/NeckoCommon.h116
-rw-r--r--netwerk/ipc/NeckoMessageUtils.h149
-rw-r--r--netwerk/ipc/NeckoParent.cpp922
-rw-r--r--netwerk/ipc/NeckoParent.h252
-rw-r--r--netwerk/ipc/NeckoTargetHolder.cpp38
-rw-r--r--netwerk/ipc/NeckoTargetHolder.h40
-rw-r--r--netwerk/ipc/PDataChannel.ipdl24
-rw-r--r--netwerk/ipc/PDocumentChannel.ipdl66
-rw-r--r--netwerk/ipc/PFileChannel.ipdl27
-rw-r--r--netwerk/ipc/PInputChannelThrottleQueue.ipdl25
-rw-r--r--netwerk/ipc/PNecko.ipdl190
-rw-r--r--netwerk/ipc/PProxyConfigLookup.ipdl21
-rw-r--r--netwerk/ipc/PSimpleChannel.ipdl24
-rw-r--r--netwerk/ipc/PSocketProcess.ipdl182
-rw-r--r--netwerk/ipc/PSocketProcessBridge.ipdl30
-rw-r--r--netwerk/ipc/ParentChannelWrapper.cpp107
-rw-r--r--netwerk/ipc/ParentChannelWrapper.h39
-rw-r--r--netwerk/ipc/ParentProcessDocumentChannel.cpp300
-rw-r--r--netwerk/ipc/ParentProcessDocumentChannel.h55
-rw-r--r--netwerk/ipc/ProxyConfigLookup.cpp98
-rw-r--r--netwerk/ipc/ProxyConfigLookup.h43
-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/SocketProcessBridgeChild.cpp180
-rw-r--r--netwerk/ipc/SocketProcessBridgeChild.h53
-rw-r--r--netwerk/ipc/SocketProcessBridgeParent.cpp65
-rw-r--r--netwerk/ipc/SocketProcessBridgeParent.h44
-rw-r--r--netwerk/ipc/SocketProcessChild.cpp618
-rw-r--r--netwerk/ipc/SocketProcessChild.h163
-rw-r--r--netwerk/ipc/SocketProcessHost.cpp292
-rw-r--r--netwerk/ipc/SocketProcessHost.h151
-rw-r--r--netwerk/ipc/SocketProcessImpl.cpp108
-rw-r--r--netwerk/ipc/SocketProcessImpl.h36
-rw-r--r--netwerk/ipc/SocketProcessLogging.h20
-rw-r--r--netwerk/ipc/SocketProcessParent.cpp429
-rw-r--r--netwerk/ipc/SocketProcessParent.h132
-rw-r--r--netwerk/ipc/moz.build99
-rw-r--r--netwerk/locales/en-US/necko.properties91
-rw-r--r--netwerk/locales/jar.mn9
-rw-r--r--netwerk/locales/moz.build7
-rw-r--r--netwerk/mime/moz.build26
-rw-r--r--netwerk/mime/nsIMIMEHeaderParam.idl207
-rw-r--r--netwerk/mime/nsIMIMEInfo.idl370
-rw-r--r--netwerk/mime/nsIMIMEService.idl100
-rw-r--r--netwerk/mime/nsMIMEHeaderParamImpl.cpp1320
-rw-r--r--netwerk/mime/nsMIMEHeaderParamImpl.h42
-rw-r--r--netwerk/mime/nsMimeTypes.h236
-rw-r--r--netwerk/moz.build37
-rw-r--r--netwerk/protocol/about/moz.build35
-rw-r--r--netwerk/protocol/about/nsAboutBlank.cpp52
-rw-r--r--netwerk/protocol/about/nsAboutBlank.h32
-rw-r--r--netwerk/protocol/about/nsAboutCache.cpp532
-rw-r--r--netwerk/protocol/about/nsAboutCache.h193
-rw-r--r--netwerk/protocol/about/nsAboutCacheEntry.cpp557
-rw-r--r--netwerk/protocol/about/nsAboutCacheEntry.h85
-rw-r--r--netwerk/protocol/about/nsAboutProtocolHandler.cpp445
-rw-r--r--netwerk/protocol/about/nsAboutProtocolHandler.h146
-rw-r--r--netwerk/protocol/about/nsAboutProtocolUtils.h57
-rw-r--r--netwerk/protocol/about/nsIAboutModule.idl114
-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.cpp109
-rw-r--r--netwerk/protocol/data/nsDataChannel.h24
-rw-r--r--netwerk/protocol/data/nsDataHandler.cpp270
-rw-r--r--netwerk/protocol/data/nsDataHandler.h57
-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.build43
-rw-r--r--netwerk/protocol/file/nsFileChannel.cpp511
-rw-r--r--netwerk/protocol/file/nsFileChannel.h53
-rw-r--r--netwerk/protocol/file/nsFileProtocolHandler.cpp277
-rw-r--r--netwerk/protocol/file/nsFileProtocolHandler.h28
-rw-r--r--netwerk/protocol/file/nsIFileChannel.idl17
-rw-r--r--netwerk/protocol/file/nsIFileProtocolHandler.idl87
-rw-r--r--netwerk/protocol/ftp/FTPChannelChild.cpp541
-rw-r--r--netwerk/protocol/ftp/FTPChannelChild.h137
-rw-r--r--netwerk/protocol/ftp/FTPChannelParent.cpp418
-rw-r--r--netwerk/protocol/ftp/FTPChannelParent.h92
-rw-r--r--netwerk/protocol/ftp/PFTPChannel.ipdl54
-rw-r--r--netwerk/protocol/ftp/doc/testdoc4
-rw-r--r--netwerk/protocol/ftp/ftpCore.h15
-rw-r--r--netwerk/protocol/ftp/moz.build50
-rw-r--r--netwerk/protocol/ftp/nsFTPChannel.cpp213
-rw-r--r--netwerk/protocol/ftp/nsFTPChannel.h115
-rw-r--r--netwerk/protocol/ftp/nsFtpConnectionThread.cpp1960
-rw-r--r--netwerk/protocol/ftp/nsFtpConnectionThread.h254
-rw-r--r--netwerk/protocol/ftp/nsFtpControlConnection.cpp169
-rw-r--r--netwerk/protocol/ftp/nsFtpControlConnection.h83
-rw-r--r--netwerk/protocol/ftp/nsFtpProtocolHandler.cpp331
-rw-r--r--netwerk/protocol/ftp/nsFtpProtocolHandler.h85
-rw-r--r--netwerk/protocol/ftp/nsIFTPChannel.idl29
-rw-r--r--netwerk/protocol/ftp/nsIFTPChannelParentInternal.idl15
-rw-r--r--netwerk/protocol/ftp/test/frametest/contents.html5
-rw-r--r--netwerk/protocol/ftp/test/frametest/index.html12
-rw-r--r--netwerk/protocol/ftp/test/frametest/menu.html371
-rw-r--r--netwerk/protocol/gio/components.conf16
-rw-r--r--netwerk/protocol/gio/moz.build24
-rw-r--r--netwerk/protocol/gio/nsGIOProtocolHandler.cpp991
-rw-r--r--netwerk/protocol/gio/nsGIOProtocolHandler.h33
-rw-r--r--netwerk/protocol/http/ASpdySession.cpp113
-rw-r--r--netwerk/protocol/http/ASpdySession.h126
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamChild.cpp196
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamChild.h51
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamParent.cpp82
-rw-r--r--netwerk/protocol/http/AltDataOutputStreamParent.h52
-rw-r--r--netwerk/protocol/http/AltServiceChild.cpp114
-rw-r--r--netwerk/protocol/http/AltServiceChild.h43
-rw-r--r--netwerk/protocol/http/AltServiceParent.cpp52
-rw-r--r--netwerk/protocol/http/AltServiceParent.h41
-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.cpp1330
-rw-r--r--netwerk/protocol/http/AlternateServices.h277
-rw-r--r--netwerk/protocol/http/BackgroundChannelRegistrar.cpp103
-rw-r--r--netwerk/protocol/http/BackgroundChannelRegistrar.h57
-rw-r--r--netwerk/protocol/http/BackgroundDataBridgeChild.cpp58
-rw-r--r--netwerk/protocol/http/BackgroundDataBridgeChild.h40
-rw-r--r--netwerk/protocol/http/BackgroundDataBridgeParent.cpp57
-rw-r--r--netwerk/protocol/http/BackgroundDataBridgeParent.h35
-rw-r--r--netwerk/protocol/http/CacheControlParser.cpp135
-rw-r--r--netwerk/protocol/http/CacheControlParser.h52
-rw-r--r--netwerk/protocol/http/CachePushChecker.cpp249
-rw-r--r--netwerk/protocol/http/CachePushChecker.h45
-rw-r--r--netwerk/protocol/http/ClassifierDummyChannel.cpp775
-rw-r--r--netwerk/protocol/http/ClassifierDummyChannel.h91
-rw-r--r--netwerk/protocol/http/ClassifierDummyChannelChild.cpp91
-rw-r--r--netwerk/protocol/http/ClassifierDummyChannelChild.h47
-rw-r--r--netwerk/protocol/http/ClassifierDummyChannelParent.cpp55
-rw-r--r--netwerk/protocol/http/ClassifierDummyChannelParent.h40
-rw-r--r--netwerk/protocol/http/ConnectionDiagnostics.cpp236
-rw-r--r--netwerk/protocol/http/ConnectionEntry.cpp962
-rw-r--r--netwerk/protocol/http/ConnectionEntry.h210
-rw-r--r--netwerk/protocol/http/ConnectionHandle.cpp87
-rw-r--r--netwerk/protocol/http/ConnectionHandle.h38
-rw-r--r--netwerk/protocol/http/DelayHttpChannelQueue.cpp122
-rw-r--r--netwerk/protocol/http/DelayHttpChannelQueue.h46
-rw-r--r--netwerk/protocol/http/HTTPSRecordResolver.cpp99
-rw-r--r--netwerk/protocol/http/HTTPSRecordResolver.h41
-rw-r--r--netwerk/protocol/http/HalfOpenSocket.cpp1307
-rw-r--r--netwerk/protocol/http/HalfOpenSocket.h165
-rw-r--r--netwerk/protocol/http/Http2Compression.cpp1443
-rw-r--r--netwerk/protocol/http/Http2Compression.h206
-rw-r--r--netwerk/protocol/http/Http2HuffmanIncoming.h709
-rw-r--r--netwerk/protocol/http/Http2HuffmanOutgoing.h85
-rw-r--r--netwerk/protocol/http/Http2Push.cpp498
-rw-r--r--netwerk/protocol/http/Http2Push.h161
-rw-r--r--netwerk/protocol/http/Http2Session.cpp4653
-rw-r--r--netwerk/protocol/http/Http2Session.h609
-rw-r--r--netwerk/protocol/http/Http2Stream.cpp1678
-rw-r--r--netwerk/protocol/http/Http2Stream.h395
-rw-r--r--netwerk/protocol/http/Http3Session.cpp1715
-rw-r--r--netwerk/protocol/http/Http3Session.h218
-rw-r--r--netwerk/protocol/http/Http3Stream.cpp489
-rw-r--r--netwerk/protocol/http/Http3Stream.h158
-rw-r--r--netwerk/protocol/http/HttpAuthUtils.cpp169
-rw-r--r--netwerk/protocol/http/HttpAuthUtils.h33
-rw-r--r--netwerk/protocol/http/HttpBackgroundChannelChild.cpp504
-rw-r--r--netwerk/protocol/http/HttpBackgroundChannelChild.h152
-rw-r--r--netwerk/protocol/http/HttpBackgroundChannelParent.cpp519
-rw-r--r--netwerk/protocol/http/HttpBackgroundChannelParent.h121
-rw-r--r--netwerk/protocol/http/HttpBaseChannel.cpp5330
-rw-r--r--netwerk/protocol/http/HttpBaseChannel.h1045
-rw-r--r--netwerk/protocol/http/HttpChannelChild.cpp3156
-rw-r--r--netwerk/protocol/http/HttpChannelChild.h451
-rw-r--r--netwerk/protocol/http/HttpChannelParams.ipdlh57
-rw-r--r--netwerk/protocol/http/HttpChannelParent.cpp2145
-rw-r--r--netwerk/protocol/http/HttpChannelParent.h327
-rw-r--r--netwerk/protocol/http/HttpConnectionBase.cpp70
-rw-r--r--netwerk/protocol/http/HttpConnectionBase.h208
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrChild.cpp182
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrChild.h53
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrParent.cpp273
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrParent.h34
-rw-r--r--netwerk/protocol/http/HttpConnectionMgrShell.h232
-rw-r--r--netwerk/protocol/http/HttpConnectionUDP.cpp772
-rw-r--r--netwerk/protocol/http/HttpConnectionUDP.h131
-rw-r--r--netwerk/protocol/http/HttpInfo.cpp16
-rw-r--r--netwerk/protocol/http/HttpInfo.h24
-rw-r--r--netwerk/protocol/http/HttpLog.h70
-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.cpp646
-rw-r--r--netwerk/protocol/http/HttpTransactionChild.h127
-rw-r--r--netwerk/protocol/http/HttpTransactionParent.cpp909
-rw-r--r--netwerk/protocol/http/HttpTransactionParent.h170
-rw-r--r--netwerk/protocol/http/HttpTransactionShell.h227
-rw-r--r--netwerk/protocol/http/InterceptedChannel.cpp232
-rw-r--r--netwerk/protocol/http/InterceptedChannel.h155
-rw-r--r--netwerk/protocol/http/InterceptedHttpChannel.cpp1311
-rw-r--r--netwerk/protocol/http/InterceptedHttpChannel.h188
-rw-r--r--netwerk/protocol/http/NullHttpChannel.cpp867
-rw-r--r--netwerk/protocol/http/NullHttpChannel.h64
-rw-r--r--netwerk/protocol/http/NullHttpTransaction.cpp220
-rw-r--r--netwerk/protocol/http/NullHttpTransaction.h99
-rw-r--r--netwerk/protocol/http/PAltDataOutputStream.ipdl41
-rw-r--r--netwerk/protocol/http/PAltService.ipdl41
-rw-r--r--netwerk/protocol/http/PAltSvcTransaction.ipdl23
-rw-r--r--netwerk/protocol/http/PBackgroundDataBridge.ipdl32
-rw-r--r--netwerk/protocol/http/PClassifierDummyChannel.ipdl47
-rw-r--r--netwerk/protocol/http/PHttpBackgroundChannel.ipdl80
-rw-r--r--netwerk/protocol/http/PHttpChannel.ipdl152
-rw-r--r--netwerk/protocol/http/PHttpChannelParams.h280
-rw-r--r--netwerk/protocol/http/PHttpConnectionMgr.ipdl40
-rw-r--r--netwerk/protocol/http/PHttpTransaction.ipdl106
-rw-r--r--netwerk/protocol/http/PSpdyPush.h56
-rw-r--r--netwerk/protocol/http/ParentChannelListener.cpp282
-rw-r--r--netwerk/protocol/http/ParentChannelListener.h92
-rw-r--r--netwerk/protocol/http/PendingTransactionInfo.cpp135
-rw-r--r--netwerk/protocol/http/PendingTransactionInfo.h60
-rw-r--r--netwerk/protocol/http/PendingTransactionQueue.cpp290
-rw-r--r--netwerk/protocol/http/PendingTransactionQueue.h92
-rw-r--r--netwerk/protocol/http/QuicSocketControl.cpp138
-rw-r--r--netwerk/protocol/http/QuicSocketControl.h62
-rw-r--r--netwerk/protocol/http/README119
-rw-r--r--netwerk/protocol/http/SpeculativeTransaction.cpp84
-rw-r--r--netwerk/protocol/http/SpeculativeTransaction.h69
-rw-r--r--netwerk/protocol/http/TRRServiceChannel.cpp1539
-rw-r--r--netwerk/protocol/http/TRRServiceChannel.h169
-rw-r--r--netwerk/protocol/http/TimingStruct.h44
-rw-r--r--netwerk/protocol/http/TunnelUtils.cpp2172
-rw-r--r--netwerk/protocol/http/TunnelUtils.h302
-rw-r--r--netwerk/protocol/http/WellKnownOpportunisticUtils.jsm30
-rw-r--r--netwerk/protocol/http/components.conf14
-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/moz.build199
-rw-r--r--netwerk/protocol/http/nsAHttpConnection.h248
-rw-r--r--netwerk/protocol/http/nsAHttpTransaction.h307
-rw-r--r--netwerk/protocol/http/nsCORSListenerProxy.cpp1599
-rw-r--r--netwerk/protocol/http/nsCORSListenerProxy.h127
-rw-r--r--netwerk/protocol/http/nsHttp.cpp1045
-rw-r--r--netwerk/protocol/http/nsHttp.h388
-rw-r--r--netwerk/protocol/http/nsHttpActivityDistributor.cpp198
-rw-r--r--netwerk/protocol/http/nsHttpActivityDistributor.h37
-rw-r--r--netwerk/protocol/http/nsHttpAtomList.h104
-rw-r--r--netwerk/protocol/http/nsHttpAuthCache.cpp462
-rw-r--r--netwerk/protocol/http/nsHttpAuthCache.h232
-rw-r--r--netwerk/protocol/http/nsHttpAuthManager.cpp113
-rw-r--r--netwerk/protocol/http/nsHttpAuthManager.h34
-rw-r--r--netwerk/protocol/http/nsHttpBasicAuth.cpp103
-rw-r--r--netwerk/protocol/http/nsHttpBasicAuth.h39
-rw-r--r--netwerk/protocol/http/nsHttpChannel.cpp10154
-rw-r--r--netwerk/protocol/http/nsHttpChannel.h888
-rw-r--r--netwerk/protocol/http/nsHttpChannelAuthProvider.cpp1698
-rw-r--r--netwerk/protocol/http/nsHttpChannelAuthProvider.h186
-rw-r--r--netwerk/protocol/http/nsHttpChunkedDecoder.cpp168
-rw-r--r--netwerk/protocol/http/nsHttpChunkedDecoder.h54
-rw-r--r--netwerk/protocol/http/nsHttpConnection.cpp2735
-rw-r--r--netwerk/protocol/http/nsHttpConnection.h359
-rw-r--r--netwerk/protocol/http/nsHttpConnectionInfo.cpp580
-rw-r--r--netwerk/protocol/http/nsHttpConnectionInfo.h280
-rw-r--r--netwerk/protocol/http/nsHttpConnectionMgr.cpp3527
-rw-r--r--netwerk/protocol/http/nsHttpConnectionMgr.h453
-rw-r--r--netwerk/protocol/http/nsHttpDigestAuth.cpp662
-rw-r--r--netwerk/protocol/http/nsHttpDigestAuth.h93
-rw-r--r--netwerk/protocol/http/nsHttpHandler.cpp3053
-rw-r--r--netwerk/protocol/http/nsHttpHandler.h939
-rw-r--r--netwerk/protocol/http/nsHttpHeaderArray.cpp452
-rw-r--r--netwerk/protocol/http/nsHttpHeaderArray.h289
-rw-r--r--netwerk/protocol/http/nsHttpNTLMAuth.cpp404
-rw-r--r--netwerk/protocol/http/nsHttpNTLMAuth.h36
-rw-r--r--netwerk/protocol/http/nsHttpRequestHead.cpp360
-rw-r--r--netwerk/protocol/http/nsHttpRequestHead.h147
-rw-r--r--netwerk/protocol/http/nsHttpResponseHead.cpp1266
-rw-r--r--netwerk/protocol/http/nsHttpResponseHead.h235
-rw-r--r--netwerk/protocol/http/nsHttpTransaction.cpp3347
-rw-r--r--netwerk/protocol/http/nsHttpTransaction.h545
-rw-r--r--netwerk/protocol/http/nsIBackgroundChannelRegistrar.idl63
-rw-r--r--netwerk/protocol/http/nsICorsPreflightCallback.h31
-rw-r--r--netwerk/protocol/http/nsIHttpActivityObserver.idl162
-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.idl510
-rw-r--r--netwerk/protocol/http/nsIHttpChannelAuthProvider.idl86
-rw-r--r--netwerk/protocol/http/nsIHttpChannelChild.idl38
-rw-r--r--netwerk/protocol/http/nsIHttpChannelInternal.idl439
-rw-r--r--netwerk/protocol/http/nsIHttpHeaderVisitor.idl26
-rw-r--r--netwerk/protocol/http/nsIHttpProtocolHandler.idl195
-rw-r--r--netwerk/protocol/http/nsIRaceCacheWithNetwork.idl55
-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/moz.build10
-rw-r--r--netwerk/protocol/res/ExtensionProtocolHandler.cpp987
-rw-r--r--netwerk/protocol/res/ExtensionProtocolHandler.h236
-rw-r--r--netwerk/protocol/res/PageThumbProtocolHandler.cpp468
-rw-r--r--netwerk/protocol/res/PageThumbProtocolHandler.h123
-rw-r--r--netwerk/protocol/res/SubstitutingJARURI.h161
-rw-r--r--netwerk/protocol/res/SubstitutingProtocolHandler.cpp653
-rw-r--r--netwerk/protocol/res/SubstitutingProtocolHandler.h134
-rw-r--r--netwerk/protocol/res/SubstitutingURL.h66
-rw-r--r--netwerk/protocol/res/moz.build40
-rw-r--r--netwerk/protocol/res/nsIResProtocolHandler.idl15
-rw-r--r--netwerk/protocol/res/nsISubstitutingProtocolHandler.idl62
-rw-r--r--netwerk/protocol/res/nsResProtocolHandler.cpp194
-rw-r--r--netwerk/protocol/res/nsResProtocolHandler.h80
-rw-r--r--netwerk/protocol/viewsource/moz.build29
-rw-r--r--netwerk/protocol/viewsource/nsIViewSourceChannel.idl43
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceChannel.cpp1175
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceChannel.h104
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceHandler.cpp153
-rw-r--r--netwerk/protocol/viewsource/nsViewSourceHandler.h48
-rw-r--r--netwerk/protocol/websocket/BaseWebSocketChannel.cpp367
-rw-r--r--netwerk/protocol/websocket/BaseWebSocketChannel.h127
-rw-r--r--netwerk/protocol/websocket/IPCTransportProvider.cpp82
-rw-r--r--netwerk/protocol/websocket/IPCTransportProvider.h89
-rw-r--r--netwerk/protocol/websocket/PTransportProvider.ipdl27
-rw-r--r--netwerk/protocol/websocket/PWebSocket.ipdl66
-rw-r--r--netwerk/protocol/websocket/PWebSocketEventListener.ipdl52
-rw-r--r--netwerk/protocol/websocket/WebSocketChannel.cpp4023
-rw-r--r--netwerk/protocol/websocket/WebSocketChannel.h315
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelChild.cpp729
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelChild.h111
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelParent.cpp351
-rw-r--r--netwerk/protocol/websocket/WebSocketChannelParent.h69
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerChild.cpp109
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerChild.h63
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerParent.cpp119
-rw-r--r--netwerk/protocol/websocket/WebSocketEventListenerParent.h44
-rw-r--r--netwerk/protocol/websocket/WebSocketEventService.cpp563
-rw-r--r--netwerk/protocol/websocket/WebSocketEventService.h122
-rw-r--r--netwerk/protocol/websocket/WebSocketFrame.cpp158
-rw-r--r--netwerk/protocol/websocket/WebSocketFrame.h100
-rw-r--r--netwerk/protocol/websocket/WebSocketLog.h24
-rw-r--r--netwerk/protocol/websocket/moz.build62
-rw-r--r--netwerk/protocol/websocket/nsITransportProvider.idl36
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketChannel.idl252
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketEventService.idl87
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketImpl.idl18
-rw-r--r--netwerk/protocol/websocket/nsIWebSocketListener.idl96
-rw-r--r--netwerk/sctp/datachannel/DataChannel.cpp3368
-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.h87
-rw-r--r--netwerk/sctp/datachannel/moz.build37
-rw-r--r--netwerk/sctp/sctp_update.log20
-rw-r--r--netwerk/sctp/src/LocalArray.h75
-rw-r--r--netwerk/sctp/src/README.sctp16
-rw-r--r--netwerk/sctp/src/ScopedFd.h46
-rw-r--r--netwerk/sctp/src/ifaddrs-android-ext.h62
-rw-r--r--netwerk/sctp/src/ifaddrs_android.cpp189
-rw-r--r--netwerk/sctp/src/moz.build58
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp.h672
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_asconf.c3548
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_asconf.h96
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_auth.c2314
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_auth.h216
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_bsd_addr.c996
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_bsd_addr.h73
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_callout.c249
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_callout.h114
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_cc_functions.c2508
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_constants.h1077
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_crc32.c831
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_crc32.h56
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_header.h611
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_indata.c5819
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_indata.h124
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_input.c6450
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_input.h66
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_lock_userspace.h243
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_os.h92
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_os_userspace.h1140
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_output.c14993
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_output.h248
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_pcb.c8096
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_pcb.h879
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_peeloff.c303
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_peeloff.h70
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_process_lock.h669
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_sha1.c329
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_sha1.h90
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_ss_functions.c1130
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_structs.h1296
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_sysctl.c1625
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_sysctl.h632
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_timer.c1633
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_timer.h104
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_uio.h1361
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_userspace.c406
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_usrreq.c9074
-rwxr-xr-xnetwerk/sctp/src/netinet/sctp_var.h450
-rwxr-xr-xnetwerk/sctp/src/netinet/sctputil.c8656
-rwxr-xr-xnetwerk/sctp/src/netinet/sctputil.h414
-rw-r--r--netwerk/sctp/src/netinet6/sctp6_usrreq.c1809
-rwxr-xr-xnetwerk/sctp/src/netinet6/sctp6_var.h81
-rwxr-xr-xnetwerk/sctp/src/user_atomic.h315
-rwxr-xr-xnetwerk/sctp/src/user_environment.c132
-rwxr-xr-xnetwerk/sctp/src/user_environment.h118
-rwxr-xr-xnetwerk/sctp/src/user_inpcb.h373
-rwxr-xr-xnetwerk/sctp/src/user_ip6_var.h126
-rwxr-xr-xnetwerk/sctp/src/user_ip_icmp.h225
-rwxr-xr-xnetwerk/sctp/src/user_malloc.h203
-rwxr-xr-xnetwerk/sctp/src/user_mbuf.c1566
-rwxr-xr-xnetwerk/sctp/src/user_mbuf.h413
-rwxr-xr-xnetwerk/sctp/src/user_queue.h639
-rwxr-xr-xnetwerk/sctp/src/user_recv_thread.c1521
-rwxr-xr-xnetwerk/sctp/src/user_recv_thread.h34
-rwxr-xr-xnetwerk/sctp/src/user_route.h130
-rwxr-xr-xnetwerk/sctp/src/user_socket.c3590
-rwxr-xr-xnetwerk/sctp/src/user_socketvar.h527
-rwxr-xr-xnetwerk/sctp/src/user_uma.h96
-rw-r--r--netwerk/sctp/src/usrsctp.h1321
-rwxr-xr-xnetwerk/sctp/update_sctp.sh35
-rw-r--r--netwerk/socket/moz.build49
-rw-r--r--netwerk/socket/neqo/extra-bindgen-flags.in1
-rw-r--r--netwerk/socket/neqo_glue/Cargo.toml26
-rw-r--r--netwerk/socket/neqo_glue/NeqoHttp3Conn.h107
-rw-r--r--netwerk/socket/neqo_glue/cbindgen.toml26
-rw-r--r--netwerk/socket/neqo_glue/moz.build21
-rw-r--r--netwerk/socket/neqo_glue/src/lib.rs816
-rw-r--r--netwerk/socket/nsINamedPipeService.idl77
-rw-r--r--netwerk/socket/nsISOCKSSocketInfo.idl24
-rw-r--r--netwerk/socket/nsISSLSocketControl.idl170
-rw-r--r--netwerk/socket/nsISocketProvider.idl114
-rw-r--r--netwerk/socket/nsISocketProviderService.idl20
-rw-r--r--netwerk/socket/nsITransportSecurityInfo.idl114
-rw-r--r--netwerk/socket/nsNamedPipeIOLayer.cpp864
-rw-r--r--netwerk/socket/nsNamedPipeIOLayer.h24
-rw-r--r--netwerk/socket/nsNamedPipeService.cpp318
-rw-r--r--netwerk/socket/nsNamedPipeService.h67
-rw-r--r--netwerk/socket/nsSOCKSIOLayer.cpp1527
-rw-r--r--netwerk/socket/nsSOCKSIOLayer.h22
-rw-r--r--netwerk/socket/nsSOCKSSocketProvider.cpp119
-rw-r--r--netwerk/socket/nsSOCKSSocketProvider.h31
-rw-r--r--netwerk/socket/nsSocketProviderService.cpp70
-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/srtp/src/LICENSE35
-rw-r--r--netwerk/srtp/src/README.md487
-rw-r--r--netwerk/srtp/src/VERSION1
-rw-r--r--netwerk/srtp/src/crypto/Makefile.in119
-rw-r--r--netwerk/srtp/src/crypto/cipher/aes.c2189
-rw-r--r--netwerk/srtp/src/crypto/cipher/aes_gcm_nss.c597
-rw-r--r--netwerk/srtp/src/crypto/cipher/aes_gcm_ossl.c582
-rw-r--r--netwerk/srtp/src/crypto/cipher/aes_icm.c530
-rw-r--r--netwerk/srtp/src/crypto/cipher/aes_icm_nss.c550
-rw-r--r--netwerk/srtp/src/crypto/cipher/aes_icm_ossl.c540
-rw-r--r--netwerk/srtp/src/crypto/cipher/cipher.c679
-rw-r--r--netwerk/srtp/src/crypto/cipher/null_cipher.c153
-rw-r--r--netwerk/srtp/src/crypto/hash/auth.c187
-rw-r--r--netwerk/srtp/src/crypto/hash/hmac.c283
-rw-r--r--netwerk/srtp/src/crypto/hash/hmac_ossl.c273
-rw-r--r--netwerk/srtp/src/crypto/hash/null_auth.c168
-rw-r--r--netwerk/srtp/src/crypto/hash/sha1.c474
-rw-r--r--netwerk/srtp/src/crypto/include/aes.h83
-rw-r--r--netwerk/srtp/src/crypto/include/aes_gcm.h87
-rw-r--r--netwerk/srtp/src/crypto/include/aes_icm.h62
-rw-r--r--netwerk/srtp/src/crypto/include/aes_icm_ext.h81
-rw-r--r--netwerk/srtp/src/crypto/include/alloc.h76
-rw-r--r--netwerk/srtp/src/crypto/include/auth.h173
-rw-r--r--netwerk/srtp/src/crypto/include/cipher.h248
-rw-r--r--netwerk/srtp/src/crypto/include/cipher_types.h84
-rw-r--r--netwerk/srtp/src/crypto/include/crypto_kernel.h215
-rw-r--r--netwerk/srtp/src/crypto/include/crypto_types.h116
-rw-r--r--netwerk/srtp/src/crypto/include/datatypes.h378
-rw-r--r--netwerk/srtp/src/crypto/include/err.h134
-rw-r--r--netwerk/srtp/src/crypto/include/hmac.h58
-rw-r--r--netwerk/srtp/src/crypto/include/integers.h146
-rw-r--r--netwerk/srtp/src/crypto/include/key.h88
-rw-r--r--netwerk/srtp/src/crypto/include/null_auth.h73
-rw-r--r--netwerk/srtp/src/crypto/include/null_cipher.h57
-rw-r--r--netwerk/srtp/src/crypto/include/rdb.h125
-rw-r--r--netwerk/srtp/src/crypto/include/rdbx.h209
-rw-r--r--netwerk/srtp/src/crypto/include/sha1.h184
-rw-r--r--netwerk/srtp/src/crypto/include/stat.h66
-rw-r--r--netwerk/srtp/src/crypto/kernel/alloc.c101
-rw-r--r--netwerk/srtp/src/crypto/kernel/crypto_kernel.c561
-rw-r--r--netwerk/srtp/src/crypto/kernel/err.c108
-rw-r--r--netwerk/srtp/src/crypto/kernel/key.c122
-rw-r--r--netwerk/srtp/src/crypto/math/datatypes.c490
-rw-r--r--netwerk/srtp/src/crypto/math/stat.c213
-rw-r--r--netwerk/srtp/src/crypto/replay/rdb.c137
-rw-r--r--netwerk/srtp/src/crypto/replay/rdbx.c386
-rw-r--r--netwerk/srtp/src/crypto/replay/ut_sim.c104
-rw-r--r--netwerk/srtp/src/crypto/test/aes_calc.c157
-rw-r--r--netwerk/srtp/src/crypto/test/cipher_driver.c604
-rw-r--r--netwerk/srtp/src/crypto/test/datatypes_driver.c256
-rw-r--r--netwerk/srtp/src/crypto/test/env.c89
-rw-r--r--netwerk/srtp/src/crypto/test/kernel_driver.c127
-rw-r--r--netwerk/srtp/src/crypto/test/sha1_driver.c387
-rw-r--r--netwerk/srtp/src/crypto/test/stat_driver.c258
-rw-r--r--netwerk/srtp/src/include/config.h6
-rw-r--r--netwerk/srtp/src/include/ekt.h181
-rw-r--r--netwerk/srtp/src/include/getopt_s.h67
-rw-r--r--netwerk/srtp/src/include/srtp.h1759
-rw-r--r--netwerk/srtp/src/include/srtp_priv.h280
-rw-r--r--netwerk/srtp/src/include/ut_sim.h83
-rw-r--r--netwerk/srtp/src/moz.build76
-rw-r--r--netwerk/srtp/src/srtp/ekt.c281
-rw-r--r--netwerk/srtp/src/srtp/srtp.c4730
-rw-r--r--netwerk/srtp/src/test/cutest.h713
-rw-r--r--netwerk/srtp/src/test/dtls_srtp_driver.c261
-rw-r--r--netwerk/srtp/src/test/getopt_s.c109
-rw-r--r--netwerk/srtp/src/test/rdbx_driver.c360
-rw-r--r--netwerk/srtp/src/test/replay_driver.c283
-rw-r--r--netwerk/srtp/src/test/roc_driver.c170
-rw-r--r--netwerk/srtp/src/test/rtp.c227
-rw-r--r--netwerk/srtp/src/test/rtp.h155
-rw-r--r--netwerk/srtp/src/test/rtp_decoder.c574
-rw-r--r--netwerk/srtp/src/test/rtp_decoder.h105
-rw-r--r--netwerk/srtp/src/test/rtpw.c701
-rwxr-xr-xnetwerk/srtp/src/test/rtpw_test.sh176
-rwxr-xr-xnetwerk/srtp/src/test/rtpw_test_gcm.sh260
-rw-r--r--netwerk/srtp/src/test/srtp_driver.c3837
-rw-r--r--netwerk/srtp/src/test/test_srtp.c185
-rw-r--r--netwerk/srtp/src/test/util.c211
-rw-r--r--netwerk/srtp/src/test/util.h53
-rw-r--r--netwerk/srtp/src/test/words.txt250
-rw-r--r--netwerk/srtp/srtp_update.log3
-rw-r--r--netwerk/srtp/update_srtp.sh29
-rw-r--r--netwerk/streamconv/converters/ParseFTPList.cpp1493
-rw-r--r--netwerk/streamconv/converters/ParseFTPList.h102
-rw-r--r--netwerk/streamconv/converters/moz.build32
-rw-r--r--netwerk/streamconv/converters/mozTXTToHTMLConv.cpp1260
-rw-r--r--netwerk/streamconv/converters/mozTXTToHTMLConv.h284
-rw-r--r--netwerk/streamconv/converters/nsDirIndex.cpp89
-rw-r--r--netwerk/streamconv/converters/nsDirIndex.h32
-rw-r--r--netwerk/streamconv/converters/nsDirIndexParser.cpp444
-rw-r--r--netwerk/streamconv/converters/nsDirIndexParser.h75
-rw-r--r--netwerk/streamconv/converters/nsFTPDirListingConv.cpp342
-rw-r--r--netwerk/streamconv/converters/nsFTPDirListingConv.h52
-rw-r--r--netwerk/streamconv/converters/nsHTTPCompressConv.cpp722
-rw-r--r--netwerk/streamconv/converters/nsHTTPCompressConv.h137
-rw-r--r--netwerk/streamconv/converters/nsICompressConvStats.idl17
-rw-r--r--netwerk/streamconv/converters/nsIndexedToHTML.cpp847
-rw-r--r--netwerk/streamconv/converters/nsIndexedToHTML.h61
-rw-r--r--netwerk/streamconv/converters/nsMultiMixedConv.cpp1038
-rw-r--r--netwerk/streamconv/converters/nsMultiMixedConv.h256
-rw-r--r--netwerk/streamconv/converters/nsUnknownDecoder.cpp894
-rw-r--r--netwerk/streamconv/converters/nsUnknownDecoder.h166
-rw-r--r--netwerk/streamconv/moz.build24
-rw-r--r--netwerk/streamconv/mozITXTToHTMLConv.idl88
-rw-r--r--netwerk/streamconv/nsIDirIndex.idl72
-rw-r--r--netwerk/streamconv/nsIDirIndexListener.idl63
-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.cpp549
-rw-r--r--netwerk/streamconv/nsStreamConverterService.h47
-rw-r--r--netwerk/system/NetworkLinkServiceDefines.h21
-rw-r--r--netwerk/system/android/moz.build14
-rw-r--r--netwerk/system/android/nsAndroidNetworkLinkService.cpp224
-rw-r--r--netwerk/system/android/nsAndroidNetworkLinkService.h48
-rw-r--r--netwerk/system/linux/moz.build12
-rw-r--r--netwerk/system/linux/nsNetworkLinkService.cpp188
-rw-r--r--netwerk/system/linux/nsNetworkLinkService.h47
-rw-r--r--netwerk/system/mac/moz.build14
-rw-r--r--netwerk/system/mac/nsNetworkLinkService.h80
-rw-r--r--netwerk/system/mac/nsNetworkLinkService.mm1065
-rw-r--r--netwerk/system/moz.build17
-rw-r--r--netwerk/system/netlink/NetlinkService.cpp1838
-rw-r--r--netwerk/system/netlink/NetlinkService.h166
-rw-r--r--netwerk/system/netlink/moz.build12
-rw-r--r--netwerk/system/win32/moz.build12
-rw-r--r--netwerk/system/win32/nsNotifyAddrListener.cpp670
-rw-r--r--netwerk/system/win32/nsNotifyAddrListener.h93
-rw-r--r--netwerk/test/browser/browser.ini31
-rw-r--r--netwerk/test/browser/browser_NetUtil.js112
-rw-r--r--netwerk/test/browser/browser_about_cache.js126
-rw-r--r--netwerk/test/browser/browser_bug1535877.js15
-rw-r--r--netwerk/test/browser/browser_child_resource.js247
-rw-r--r--netwerk/test/browser/browser_cookie_sync_across_tabs.js79
-rw-r--r--netwerk/test/browser/browser_fetch_lnk.js27
-rw-r--r--netwerk/test/browser/browser_nsIFormPOSTActionChannel.js292
-rw-r--r--netwerk/test/browser/browser_post_file.js78
-rw-r--r--netwerk/test/browser/browser_resource_navigation.js76
-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/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/file_favicon.html7
-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/redirect.sjs7
-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/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.list6
-rw-r--r--netwerk/test/fuzz/FuzzingStreamListener.cpp45
-rw-r--r--netwerk/test/fuzz/FuzzingStreamListener.h36
-rw-r--r--netwerk/test/fuzz/TestFtpFuzzing.cpp141
-rw-r--r--netwerk/test/fuzz/TestHttpFuzzing.cpp274
-rw-r--r--netwerk/test/fuzz/TestURIFuzzing.cpp240
-rw-r--r--netwerk/test/fuzz/TestWebsocketFuzzing.cpp222
-rw-r--r--netwerk/test/fuzz/moz.build29
-rw-r--r--netwerk/test/fuzz/url_tokens.dict51
-rw-r--r--netwerk/test/gtest/TestBase64Stream.cpp95
-rw-r--r--netwerk/test/gtest/TestBind.cpp176
-rw-r--r--netwerk/test/gtest/TestBufferedInputStream.cpp183
-rw-r--r--netwerk/test/gtest/TestCommon.cpp7
-rw-r--r--netwerk/test/gtest/TestCommon.h44
-rw-r--r--netwerk/test/gtest/TestCookie.cpp1062
-rw-r--r--netwerk/test/gtest/TestHeaders.cpp29
-rw-r--r--netwerk/test/gtest/TestHttpAuthUtils.cpp43
-rw-r--r--netwerk/test/gtest/TestHttpResponseHead.cpp109
-rw-r--r--netwerk/test/gtest/TestInputStreamTransport.cpp190
-rw-r--r--netwerk/test/gtest/TestIsValidIp.cpp178
-rw-r--r--netwerk/test/gtest/TestMIMEInputStream.cpp261
-rw-r--r--netwerk/test/gtest/TestMozURL.cpp390
-rw-r--r--netwerk/test/gtest/TestNamedPipeService.cpp279
-rw-r--r--netwerk/test/gtest/TestNetworkLinkIdHashingDarwin.cpp93
-rw-r--r--netwerk/test/gtest/TestNetworkLinkIdHashingWindows.cpp88
-rw-r--r--netwerk/test/gtest/TestPACMan.cpp242
-rw-r--r--netwerk/test/gtest/TestPartiallySeekableInputStream.cpp494
-rw-r--r--netwerk/test/gtest/TestProtocolProxyService.cpp164
-rw-r--r--netwerk/test/gtest/TestReadStreamToString.cpp190
-rw-r--r--netwerk/test/gtest/TestServerTimingHeader.cpp239
-rw-r--r--netwerk/test/gtest/TestSocketTransportService.cpp140
-rw-r--r--netwerk/test/gtest/TestStandardURL.cpp359
-rw-r--r--netwerk/test/gtest/TestSupportAlpn.cpp61
-rw-r--r--netwerk/test/gtest/TestUDPSocket.cpp395
-rw-r--r--netwerk/test/gtest/TestURIMutator.cpp128
-rw-r--r--netwerk/test/gtest/moz.build84
-rw-r--r--netwerk/test/gtest/parse-ftp/3-guess.in30
-rw-r--r--netwerk/test/gtest/parse-ftp/3-guess.out28
-rw-r--r--netwerk/test/gtest/parse-ftp/C-VMold.in16
-rw-r--r--netwerk/test/gtest/parse-ftp/C-VMold.out12
-rw-r--r--netwerk/test/gtest/parse-ftp/C-zVM.in61
-rw-r--r--netwerk/test/gtest/parse-ftp/C-zVM.out19
-rw-r--r--netwerk/test/gtest/parse-ftp/D-WinNT.in62
-rw-r--r--netwerk/test/gtest/parse-ftp/D-WinNT.out49
-rw-r--r--netwerk/test/gtest/parse-ftp/E-EPLF.in189
-rw-r--r--netwerk/test/gtest/parse-ftp/E-EPLF.out178
-rw-r--r--netwerk/test/gtest/parse-ftp/O-guess.in19
-rw-r--r--netwerk/test/gtest/parse-ftp/O-guess.out13
-rw-r--r--netwerk/test/gtest/parse-ftp/R-dls.in23
-rw-r--r--netwerk/test/gtest/parse-ftp/R-dls.out22
-rw-r--r--netwerk/test/gtest/parse-ftp/README28
-rw-r--r--netwerk/test/gtest/parse-ftp/TestParseFTPList.cpp138
-rw-r--r--netwerk/test/gtest/parse-ftp/U-HellSoft.in25
-rw-r--r--netwerk/test/gtest/parse-ftp/U-HellSoft.out16
-rw-r--r--netwerk/test/gtest/parse-ftp/U-NetPresenz.in31
-rw-r--r--netwerk/test/gtest/parse-ftp/U-NetPresenz.out17
-rw-r--r--netwerk/test/gtest/parse-ftp/U-NetWare.in51
-rw-r--r--netwerk/test/gtest/parse-ftp/U-NetWare.out31
-rw-r--r--netwerk/test/gtest/parse-ftp/U-Novonyx.in92
-rw-r--r--netwerk/test/gtest/parse-ftp/U-Novonyx.out78
-rw-r--r--netwerk/test/gtest/parse-ftp/U-Surge.in66
-rw-r--r--netwerk/test/gtest/parse-ftp/U-Surge.out52
-rw-r--r--netwerk/test/gtest/parse-ftp/U-WarFTPd.in38
-rw-r--r--netwerk/test/gtest/parse-ftp/U-WarFTPd.out22
-rw-r--r--netwerk/test/gtest/parse-ftp/U-WebStar.in41
-rw-r--r--netwerk/test/gtest/parse-ftp/U-WebStar.out21
-rw-r--r--netwerk/test/gtest/parse-ftp/U-WinNT.in68
-rw-r--r--netwerk/test/gtest/parse-ftp/U-WinNT.out54
-rw-r--r--netwerk/test/gtest/parse-ftp/U-hethmon.in42
-rw-r--r--netwerk/test/gtest/parse-ftp/U-hethmon.out30
-rw-r--r--netwerk/test/gtest/parse-ftp/U-murksw.in29
-rw-r--r--netwerk/test/gtest/parse-ftp/U-murksw.out14
-rw-r--r--netwerk/test/gtest/parse-ftp/U-ncFTPd.in1411
-rw-r--r--netwerk/test/gtest/parse-ftp/U-ncFTPd.out1377
-rw-r--r--netwerk/test/gtest/parse-ftp/U-no_ug.in84
-rw-r--r--netwerk/test/gtest/parse-ftp/U-no_ug.out26
-rw-r--r--netwerk/test/gtest/parse-ftp/U-nogid.in82
-rw-r--r--netwerk/test/gtest/parse-ftp/U-nogid.out47
-rw-r--r--netwerk/test/gtest/parse-ftp/U-proftpd.in21
-rw-r--r--netwerk/test/gtest/parse-ftp/U-proftpd.out7
-rw-r--r--netwerk/test/gtest/parse-ftp/U-wu.in71
-rw-r--r--netwerk/test/gtest/parse-ftp/U-wu.out15
-rw-r--r--netwerk/test/gtest/parse-ftp/V-MultiNet.in34
-rw-r--r--netwerk/test/gtest/parse-ftp/V-MultiNet.out14
-rw-r--r--netwerk/test/gtest/parse-ftp/V-VMS-mix.in22
-rw-r--r--netwerk/test/gtest/parse-ftp/V-VMS-mix.out10
-rw-r--r--netwerk/test/gtest/parse-ftp/moz.build68
-rw-r--r--netwerk/test/gtest/urltestdata-orig.json6148
-rw-r--r--netwerk/test/gtest/urltestdata.json6480
-rw-r--r--netwerk/test/http3server/Cargo.toml28
-rw-r--r--netwerk/test/http3server/moz.build16
-rw-r--r--netwerk/test/http3server/src/main.rs620
-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.js5475
-rw-r--r--netwerk/test/httpserver/moz.build21
-rw-r--r--netwerk/test/httpserver/nsIHttpServer.idl624
-rw-r--r--netwerk/test/httpserver/test/.eslintrc.js5
-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.sjs87
-rw-r--r--netwerk/test/httpserver/test/data/sjs/qi.sjs46
-rw-r--r--netwerk/test/httpserver/test/data/sjs/range-checker.sjs3
-rw-r--r--netwerk/test/httpserver/test/data/sjs/sjs4
-rw-r--r--netwerk/test/httpserver/test/data/sjs/state1.sjs42
-rw-r--r--netwerk/test/httpserver/test/data/sjs/state2.sjs42
-rw-r--r--netwerk/test/httpserver/test/data/sjs/thrower.sjs6
-rw-r--r--netwerk/test/httpserver/test/head_utils.js567
-rw-r--r--netwerk/test/httpserver/test/test_async_response_sending.js1670
-rw-r--r--netwerk/test/httpserver/test/test_basic_functionality.js181
-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.js247
-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_linedata.js19
-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.js131
-rw-r--r--netwerk/test/httpserver/test/test_request_line_split_in_two_packets.js138
-rw-r--r--netwerk/test/httpserver/test/test_response_write.js57
-rw-r--r--netwerk/test/httpserver/test/test_seizepower.js188
-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/xpcshell.ini35
-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.js26
-rw-r--r--netwerk/test/mochitests/file_1502055.sjs18
-rw-r--r--netwerk/test/mochitests/file_1503201.sjs4
-rw-r--r--netwerk/test/mochitests/file_chromecommon.js14
-rw-r--r--netwerk/test/mochitests/file_documentcookie_maxage_chromescript.js44
-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.sjs106
-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.js132
-rw-r--r--netwerk/test/mochitests/file_testloadflags_chromescript.js143
-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.sjs12
-rw-r--r--netwerk/test/mochitests/mochitest.ini98
-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.sjs151
-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.sjs15
-rw-r--r--netwerk/test/mochitests/reset_cookie_xhr.sjs13
-rw-r--r--netwerk/test/mochitests/set_cookie_xhr.sjs13
-rw-r--r--netwerk/test/mochitests/signed_web_packaged_app.sjs74
-rw-r--r--netwerk/test/mochitests/subResources.sjs47
-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.html78
-rw-r--r--netwerk/test/mochitests/test_1331680_iframe.html65
-rw-r--r--netwerk/test/mochitests/test_1331680_xhr.html87
-rw-r--r--netwerk/test/mochitests/test_1396395.html50
-rw-r--r--netwerk/test/mochitests/test_1421324.html85
-rw-r--r--netwerk/test/mochitests/test_1425031.html67
-rw-r--r--netwerk/test/mochitests/test_1502055.html36
-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.sjs49
-rw-r--r--netwerk/test/mochitests/test_arraybufferinputstream.html89
-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.html225
-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_viewsource_unlinkable.html25
-rw-r--r--netwerk/test/mochitests/test_xhr_method_case.html65
-rw-r--r--netwerk/test/mochitests/web_packaged_app.sjs31
-rw-r--r--netwerk/test/moz.build20
-rw-r--r--netwerk/test/perf/.eslintrc.js12
-rw-r--r--netwerk/test/perf/hooks_throttling.py203
-rw-r--r--netwerk/test/perf/perftest.ini8
-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.js166
-rw-r--r--netwerk/test/perf/perftest_http3_google_image.js191
-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/CA.key.pem30
-rw-r--r--netwerk/test/unit/client_cert_chooser.js27
-rw-r--r--netwerk/test/unit/client_cert_chooser.manifest2
-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.js167
-rw-r--r--netwerk/test/unit/head_cache2.js428
-rw-r--r--netwerk/test/unit/head_channels.js456
-rw-r--r--netwerk/test/unit/head_cookies.js973
-rw-r--r--netwerk/test/unit/head_http3.js105
-rw-r--r--netwerk/test/unit/head_trr.js364
-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/moz.build8
-rw-r--r--netwerk/test/unit/perftest.ini1
-rw-r--r--netwerk/test/unit/socks_client_subprocess.js101
-rw-r--r--netwerk/test/unit/test_1073747.js42
-rw-r--r--netwerk/test/unit/test_304_responses.js106
-rw-r--r--netwerk/test/unit/test_307_redirect.js96
-rw-r--r--netwerk/test/unit/test_421.js64
-rw-r--r--netwerk/test/unit/test_MIME_params.js784
-rw-r--r--netwerk/test/unit/test_NetUtil.js818
-rw-r--r--netwerk/test/unit/test_SuperfluousAuth.js99
-rw-r--r--netwerk/test/unit/test_URIs.js1042
-rw-r--r--netwerk/test/unit/test_URIs2.js906
-rw-r--r--netwerk/test/unit/test_XHR_redirects.js273
-rw-r--r--netwerk/test/unit/test_about_networking.js121
-rw-r--r--netwerk/test/unit/test_about_protocol.js57
-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.js174
-rw-r--r--netwerk/test/unit/test_alt-data_cross_process.js143
-rw-r--r--netwerk/test/unit/test_alt-data_overwrite.js203
-rw-r--r--netwerk/test/unit/test_alt-data_simple.js196
-rw-r--r--netwerk/test/unit/test_alt-data_stream.js153
-rw-r--r--netwerk/test/unit/test_alt-data_too_big.js113
-rw-r--r--netwerk/test/unit/test_altsvc.js519
-rw-r--r--netwerk/test/unit/test_altsvc_http3.js496
-rw-r--r--netwerk/test/unit/test_altsvc_pref.js139
-rw-r--r--netwerk/test/unit/test_anonymous-coalescing.js186
-rw-r--r--netwerk/test/unit/test_auth_dialog_permission.js283
-rw-r--r--netwerk/test/unit/test_auth_jar.js97
-rw-r--r--netwerk/test/unit/test_auth_proxy.js465
-rw-r--r--netwerk/test/unit/test_authentication.js754
-rw-r--r--netwerk/test/unit/test_authpromptwrapper.js233
-rw-r--r--netwerk/test/unit/test_backgroundfilesaver.js775
-rw-r--r--netwerk/test/unit/test_be_conservative.js247
-rw-r--r--netwerk/test/unit/test_be_conservative_error_handling.js242
-rw-r--r--netwerk/test/unit/test_blob_channelname.js42
-rw-r--r--netwerk/test/unit/test_brotli_http.js44
-rw-r--r--netwerk/test/unit/test_bug1064258.js142
-rw-r--r--netwerk/test/unit/test_bug1177909.js194
-rw-r--r--netwerk/test/unit/test_bug1195415.js163
-rw-r--r--netwerk/test/unit/test_bug1218029.js116
-rw-r--r--netwerk/test/unit/test_bug1279246.js98
-rw-r--r--netwerk/test/unit/test_bug1312774_http1.js153
-rw-r--r--netwerk/test/unit/test_bug1312782_http1.js206
-rw-r--r--netwerk/test/unit/test_bug1355539_http1.js207
-rw-r--r--netwerk/test/unit/test_bug1378385_http1.js202
-rw-r--r--netwerk/test/unit/test_bug1411316_http1.js117
-rw-r--r--netwerk/test/unit/test_bug1527293.js92
-rw-r--r--netwerk/test/unit/test_bug1683176.js104
-rw-r--r--netwerk/test/unit/test_bug203271.js248
-rw-r--r--netwerk/test/unit/test_bug248970_cache.js168
-rw-r--r--netwerk/test/unit/test_bug248970_cookie.js150
-rw-r--r--netwerk/test/unit/test_bug261425.js37
-rw-r--r--netwerk/test/unit/test_bug263127.js58
-rw-r--r--netwerk/test/unit/test_bug282432.js38
-rw-r--r--netwerk/test/unit/test_bug321706.js12
-rw-r--r--netwerk/test/unit/test_bug331825.js41
-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_bug365133.js131
-rw-r--r--netwerk/test/unit/test_bug368702.js153
-rw-r--r--netwerk/test/unit/test_bug369787.js71
-rw-r--r--netwerk/test/unit/test_bug371473.js40
-rw-r--r--netwerk/test/unit/test_bug376844.js23
-rw-r--r--netwerk/test/unit/test_bug376865.js23
-rw-r--r--netwerk/test/unit/test_bug379034.js21
-rw-r--r--netwerk/test/unit/test_bug380994.js26
-rw-r--r--netwerk/test/unit/test_bug388281.js42
-rw-r--r--netwerk/test/unit/test_bug396389.js71
-rw-r--r--netwerk/test/unit/test_bug401564.js44
-rw-r--r--netwerk/test/unit/test_bug411952.js53
-rw-r--r--netwerk/test/unit/test_bug412457.js117
-rw-r--r--netwerk/test/unit/test_bug412945.js42
-rw-r--r--netwerk/test/unit/test_bug414122.js62
-rw-r--r--netwerk/test/unit/test_bug427957.js106
-rw-r--r--netwerk/test/unit/test_bug429347.js75
-rw-r--r--netwerk/test/unit/test_bug455311.js128
-rw-r--r--netwerk/test/unit/test_bug464591.js100
-rw-r--r--netwerk/test/unit/test_bug468426.js129
-rw-r--r--netwerk/test/unit/test_bug468594.js176
-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.js61
-rw-r--r--netwerk/test/unit/test_bug479485.js65
-rw-r--r--netwerk/test/unit/test_bug482601.js262
-rw-r--r--netwerk/test/unit/test_bug482934.js189
-rw-r--r--netwerk/test/unit/test_bug484684.js139
-rw-r--r--netwerk/test/unit/test_bug490095.js158
-rw-r--r--netwerk/test/unit/test_bug504014.js74
-rw-r--r--netwerk/test/unit/test_bug510359.js102
-rw-r--r--netwerk/test/unit/test_bug515583.js82
-rw-r--r--netwerk/test/unit/test_bug526789.js287
-rw-r--r--netwerk/test/unit/test_bug528292.js93
-rw-r--r--netwerk/test/unit/test_bug536324_64bit_content_length.js64
-rw-r--r--netwerk/test/unit/test_bug540566.js24
-rw-r--r--netwerk/test/unit/test_bug543805.js136
-rw-r--r--netwerk/test/unit/test_bug553970.js53
-rw-r--r--netwerk/test/unit/test_bug561042.js42
-rw-r--r--netwerk/test/unit/test_bug561276.js64
-rw-r--r--netwerk/test/unit/test_bug580508.js33
-rw-r--r--netwerk/test/unit/test_bug586908.js104
-rw-r--r--netwerk/test/unit/test_bug596443.js115
-rw-r--r--netwerk/test/unit/test_bug618835.js122
-rw-r--r--netwerk/test/unit/test_bug633743.js193
-rw-r--r--netwerk/test/unit/test_bug650522.js34
-rw-r--r--netwerk/test/unit/test_bug650995.js189
-rw-r--r--netwerk/test/unit/test_bug652761.js19
-rw-r--r--netwerk/test/unit/test_bug654926.js102
-rw-r--r--netwerk/test/unit/test_bug654926_doom_and_read.js90
-rw-r--r--netwerk/test/unit/test_bug654926_test_seek.js76
-rw-r--r--netwerk/test/unit/test_bug659569.js58
-rw-r--r--netwerk/test/unit/test_bug660066.js56
-rw-r--r--netwerk/test/unit/test_bug667087.js32
-rw-r--r--netwerk/test/unit/test_bug667818.js50
-rw-r--r--netwerk/test/unit/test_bug667907.js86
-rw-r--r--netwerk/test/unit/test_bug669001.js179
-rw-r--r--netwerk/test/unit/test_bug767025.js315
-rw-r--r--netwerk/test/unit/test_bug770243.js246
-rw-r--r--netwerk/test/unit/test_bug812167.js141
-rw-r--r--netwerk/test/unit/test_bug826063.js91
-rw-r--r--netwerk/test/unit/test_bug856978.js135
-rw-r--r--netwerk/test/unit/test_bug894586.js157
-rw-r--r--netwerk/test/unit/test_bug935499.js10
-rw-r--r--netwerk/test/unit/test_cache-control_request.js447
-rw-r--r--netwerk/test/unit/test_cache-entry-id.js215
-rw-r--r--netwerk/test/unit/test_cache2-00-service-get.js19
-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.js49
-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-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.js53
-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.js18
-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.js66
-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.js57
-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.js74
-rw-r--r--netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js78
-rw-r--r--netwerk/test/unit/test_cache2-29c-concurrent_read_half-interrupted.js93
-rw-r--r--netwerk/test/unit/test_cache2-29d-concurrent_read_half-corrupted-206.js93
-rw-r--r--netwerk/test/unit/test_cache2-29e-concurrent_read_half-non-206-response.js88
-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.js188
-rw-r--r--netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js151
-rw-r--r--netwerk/test/unit/test_cache2-31-visit-all.js88
-rw-r--r--netwerk/test/unit/test_cache2-32-clear-origin.js68
-rw-r--r--netwerk/test/unit/test_cacheForOfflineUse_no-store.js104
-rw-r--r--netwerk/test/unit/test_cache_204_response.js60
-rw-r--r--netwerk/test/unit/test_cache_jar.js104
-rw-r--r--netwerk/test/unit/test_cacheflags.js436
-rw-r--r--netwerk/test/unit/test_captive_portal_service.js207
-rw-r--r--netwerk/test/unit/test_channel_close.js68
-rw-r--r--netwerk/test/unit/test_channel_priority.js97
-rw-r--r--netwerk/test/unit/test_chunked_responses.js172
-rw-r--r--netwerk/test/unit/test_compareURIs.js60
-rw-r--r--netwerk/test/unit/test_compressappend.js99
-rw-r--r--netwerk/test/unit/test_content_encoding_gzip.js210
-rw-r--r--netwerk/test/unit/test_content_length_underrun.js280
-rw-r--r--netwerk/test/unit/test_content_sniffer.js163
-rw-r--r--netwerk/test/unit/test_cookie_blacklist.js39
-rw-r--r--netwerk/test/unit/test_cookie_header.js113
-rw-r--r--netwerk/test/unit/test_cookie_ipv6.js52
-rw-r--r--netwerk/test/unit/test_cookiejars.js172
-rw-r--r--netwerk/test/unit/test_cookiejars_safebrowsing.js238
-rw-r--r--netwerk/test/unit/test_cookies_async_failure.js508
-rw-r--r--netwerk/test/unit/test_cookies_persistence.js76
-rw-r--r--netwerk/test/unit/test_cookies_privatebrowsing.js139
-rw-r--r--netwerk/test/unit/test_cookies_profile_close.js112
-rw-r--r--netwerk/test/unit/test_cookies_read.js114
-rw-r--r--netwerk/test/unit/test_cookies_sync_failure.js344
-rw-r--r--netwerk/test/unit/test_cookies_thirdparty.js162
-rw-r--r--netwerk/test/unit/test_cookies_thirdparty_session.js71
-rw-r--r--netwerk/test/unit/test_cookies_upgrade_10.js59
-rw-r--r--netwerk/test/unit/test_data_protocol.js91
-rw-r--r--netwerk/test/unit/test_defaultURI.js185
-rw-r--r--netwerk/test/unit/test_disabled_ftp.js21
-rw-r--r--netwerk/test/unit/test_dns_by_type_resolve.js180
-rw-r--r--netwerk/test/unit/test_dns_cancel.js125
-rw-r--r--netwerk/test/unit/test_dns_disable_ipv4.js55
-rw-r--r--netwerk/test/unit/test_dns_disable_ipv6.js56
-rw-r--r--netwerk/test/unit/test_dns_disabled.js94
-rw-r--r--netwerk/test/unit/test_dns_localredirect.js68
-rw-r--r--netwerk/test/unit/test_dns_offline.js113
-rw-r--r--netwerk/test/unit/test_dns_onion.js82
-rw-r--r--netwerk/test/unit/test_dns_originAttributes.js99
-rw-r--r--netwerk/test/unit/test_dns_override.js311
-rw-r--r--netwerk/test/unit/test_dns_override_for_localhost.js92
-rw-r--r--netwerk/test/unit/test_dns_proxy_bypass.js82
-rw-r--r--netwerk/test/unit/test_dns_service.js68
-rw-r--r--netwerk/test/unit/test_domain_eviction.js185
-rw-r--r--netwerk/test/unit/test_doomentry.js108
-rw-r--r--netwerk/test/unit/test_duplicate_headers.js558
-rw-r--r--netwerk/test/unit/test_event_sink.js195
-rw-r--r--netwerk/test/unit/test_eviction.js252
-rw-r--r--netwerk/test/unit/test_extract_charset_from_content_type.js245
-rw-r--r--netwerk/test/unit/test_fallback_no-cache-entry_canceled.js131
-rw-r--r--netwerk/test/unit/test_fallback_no-cache-entry_passing.js130
-rw-r--r--netwerk/test/unit/test_fallback_redirect-to-different-origin_canceled.js133
-rw-r--r--netwerk/test/unit/test_fallback_redirect-to-different-origin_passing.js132
-rw-r--r--netwerk/test/unit/test_fallback_request-error_canceled.js140
-rw-r--r--netwerk/test/unit/test_fallback_request-error_passing.js136
-rw-r--r--netwerk/test/unit/test_fallback_response-error_canceled.js133
-rw-r--r--netwerk/test/unit/test_fallback_response-error_passing.js132
-rw-r--r--netwerk/test/unit/test_file_protocol.js280
-rw-r--r--netwerk/test/unit/test_filestreams.js306
-rw-r--r--netwerk/test/unit/test_freshconnection.js30
-rw-r--r--netwerk/test/unit/test_getHost.js63
-rw-r--r--netwerk/test/unit/test_gre_resources.js32
-rw-r--r--netwerk/test/unit/test_gzipped_206.js167
-rw-r--r--netwerk/test/unit/test_head.js169
-rw-r--r--netwerk/test/unit/test_head_request_no_response_body.js76
-rw-r--r--netwerk/test/unit/test_header_Accept-Language.js101
-rw-r--r--netwerk/test/unit/test_header_Accept-Language_case.js53
-rw-r--r--netwerk/test/unit/test_header_Server_Timing.js67
-rw-r--r--netwerk/test/unit/test_headers.js175
-rw-r--r--netwerk/test/unit/test_hostnameIsLocalIPAddress.js41
-rw-r--r--netwerk/test/unit/test_hostnameIsSharedIPAddress.js21
-rw-r--r--netwerk/test/unit/test_http1-proxy.js229
-rw-r--r--netwerk/test/unit/test_http2-proxy.js615
-rw-r--r--netwerk/test/unit/test_http2.js1442
-rw-r--r--netwerk/test/unit/test_http3.js569
-rw-r--r--netwerk/test/unit/test_http3_421.js175
-rw-r--r--netwerk/test/unit/test_http3_alt_svc.js129
-rw-r--r--netwerk/test/unit/test_http3_error_before_connect.js123
-rw-r--r--netwerk/test/unit/test_http3_fast_fallback.js597
-rw-r--r--netwerk/test/unit/test_http3_fatal_stream_error.js149
-rw-r--r--netwerk/test/unit/test_http3_large_post.js101
-rw-r--r--netwerk/test/unit/test_http3_perf.js262
-rw-r--r--netwerk/test/unit/test_http3_server_not_existing.js123
-rw-r--r--netwerk/test/unit/test_http3_trans_close.js84
-rw-r--r--netwerk/test/unit/test_httpResponseTimeout.js162
-rw-r--r--netwerk/test/unit/test_http_headers.js75
-rw-r--r--netwerk/test/unit/test_http_sfv.js590
-rw-r--r--netwerk/test/unit/test_httpauth.js204
-rw-r--r--netwerk/test/unit/test_httpcancel.js260
-rw-r--r--netwerk/test/unit/test_httpssvc_https_upgrade.js298
-rw-r--r--netwerk/test/unit/test_httpssvc_iphint.js301
-rw-r--r--netwerk/test/unit/test_httpssvc_priority.js235
-rw-r--r--netwerk/test/unit/test_httpssvc_retry_with_ech.js288
-rw-r--r--netwerk/test/unit/test_httpssvc_retry_without_ech.js223
-rw-r--r--netwerk/test/unit/test_httpsuspend.js84
-rw-r--r--netwerk/test/unit/test_idn_blacklist.js173
-rw-r--r--netwerk/test/unit/test_idn_urls.js441
-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.js167
-rw-r--r--netwerk/test/unit/test_inhibit_caching.js92
-rw-r--r--netwerk/test/unit/test_ioservice.js19
-rw-r--r--netwerk/test/unit/test_large_port.js68
-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.js94
-rw-r--r--netwerk/test/unit/test_localstreams.js90
-rw-r--r--netwerk/test/unit/test_mismatch_last-modified.js155
-rw-r--r--netwerk/test/unit/test_mozTXTToHTMLConv.js395
-rw-r--r--netwerk/test/unit/test_multipart_byteranges.js141
-rw-r--r--netwerk/test/unit/test_multipart_streamconv-byte-by-byte.js113
-rw-r--r--netwerk/test/unit/test_multipart_streamconv.js98
-rw-r--r--netwerk/test/unit/test_multipart_streamconv_empty.js68
-rw-r--r--netwerk/test/unit/test_multipart_streamconv_missing_boundary_lead_dashes.js90
-rw-r--r--netwerk/test/unit/test_multipart_streamconv_missing_lead_boundary.js90
-rw-r--r--netwerk/test/unit/test_nestedabout_serialize.js41
-rw-r--r--netwerk/test/unit/test_net_addr.js222
-rw-r--r--netwerk/test/unit/test_network_activity.js61
-rw-r--r--netwerk/test/unit/test_network_connectivity_service.js215
-rw-r--r--netwerk/test/unit/test_no_cookies_after_last_pb_exit.js134
-rw-r--r--netwerk/test/unit/test_node_execute.js87
-rw-r--r--netwerk/test/unit/test_nojsredir.js65
-rw-r--r--netwerk/test/unit/test_nsIBufferedOutputStream_writeFrom_block.js193
-rw-r--r--netwerk/test/unit/test_obs-fold.js73
-rw-r--r--netwerk/test/unit/test_offline_status.js19
-rw-r--r--netwerk/test/unit/test_offlinecache_custom-directory.js165
-rw-r--r--netwerk/test/unit/test_origin.js330
-rw-r--r--netwerk/test/unit/test_original_sent_received_head.js246
-rw-r--r--netwerk/test/unit/test_parse_content_type.js365
-rw-r--r--netwerk/test/unit/test_partial_response_entry_size_smart_shrink.js104
-rw-r--r--netwerk/test/unit/test_permmgr.js131
-rw-r--r--netwerk/test/unit/test_ping_aboutnetworking.js105
-rw-r--r--netwerk/test/unit/test_pinned_app_cache.js302
-rw-r--r--netwerk/test/unit/test_plaintext_sniff.js209
-rw-r--r--netwerk/test/unit/test_port_remapping.js48
-rw-r--r--netwerk/test/unit/test_post.js139
-rw-r--r--netwerk/test/unit/test_predictor.js771
-rw-r--r--netwerk/test/unit/test_private_cookie_changed.js44
-rw-r--r--netwerk/test/unit/test_private_necko_channel.js56
-rw-r--r--netwerk/test/unit/test_progress.js132
-rw-r--r--netwerk/test/unit/test_protocolproxyservice-async-filters.js440
-rw-r--r--netwerk/test/unit/test_protocolproxyservice.js1049
-rw-r--r--netwerk/test/unit/test_proxy-failover_canceled.js58
-rw-r--r--netwerk/test/unit/test_proxy-failover_passing.js46
-rw-r--r--netwerk/test/unit/test_proxy-replace_canceled.js58
-rw-r--r--netwerk/test/unit/test_proxy-replace_passing.js46
-rw-r--r--netwerk/test/unit/test_proxyconnect.js358
-rw-r--r--netwerk/test/unit/test_psl.js45
-rw-r--r--netwerk/test/unit/test_race_cache_with_network.js284
-rw-r--r--netwerk/test/unit/test_range_requests.js527
-rw-r--r--netwerk/test/unit/test_rcwn_always_cache_new_content.js115
-rw-r--r--netwerk/test/unit/test_readline.js104
-rw-r--r--netwerk/test/unit/test_redirect-caching_canceled.js62
-rw-r--r--netwerk/test/unit/test_redirect-caching_failure.js82
-rw-r--r--netwerk/test/unit/test_redirect-caching_passing.js54
-rw-r--r--netwerk/test/unit/test_redirect_baduri.js42
-rw-r--r--netwerk/test/unit/test_redirect_canceled.js48
-rw-r--r--netwerk/test/unit/test_redirect_different-protocol.js54
-rw-r--r--netwerk/test/unit/test_redirect_failure.js60
-rw-r--r--netwerk/test/unit/test_redirect_from_script.js249
-rw-r--r--netwerk/test/unit/test_redirect_from_script_after-open_passing.js249
-rw-r--r--netwerk/test/unit/test_redirect_history.js74
-rw-r--r--netwerk/test/unit/test_redirect_loop.js86
-rw-r--r--netwerk/test/unit/test_redirect_passing.js53
-rw-r--r--netwerk/test/unit/test_redirect_protocol_telemetry.js66
-rw-r--r--netwerk/test/unit/test_reentrancy.js107
-rw-r--r--netwerk/test/unit/test_referrer.js247
-rw-r--r--netwerk/test/unit/test_referrer_cross_origin.js319
-rw-r--r--netwerk/test/unit/test_referrer_policy.js143
-rw-r--r--netwerk/test/unit/test_reopen.js147
-rw-r--r--netwerk/test/unit/test_reply_without_content_type.js150
-rw-r--r--netwerk/test/unit/test_resumable_channel.js424
-rw-r--r--netwerk/test/unit/test_resumable_truncate.js93
-rw-r--r--netwerk/test/unit/test_safeoutputstream.js72
-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_2_migration.js303
-rw-r--r--netwerk/test/unit/test_schema_3_migration.js170
-rw-r--r--netwerk/test/unit/test_separate_connections.js102
-rw-r--r--netwerk/test/unit/test_signature_extraction.js215
-rw-r--r--netwerk/test/unit/test_simple.js69
-rw-r--r--netwerk/test/unit/test_sockettransportsvc_available.js11
-rw-r--r--netwerk/test/unit/test_socks.js519
-rw-r--r--netwerk/test/unit/test_speculative_connect.js368
-rw-r--r--netwerk/test/unit/test_stale-while-revalidate_loop.js46
-rw-r--r--netwerk/test/unit/test_stale-while-revalidate_max-age-0.js111
-rw-r--r--netwerk/test/unit/test_stale-while-revalidate_negative.js90
-rw-r--r--netwerk/test/unit/test_stale-while-revalidate_positive.js111
-rw-r--r--netwerk/test/unit/test_standardurl.js1299
-rw-r--r--netwerk/test/unit/test_standardurl_default_port.js61
-rw-r--r--netwerk/test/unit/test_standardurl_port.js71
-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.js99
-rw-r--r--netwerk/test/unit/test_suspend_channel_on_authRetry.js276
-rw-r--r--netwerk/test/unit/test_suspend_channel_on_examine.js77
-rw-r--r--netwerk/test/unit/test_suspend_channel_on_examine_merged_response.js212
-rw-r--r--netwerk/test/unit/test_suspend_channel_on_modified.js181
-rw-r--r--netwerk/test/unit/test_synthesized_response.js294
-rw-r--r--netwerk/test/unit/test_throttlechannel.js46
-rw-r--r--netwerk/test/unit/test_throttlequeue.js25
-rw-r--r--netwerk/test/unit/test_throttling.js64
-rw-r--r--netwerk/test/unit/test_tldservice_nextsubdomain.js28
-rw-r--r--netwerk/test/unit/test_tls_flags.js274
-rw-r--r--netwerk/test/unit/test_tls_flags_separate_connections.js119
-rw-r--r--netwerk/test/unit/test_tls_server.js301
-rw-r--r--netwerk/test/unit/test_tls_server_multiple_clients.js152
-rw-r--r--netwerk/test/unit/test_traceable_channel.js145
-rw-r--r--netwerk/test/unit/test_trackingProtection_annotateChannels.js388
-rw-r--r--netwerk/test/unit/test_trr.js2136
-rw-r--r--netwerk/test/unit/test_trr_additional_section.js314
-rw-r--r--netwerk/test/unit/test_trr_case_sensitivity.js143
-rw-r--r--netwerk/test/unit/test_trr_cname_chain.js110
-rw-r--r--netwerk/test/unit/test_trr_extended_error.js340
-rw-r--r--netwerk/test/unit/test_trr_https_fallback.js1090
-rw-r--r--netwerk/test/unit/test_trr_httpssvc.js669
-rw-r--r--netwerk/test/unit/test_trr_nat64.js121
-rw-r--r--netwerk/test/unit/test_trr_proxy.js133
-rw-r--r--netwerk/test/unit/test_udp_multicast.js114
-rw-r--r--netwerk/test/unit/test_udpsocket.js89
-rw-r--r--netwerk/test/unit/test_udpsocket_offline.js108
-rw-r--r--netwerk/test/unit/test_unescapestring.js34
-rw-r--r--netwerk/test/unit/test_unix_domain.js705
-rw-r--r--netwerk/test/unit/test_uri_mutator.js48
-rw-r--r--netwerk/test/unit/test_use_httpssvc.js325
-rw-r--r--netwerk/test/unit/test_websocket_offline.js51
-rw-r--r--netwerk/test/unit/test_xmlhttprequest.js55
-rw-r--r--netwerk/test/unit/xpcshell.ini517
-rw-r--r--netwerk/test/unit_ipc/child_channel_id.js47
-rw-r--r--netwerk/test/unit_ipc/child_cookie_header.js92
-rw-r--r--netwerk/test/unit_ipc/child_dns_by_type_resolve.js55
-rw-r--r--netwerk/test/unit_ipc/head_channels_clone.js10
-rw-r--r--netwerk/test/unit_ipc/head_trr_clone.js8
-rw-r--r--netwerk/test/unit_ipc/test_XHR_redirects.js3
-rw-r--r--netwerk/test/unit_ipc/test_alt-data_closeWithStatus_wrap.js21
-rw-r--r--netwerk/test/unit_ipc/test_alt-data_cross_process_wrap.js91
-rw-r--r--netwerk/test/unit_ipc/test_alt-data_simple_wrap.js19
-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.js15
-rw-r--r--netwerk/test/unit_ipc/test_cache_jar_wrap.js4
-rw-r--r--netwerk/test/unit_ipc/test_cacheflags_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_channel_close_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_channel_id.js108
-rw-r--r--netwerk/test/unit_ipc/test_channel_priority_wrap.js48
-rw-r--r--netwerk/test/unit_ipc/test_chunked_responses_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_cookie_header_stripped.js93
-rw-r--r--netwerk/test/unit_ipc/test_cookiejars_wrap.js11
-rw-r--r--netwerk/test/unit_ipc/test_dns_by_type_resolve_wrap.js69
-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_head_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_headers_wrap.js7
-rw-r--r--netwerk/test/unit_ipc/test_httpcancel_wrap.js51
-rw-r--r--netwerk/test/unit_ipc/test_httpsuspend_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/test_multipart_streamconv_wrap.js3
-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.js13
-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.js10
-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_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.js27
-rw-r--r--netwerk/test/unit_ipc/test_trackingProtection_annotateChannels_wrap2.js27
-rw-r--r--netwerk/test/unit_ipc/test_trr_httpssvc_wrap.js81
-rw-r--r--netwerk/test/unit_ipc/test_xmlhttprequest_wrap.js3
-rw-r--r--netwerk/test/unit_ipc/xpcshell.ini113
-rw-r--r--netwerk/url-classifier/AsyncUrlChannelClassifier.cpp924
-rw-r--r--netwerk/url-classifier/AsyncUrlChannelClassifier.h27
-rw-r--r--netwerk/url-classifier/ChannelClassifierService.cpp268
-rw-r--r--netwerk/url-classifier/ChannelClassifierService.h78
-rw-r--r--netwerk/url-classifier/UrlClassifierCommon.cpp659
-rw-r--r--netwerk/url-classifier/UrlClassifierCommon.h103
-rw-r--r--netwerk/url-classifier/UrlClassifierExceptionListService.jsm172
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureBase.cpp172
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureBase.h79
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.cpp167
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.cpp197
-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/UrlClassifierFeatureFactory.cpp374
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFactory.h59
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.cpp175
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.cpp203
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFlash.cpp200
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureFlash.h51
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureLoginReputation.cpp102
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureLoginReputation.h46
-rw-r--r--netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp126
-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.cpp175
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.h49
-rw-r--r--netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.cpp199
-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.cpp204
-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.idl59
-rw-r--r--netwerk/url-classifier/nsIURIClassifier.idl121
-rw-r--r--netwerk/url-classifier/nsIUrlClassifierExceptionListService.idl71
-rw-r--r--netwerk/url-classifier/nsIUrlClassifierFeature.idl120
-rw-r--r--netwerk/wifi/moz.build56
-rw-r--r--netwerk/wifi/nsIWifiAccessPoint.idl41
-rw-r--r--netwerk/wifi/nsIWifiListener.idl29
-rw-r--r--netwerk/wifi/nsIWifiMonitor.idl24
-rw-r--r--netwerk/wifi/nsWifiAccessPoint.cpp82
-rw-r--r--netwerk/wifi/nsWifiAccessPoint.h79
-rw-r--r--netwerk/wifi/nsWifiMonitor.cpp232
-rw-r--r--netwerk/wifi/nsWifiMonitor.h75
-rw-r--r--netwerk/wifi/nsWifiScannerDBus.cpp374
-rw-r--r--netwerk/wifi/nsWifiScannerDBus.h44
-rw-r--r--netwerk/wifi/nsWifiScannerFreeBSD.cpp168
-rw-r--r--netwerk/wifi/nsWifiScannerMac.cpp51
-rw-r--r--netwerk/wifi/nsWifiScannerSolaris.cpp142
-rw-r--r--netwerk/wifi/nsWifiScannerWin.cpp64
-rw-r--r--netwerk/wifi/osx_corewlan.mm105
-rw-r--r--netwerk/wifi/osx_wifi.h119
-rw-r--r--netwerk/wifi/win_wifiScanner.cpp168
-rw-r--r--netwerk/wifi/win_wifiScanner.h32
-rw-r--r--netwerk/wifi/win_wlanLibrary.cpp122
-rw-r--r--netwerk/wifi/win_wlanLibrary.h54
1993 files changed, 561460 insertions, 0 deletions
diff --git a/netwerk/base/ARefBase.h b/netwerk/base/ARefBase.h
new file mode 100644
index 0000000000..cdb3a32e6a
--- /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 "nscore.h"
+
+namespace mozilla {
+namespace net {
+
+// This is an abstract class that can be pointed to by either
+// nsCOMPtr or nsRefPtr. nsHttpConnectionMgr uses it for generic
+// objects that need to be reference counted - similiar to nsISupports
+// but it may or may not be xpcom.
+
+class ARefBase {
+ public:
+ ARefBase() = 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..d5102051bc
--- /dev/null
+++ b/netwerk/base/ArrayBufferInputStream.cpp
@@ -0,0 +1,125 @@
+/* -*- 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/UniquePtrExtensions.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+using mozilla::dom::RootingCx;
+
+NS_IMPL_ISUPPORTS(ArrayBufferInputStream, nsIArrayBufferInputStream,
+ nsIInputStream);
+
+ArrayBufferInputStream::ArrayBufferInputStream()
+ : mBufferLength(0), mPos(0), mClosed(false) {}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::SetData(JS::Handle<JS::Value> aBuffer,
+ uint32_t aByteOffset, uint32_t aLength) {
+ 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;
+ }
+
+ uint32_t buflen = JS::GetArrayBufferByteLength(arrayBuffer);
+ uint32_t offset = std::min(buflen, aByteOffset);
+ uint32_t bufferLength = std::min(buflen - offset, aLength);
+
+ mArrayBuffer = mozilla::MakeUniqueFallible<char[]>(bufferLength);
+ if (!mArrayBuffer) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mBufferLength = bufferLength;
+
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ char* src =
+ (char*)JS::GetArrayBufferData(arrayBuffer, &isShared, nogc) + offset;
+ memcpy(&mArrayBuffer[0], src, mBufferLength);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Close() {
+ mClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Available(uint64_t* aCount) {
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+ if (mArrayBuffer) {
+ *aCount = mBufferLength ? mBufferLength - mPos : 0;
+ } else {
+ *aCount = 0;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount,
+ uint32_t* aReadCount) {
+ return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount);
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t aCount, uint32_t* result) {
+ NS_ASSERTION(result, "null ptr");
+ NS_ASSERTION(mBufferLength >= mPos, "bad stream state");
+
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ MOZ_ASSERT(mArrayBuffer || (mPos == mBufferLength),
+ "stream inited incorrectly");
+
+ *result = 0;
+ while (mPos < mBufferLength) {
+ uint32_t remaining = mBufferLength - mPos;
+ MOZ_ASSERT(mArrayBuffer);
+
+ uint32_t count = std::min(aCount, remaining);
+ if (count == 0) {
+ break;
+ }
+
+ uint32_t written;
+ nsresult rv = writer(this, closure, &mArrayBuffer[0] + mPos, *result, count,
+ &written);
+ if (NS_FAILED(rv)) {
+ // InputStreams do not propagate errors to caller.
+ return NS_OK;
+ }
+
+ NS_ASSERTION(written <= count,
+ "writer should not write more than we asked it to write");
+ mPos += written;
+ *result += written;
+ aCount -= written;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = true;
+ return NS_OK;
+}
diff --git a/netwerk/base/ArrayBufferInputStream.h b/netwerk/base/ArrayBufferInputStream.h
new file mode 100644
index 0000000000..e38a096764
--- /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();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIARRAYBUFFERINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAM
+
+ private:
+ virtual ~ArrayBufferInputStream() = default;
+ mozilla::UniquePtr<char[]> mArrayBuffer;
+ uint32_t mBufferLength;
+ uint32_t mPos;
+ bool mClosed;
+};
+
+#endif // ArrayBufferInputStream_h
diff --git a/netwerk/base/AutoClose.h b/netwerk/base/AutoClose.h
new file mode 100644
index 0000000000..632b29ed7b
--- /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;
+};
+
+} // 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..7c77038f08
--- /dev/null
+++ b/netwerk/base/BackgroundFileSaver.cpp
@@ -0,0 +1,1120 @@
+/* -*- 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/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()
+ : mControlEventTarget(nullptr),
+ mBackgroundET(nullptr),
+ mPipeOutputStream(nullptr),
+ mPipeInputStream(nullptr),
+ mObserver(nullptr),
+ mLock("BackgroundFileSaver.mLock"),
+ mWorkerThreadAttentionRequested(false),
+ mFinishRequested(false),
+ mComplete(false),
+ mStatus(NS_OK),
+ mAppend(false),
+ mInitialTarget(nullptr),
+ mInitialTargetKeepPartial(false),
+ mRenamedTarget(nullptr),
+ mRenamedTargetKeepPartial(false),
+ mAsyncCopyContext(nullptr),
+ mSha256Enabled(false),
+ mSignatureInfoEnabled(false),
+ mActualTarget(nullptr),
+ mActualTargetKeepPartial(false) {
+ 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");
+
+ nsresult rv;
+
+ rv = NS_NewPipe2(getter_AddRefs(mPipeInputStream),
+ getter_AddRefs(mPipeOutputStream), true, true, 0,
+ HasInfiniteBuffer() ? UINT32_MAX : 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mControlEventTarget = GetCurrentEventTarget();
+ NS_ENSURE_TRUE(mControlEventTarget, NS_ERROR_NOT_INITIALIZED);
+
+ 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 = mObserver;
+ NS_IF_ADDREF(*aObserver);
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::SetObserver(nsIBackgroundFileSaverObserver* aObserver) {
+ mObserver = aObserver;
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::EnableAppend() {
+ MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
+
+ MutexAutoLock lock(mLock);
+ mAppend = true;
+
+ return NS_OK;
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::SetTarget(nsIFile* aTarget, bool aKeepPartial) {
+ NS_ENSURE_ARG(aTarget);
+ {
+ MutexAutoLock lock(mLock);
+ if (!mInitialTarget) {
+ aTarget->Clone(getter_AddRefs(mInitialTarget));
+ mInitialTargetKeepPartial = aKeepPartial;
+ } else {
+ aTarget->Clone(getter_AddRefs(mRenamedTarget));
+ mRenamedTargetKeepPartial = aKeepPartial;
+ }
+ }
+
+ // After the worker thread wakes up because attention is requested, it will
+ // rename or create the target file as requested, and start copying data.
+ return GetWorkerThreadAttention(true);
+}
+
+// Called on the control thread.
+NS_IMETHODIMP
+BackgroundFileSaver::Finish(nsresult aStatus) {
+ nsresult rv;
+
+ // This will cause the NS_AsyncCopy operation, if it's in progress, to consume
+ // all the data that is still in the pipe, and then finish.
+ rv = mPipeOutputStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensure that, when we get attention from the worker thread, if no pending
+ // rename operation is waiting, the operation will complete.
+ {
+ MutexAutoLock lock(mLock);
+ mFinishRequested = true;
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+ }
+
+ // After the worker thread wakes up because attention is requested, it will
+ // process the completion conditions, detect that completion is requested, and
+ // notify the main thread of the completion. If this function was called with
+ // a success code, we wait for the copy to finish before processing the
+ // completion conditions, otherwise we interrupt the copy immediately.
+ return GetWorkerThreadAttention(NS_FAILED(aStatus));
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::EnableSha256() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can't enable sha256 or initialize NSS off the main thread");
+ // Ensure Personal Security Manager is initialized. This is required for
+ // PK11_* operations to work.
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSha256Enabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::GetSha256Hash(nsACString& aHash) {
+ MOZ_ASSERT(NS_IsMainThread(), "Can't inspect sha256 off the main thread");
+ // We acquire a lock because mSha256 is written on the worker thread.
+ MutexAutoLock lock(mLock);
+ if (mSha256.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ aHash = mSha256;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::EnableSignatureInfo() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Can't enable signature extraction off the main thread");
+ // Ensure Personal Security Manager is initialized.
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSignatureInfoEnabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaver::GetSignatureInfo(
+ 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.
+ 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);
+
+ 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;
+ }
+
+ nsresult rv =
+ mDigest->Update(BitwiseCast<unsigned char*, char*>(buffer), count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = inputStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // We will append to the initial target file only if it was requested by the
+ // caller, but we'll always append on subsequent accesses to the target file.
+ int32_t creationIoFlags;
+ if (isContinuation) {
+ creationIoFlags = PR_APPEND;
+ } else {
+ creationIoFlags = (append ? PR_APPEND : PR_TRUNCATE) | PR_CREATE_FILE;
+ }
+
+ // Create the target file, or append to it if we already started writing it.
+ // The 0600 permissions are used while the file is being downloaded, and for
+ // interrupted downloads. Those may be located in the system temporary
+ // directory, as well as the target directory, and generally have a ".part"
+ // extension. Those part files should never be group or world-writable even
+ // if the umask allows it.
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mActualTarget,
+ PR_WRONLY | creationIoFlags, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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;
+
+ MOZ_ASSERT(!mAsyncCopyContext,
+ "Should not be copying when checking completion conditions.");
+
+ bool failed = true;
+ {
+ MutexAutoLock lock(mLock);
+
+ if (mComplete) {
+ return true;
+ }
+
+ // If an error occurred, we don't need to do the checks in this code block,
+ // and the operation can be completed immediately with a failure code.
+ if (NS_SUCCEEDED(mStatus)) {
+ failed = false;
+
+ // We did not incur in an error, so we must determine if we can stop now.
+ // If the Finish method has not been called, we can just continue now.
+ if (!mFinishRequested) {
+ return false;
+ }
+
+ // We can only stop when all the operations requested by the control
+ // thread have been processed. First, we check whether we have processed
+ // the first SetTarget call, if any. Then, we check whether we have
+ // processed any rename requested by subsequent SetTarget calls.
+ if ((mInitialTarget && !mActualTarget) ||
+ (mRenamedTarget && mRenamedTarget != mActualTarget)) {
+ return false;
+ }
+
+ // If we still have data to write to the output file, allow the copy
+ // operation to resume. The Available getter may return an error if one
+ // of the pipe's streams has been already closed.
+ uint64_t available;
+ rv = mPipeInputStream->Available(&available);
+ if (NS_SUCCEEDED(rv) && available != 0) {
+ return false;
+ }
+ }
+
+ mComplete = true;
+ }
+
+ // Ensure we notify completion now that the operation finished.
+ // Do a best-effort attempt to remove the file if required.
+ if (failed && mActualTarget && !mActualTargetKeepPartial) {
+ (void)mActualTarget->Remove(false);
+ }
+
+ // Finish computing the hash
+ if (!failed && 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()
+ : BackgroundFileSaver(), mAsyncWaitCallback(nullptr) {}
+
+bool BackgroundFileSaverOutputStream::HasInfiniteBuffer() { return false; }
+
+nsAsyncCopyProgressFun BackgroundFileSaverOutputStream::GetProgressCallback() {
+ return nullptr;
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Close() { return mPipeOutputStream->Close(); }
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Flush() { return mPipeOutputStream->Flush(); }
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::Write(const char* aBuf, uint32_t aCount,
+ uint32_t* _retval) {
+ return mPipeOutputStream->Write(aBuf, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::WriteFrom(nsIInputStream* aFromStream,
+ uint32_t aCount, uint32_t* _retval) {
+ return mPipeOutputStream->WriteFrom(aFromStream, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::WriteSegments(nsReadSegmentFun aReader,
+ void* aClosure, uint32_t aCount,
+ uint32_t* _retval) {
+ return mPipeOutputStream->WriteSegments(aReader, aClosure, aCount, _retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::IsNonBlocking(bool* _retval) {
+ return mPipeOutputStream->IsNonBlocking(_retval);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::CloseWithStatus(nsresult reason) {
+ return mPipeOutputStream->CloseWithStatus(reason);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::AsyncWait(nsIOutputStreamCallback* aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ NS_ENSURE_STATE(!mAsyncWaitCallback);
+
+ mAsyncWaitCallback = aCallback;
+
+ return mPipeOutputStream->AsyncWait(this, aFlags, aRequestedCount,
+ aEventTarget);
+}
+
+NS_IMETHODIMP
+BackgroundFileSaverOutputStream::OnOutputStreamReady(
+ nsIAsyncOutputStream* aStream) {
+ NS_ENSURE_STATE(mAsyncWaitCallback);
+
+ nsCOMPtr<nsIOutputStreamCallback> asyncWaitCallback = nullptr;
+ asyncWaitCallback.swap(mAsyncWaitCallback);
+
+ return asyncWaitCallback->OnOutputStreamReady(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// BackgroundFileSaverStreamListener
+
+NS_IMPL_ISUPPORTS(BackgroundFileSaverStreamListener, nsIBackgroundFileSaver,
+ nsIRequestObserver, nsIStreamListener)
+
+BackgroundFileSaverStreamListener::BackgroundFileSaverStreamListener()
+ : BackgroundFileSaver(),
+ mSuspensionLock("BackgroundFileSaverStreamListener.mSuspensionLock"),
+ mReceivedTooMuchData(false),
+ mRequest(nullptr),
+ mRequestSuspended(false) {}
+
+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::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..0ac247786f
--- /dev/null
+++ b/netwerk/base/BackgroundFileSaver.h
@@ -0,0 +1,396 @@
+/* -*- 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;
+
+ /**
+ * True if the worker thread is already waiting to process a change in state.
+ */
+ bool mWorkerThreadAttentionRequested;
+
+ /**
+ * True if the operation should finish as soon as possibile.
+ */
+ bool mFinishRequested;
+
+ /**
+ * True if the operation completed, with either success or failure.
+ */
+ bool mComplete;
+
+ /**
+ * Holds the current file saver status. This is a success status while the
+ * object is working correctly, and remains such if the operation completes
+ * successfully. This becomes an error status when an error occurs on the
+ * worker thread, or when the operation is canceled.
+ */
+ nsresult mStatus;
+
+ /**
+ * True if we should append data to the initial target file, instead of
+ * overwriting it.
+ */
+ bool mAppend;
+
+ /**
+ * This is set by the first SetTarget call on the control thread, and contains
+ * the target file name that will be used by the worker thread, as soon as it
+ * is possible to update mActualTarget and open the file. This is null if no
+ * target was ever assigned to this object.
+ */
+ nsCOMPtr<nsIFile> mInitialTarget;
+
+ /**
+ * This is set by the first SetTarget call on the control thread, and
+ * indicates whether mInitialTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mInitialTargetKeepPartial;
+
+ /**
+ * This is set by subsequent SetTarget calls on the control thread, and
+ * contains the new target file name to which the worker thread will move the
+ * target file, as soon as it can be done. This is null if SetTarget was
+ * called only once, or no target was ever assigned to this object.
+ *
+ * The target file can be renamed multiple times, though only the most recent
+ * rename is guaranteed to be processed by the worker thread.
+ */
+ nsCOMPtr<nsIFile> mRenamedTarget;
+
+ /**
+ * This is set by subsequent SetTarget calls on the control thread, and
+ * indicates whether mRenamedTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mRenamedTargetKeepPartial;
+
+ /**
+ * While NS_AsyncCopy is in progress, allows canceling it. Null otherwise.
+ * This is read by both threads but only written by the worker thread.
+ */
+ nsCOMPtr<nsISupports> mAsyncCopyContext;
+
+ /**
+ * The SHA 256 hash in raw bytes of the downloaded file. This is written
+ * by the worker thread but can be read on the main thread.
+ */
+ nsCString mSha256;
+
+ /**
+ * Whether or not to compute the hash. Must be set on the main thread before
+ * setTarget is called.
+ */
+ bool mSha256Enabled;
+
+ /**
+ * Store the signature info.
+ */
+ nsTArray<nsTArray<nsTArray<uint8_t>>> mSignatureInfo;
+
+ /**
+ * Whether or not to extract the signature. Must be set on the main thread
+ * before setTarget is called.
+ */
+ bool mSignatureInfoEnabled;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// State handled exclusively by the worker thread
+
+ /**
+ * Current target file associated to the input and output streams.
+ */
+ nsCOMPtr<nsIFile> mActualTarget;
+
+ /**
+ * Indicates whether mActualTarget should be kept as partially completed,
+ * rather than deleted, if the operation fails or is canceled.
+ */
+ bool mActualTargetKeepPartial;
+
+ /**
+ * Used to calculate the file hash. This keeps state across file renames and
+ * is lazily initialized in ProcessStateChange.
+ */
+ 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.jsm, and possibly others.
+
+class BackgroundFileSaverStreamListener final : public BackgroundFileSaver,
+ public nsIStreamListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ BackgroundFileSaverStreamListener();
+
+ protected:
+ virtual bool HasInfiniteBuffer() override;
+ virtual nsAsyncCopyProgressFun GetProgressCallback() override;
+
+ private:
+ ~BackgroundFileSaverStreamListener() = default;
+
+ /**
+ * Protects the state related to whether the request should be suspended.
+ */
+ mozilla::Mutex mSuspensionLock;
+
+ /**
+ * Whether we should suspend the request because we received too much data.
+ */
+ bool mReceivedTooMuchData;
+
+ /**
+ * Request for which we received too much data. This is populated when
+ * mReceivedTooMuchData becomes true for the first time.
+ */
+ nsCOMPtr<nsIRequest> mRequest;
+
+ /**
+ * Whether mRequest is currently suspended.
+ */
+ bool mRequestSuspended;
+
+ /**
+ * Called while NS_AsyncCopy is copying data.
+ */
+ static void AsyncCopyProgressCallback(void* aClosure, uint32_t aCount);
+
+ /**
+ * Called on the control thread to suspend or resume the request.
+ */
+ nsresult NotifySuspendOrResume();
+};
+
+// A wrapper around nsIOutputStream, so that we can compute hashes on the
+// stream without copying and without polluting pristine NSS code with XPCOM
+// interfaces.
+class DigestOutputStream : public 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/CaptivePortalService.cpp b/netwerk/base/CaptivePortalService.cpp
new file mode 100644
index 0000000000..6090a72a13
--- /dev/null
+++ b/netwerk/base/CaptivePortalService.cpp
@@ -0,0 +1,396 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/ClearOnShutdown.h"
+#include "mozilla/Services.h"
+#include "mozilla/Preferences.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsXULAppAPI.h"
+#include "xpcpublic.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";
+
+static const uint32_t kDefaultInterval = 60 * 1000; // check every 60 seconds
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gCaptivePortalLog("CaptivePortalService");
+#undef LOG
+#define LOG(args) MOZ_LOG(gCaptivePortalLog, mozilla::LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(CaptivePortalService, nsICaptivePortalService, nsIObserver,
+ nsISupportsWeakReference, nsITimerCallback,
+ nsICaptivePortalCallback, 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()
+ : mState(UNKNOWN),
+ mStarted(false),
+ mInitialized(false),
+ mRequestInProgress(false),
+ mEverBeenCaptive(false),
+ mDelay(kDefaultInterval),
+ mSlackCount(0),
+ mMinInterval(kDefaultInterval),
+ mMaxInterval(25 * kDefaultInterval),
+ mBackoffFactor(5.0) {
+ mLastChecked = TimeStamp::Now();
+}
+
+CaptivePortalService::~CaptivePortalService() {
+ LOG(("CaptivePortalService::~CaptivePortalService isParentProcess:%d\n",
+ XRE_GetProcessType() == GeckoProcessType_Default));
+}
+
+nsresult CaptivePortalService::PerformCheck() {
+ LOG(
+ ("CaptivePortalService::PerformCheck mRequestInProgress:%d "
+ "mInitialized:%d mStarted:%d\n",
+ mRequestInProgress, mInitialized, mStarted));
+ // Don't issue another request if last one didn't complete
+ if (mRequestInProgress || !mInitialized || !mStarted) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ nsresult rv;
+ if (!mCaptivePortalDetector) {
+ mCaptivePortalDetector =
+ do_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 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);
+ }
+
+ 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;
+ }
+
+ 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.
+ mState = LOCKED_PORTAL;
+ mLastChecked = TimeStamp::Now();
+ mEverBeenCaptive = true;
+ } else if (!strcmp(aTopic, kCaptivePortalLoginSuccessEvent)) {
+ // The user has successfully logged in. We have connectivity.
+ mState = UNLOCKED_PORTAL;
+ mLastChecked = TimeStamp::Now();
+ mSlackCount = 0;
+ mDelay = mMinInterval;
+
+ RearmTimer();
+ } else if (!strcmp(aTopic, kAbortCaptivePortalLoginEvent)) {
+ // The login has been aborted
+ mState = UNKNOWN;
+ mLastChecked = TimeStamp::Now();
+ mSlackCount = 0;
+ }
+
+ // Send notification so that the captive portal state is mirrored in the
+ // content process.
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ nsCOMPtr<nsICaptivePortalService> cps(this);
+ observerService->NotifyObservers(cps, NS_IPC_CAPTIVE_PORTAL_SET_STATE,
+ nullptr);
+ }
+
+ return NS_OK;
+}
+
+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);
+ // XXX: Finish preparation shouldn't be called until dns and routing is
+ // available.
+ if (mCaptivePortalDetector) {
+ mCaptivePortalDetector->FinishPreparation(kInterfaceName);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CaptivePortalService::Complete(bool success) {
+ LOG(("CaptivePortalService::Complete(success=%d) mState=%d\n", success,
+ mState));
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+ mLastChecked = TimeStamp::Now();
+
+ // Note: this callback gets called when:
+ // 1. the request is completed, and content is valid (success == true)
+ // 2. when the request is aborted or times out (success == false)
+
+ if (success) {
+ if (mEverBeenCaptive) {
+ mState = UNLOCKED_PORTAL;
+ NotifyConnectivityAvailable(true);
+ } else {
+ mState = NOT_CAPTIVE;
+ NotifyConnectivityAvailable(false);
+ }
+ }
+
+ mRequestInProgress = false;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/CaptivePortalService.h b/netwerk/base/CaptivePortalService.h
new file mode 100644
index 0000000000..9edd0752df
--- /dev/null
+++ b/netwerk/base/CaptivePortalService.h
@@ -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/. */
+
+#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:
+ CaptivePortalService();
+ virtual ~CaptivePortalService();
+ nsresult PerformCheck();
+ nsresult RearmTimer();
+ void NotifyConnectivityAvailable(bool aCaptive);
+
+ nsCOMPtr<nsICaptivePortalDetector> mCaptivePortalDetector;
+ int32_t mState;
+
+ nsCOMPtr<nsITimer> mTimer;
+ bool mStarted;
+ bool mInitialized;
+ bool mRequestInProgress;
+ bool mEverBeenCaptive;
+
+ uint32_t mDelay;
+ int32_t mSlackCount;
+
+ uint32_t mMinInterval;
+ uint32_t mMaxInterval;
+ float mBackoffFactor;
+
+ // This holds a timestamp when the last time when the captive portal check
+ // has changed state.
+ mozilla::TimeStamp mLastChecked;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // CaptivePortalService_h_
diff --git a/netwerk/base/Dashboard.cpp b/netwerk/base/Dashboard.cpp
new file mode 100644
index 0000000000..e775fbde1c
--- /dev/null
+++ b/netwerk/base/Dashboard.cpp
@@ -0,0 +1,1186 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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() {
+ mTotalSent = 0;
+ mTotalRecv = 0;
+ mEventTarget = nullptr;
+ }
+
+ uint64_t mTotalSent;
+ uint64_t mTotalRecv;
+ nsTArray<SocketInfo> mData;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget;
+
+ 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() { mEventTarget = nullptr; }
+
+ nsTArray<HttpRetParams> mData;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget;
+};
+
+NS_IMPL_ISUPPORTS0(HttpData)
+
+class WebSocketRequest : public nsISupports {
+ virtual ~WebSocketRequest() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ WebSocketRequest() { mEventTarget = nullptr; }
+
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget;
+};
+
+NS_IMPL_ISUPPORTS0(WebSocketRequest)
+
+class DnsData : public nsISupports {
+ virtual ~DnsData() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ DnsData() { mEventTarget = nullptr; }
+
+ nsTArray<DNSCacheEntries> mData;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget;
+};
+
+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) : mPort(0), mTimeout(0) {
+ mEventTarget = nullptr;
+ mDashboard = target;
+ }
+
+ nsCOMPtr<nsISocketTransport> mSocket;
+ nsCOMPtr<nsIInputStream> mStreamIn;
+ nsCOMPtr<nsITimer> mTimer;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget;
+ Dashboard* mDashboard;
+
+ nsCString mHost;
+ uint32_t mPort;
+ nsCString mProtocol;
+ uint32_t mTimeout;
+
+ nsString mStatus;
+};
+
+NS_IMPL_ISUPPORTS(ConnectionData, nsITransportEventSink, nsITimerCallback,
+ nsINamed)
+
+class RcwnData : public nsISupports {
+ virtual ~RcwnData() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ RcwnData() { mEventTarget = nullptr; }
+
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget;
+};
+
+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() : mEventTarget{nullptr}, mStatus{NS_ERROR_NOT_INITIALIZED} {}
+
+ nsresult ConstructAnswer(LookupArgument* aArgument);
+ nsresult ConstructHTTPSRRAnswer(LookupArgument* aArgument);
+
+ public:
+ nsCOMPtr<nsICancelable> mCancel;
+ nsMainThreadPtrHandle<nsINetDashboardCallback> mCallback;
+ nsIEventTarget* mEventTarget;
+ nsresult mStatus;
+};
+
+NS_IMPL_ISUPPORTS(LookupHelper, nsIDNSListener)
+
+NS_IMETHODIMP
+LookupHelper::OnLookupComplete(nsICancelable* aRequest, nsIDNSRecord* aRecord,
+ nsresult aStatus) {
+ MOZ_ASSERT(aRequest == mCancel);
+ mCancel = nullptr;
+ mStatus = aStatus;
+
+ 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::RootedValue 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::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ this->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(Dashboard, nsIDashboard, nsIDashboardEventNotifier)
+
+Dashboard::Dashboard() { mEnableLogging = false; }
+
+NS_IMETHODIMP
+Dashboard::RequestSockets(nsINetDashboardCallback* aCallback) {
+ RefPtr<SocketData> socketData = new SocketData();
+ socketData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+ socketData->mEventTarget = GetCurrentEventTarget();
+
+ 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;
+ mSocket.mTcp = socketData->mData[i].tcp;
+ mSocket.mSent = (double)socketData->mData[i].sent;
+ mSocket.mReceived = (double)socketData->mData[i].received;
+ dict.mSent += socketData->mData[i].sent;
+ dict.mReceived += socketData->mData[i].received;
+ }
+
+ dict.mSent += socketData->mTotalSent;
+ dict.mReceived += socketData->mTotalRecv;
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val)) return NS_ERROR_FAILURE;
+ socketData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestHttpConnections(nsINetDashboardCallback* aCallback) {
+ RefPtr<HttpData> httpData = new HttpData();
+ httpData->mCallback = new nsMainThreadPtrHolder<nsINetDashboardCallback>(
+ "nsINetDashboardCallback", aCallback, true);
+ httpData->mEventTarget = GetCurrentEventTarget();
+
+ 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::HalfOpenInfoDict;
+ using mozilla::dom::HttpConnectionElement;
+ using mozilla::dom::HttpConnInfo;
+ Sequence<HttpConnectionElement>& connections = dict.mConnections.Value();
+
+ uint32_t length = httpData->mData.Length();
+ if (!connections.SetCapacity(length, fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < httpData->mData.Length(); i++) {
+ HttpConnectionElement& connection = *connections.AppendElement(fallible);
+
+ CopyASCIItoUTF16(httpData->mData[i].host, connection.mHost);
+ connection.mPort = httpData->mData[i].port;
+ CopyASCIItoUTF16(httpData->mData[i].httpVersion, connection.mHttpVersion);
+ connection.mSsl = httpData->mData[i].ssl;
+
+ connection.mActive.Construct();
+ connection.mIdle.Construct();
+ connection.mHalfOpens.Construct();
+
+ Sequence<HttpConnInfo>& active = connection.mActive.Value();
+ Sequence<HttpConnInfo>& idle = connection.mIdle.Value();
+ Sequence<HalfOpenInfoDict>& halfOpens = connection.mHalfOpens.Value();
+
+ if (!active.SetCapacity(httpData->mData[i].active.Length(), fallible) ||
+ !idle.SetCapacity(httpData->mData[i].idle.Length(), fallible) ||
+ !halfOpens.SetCapacity(httpData->mData[i].halfOpens.Length(),
+ fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].active.Length(); j++) {
+ HttpConnInfo& info = *active.AppendElement(fallible);
+ info.mRtt = httpData->mData[i].active[j].rtt;
+ info.mTtl = httpData->mData[i].active[j].ttl;
+ info.mProtocolVersion = httpData->mData[i].active[j].protocolVersion;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].idle.Length(); j++) {
+ HttpConnInfo& info = *idle.AppendElement(fallible);
+ info.mRtt = httpData->mData[i].idle[j].rtt;
+ info.mTtl = httpData->mData[i].idle[j].ttl;
+ info.mProtocolVersion = httpData->mData[i].idle[j].protocolVersion;
+ }
+
+ for (uint32_t j = 0; j < httpData->mData[i].halfOpens.Length(); j++) {
+ HalfOpenInfoDict& info = *halfOpens.AppendElement(fallible);
+ info.mSpeculative = httpData->mData[i].halfOpens[j].speculative;
+ }
+ }
+
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ httpData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::GetEnableLogging(bool* value) {
+ *value = mEnableLogging;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::SetEnableLogging(const bool value) {
+ mEnableLogging = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::AddHost(const nsACString& aHost, uint32_t aSerial, bool aEncrypted) {
+ if (mEnableLogging) {
+ mozilla::MutexAutoLock lock(mWs.lock);
+ LogData mData(nsCString(aHost), aSerial, aEncrypted);
+ if (mWs.data.Contains(mData)) {
+ return NS_OK;
+ }
+ // 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 = GetCurrentEventTarget();
+
+ 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::RootedValue 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 = GetCurrentEventTarget();
+
+ 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);
+ }
+
+ JS::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val)) {
+ return NS_ERROR_FAILURE;
+ }
+ dnsData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Dashboard::RequestDNSLookup(const nsACString& aHost,
+ 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 = GetCurrentEventTarget();
+ OriginAttributes attrs;
+ rv = mDnsService->AsyncResolveNative(
+ aHost, nsIDNSService::RESOLVE_TYPE_DEFAULT, 0, 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 = GetCurrentEventTarget();
+ OriginAttributes attrs;
+ rv = mDnsService->AsyncResolveNative(
+ aHost, nsIDNSService::RESOLVE_TYPE_HTTPSSVC, 0, 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 = GetCurrentEventTarget();
+ 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::RootedValue 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 = GetCurrentEventTarget();
+
+ 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::RootedValue val(cx);
+ if (!ToJSValue(cx, dict, &val)) return NS_ERROR_FAILURE;
+
+ connectionData->mCallback->OnDashboardDataAvailable(val);
+
+ return NS_OK;
+}
+
+nsresult Dashboard::TestNewConnection(ConnectionData* aConnectionData) {
+ RefPtr<ConnectionData> connectionData = aConnectionData;
+
+ nsresult rv;
+ if (!connectionData->mHost.Length() ||
+ !net_IsValidHostName(connectionData->mHost)) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (connectionData->mProtocol.EqualsLiteral("ssl")) {
+ AutoTArray<nsCString, 1> socketTypes = {connectionData->mProtocol};
+ rv = gSocketTransportService->CreateTransport(
+ socketTypes, connectionData->mHost, connectionData->mPort, nullptr,
+ getter_AddRefs(connectionData->mSocket));
+ } else {
+ rv = gSocketTransportService->CreateTransport(
+ nsTArray<nsCString>(), connectionData->mHost, connectionData->mPort,
+ nullptr, getter_AddRefs(connectionData->mSocket));
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = connectionData->mSocket->SetEventSink(connectionData,
+ GetCurrentEventTarget());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = connectionData->mSocket->OpenInputStream(
+ nsITransport::OPEN_BLOCKING, 0, 0,
+ getter_AddRefs(connectionData->mStreamIn));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ connectionData->StartTimer(connectionData->mTimeout);
+
+ return rv;
+}
+
+typedef struct {
+ nsresult key;
+ const char* error;
+} ErrorEntry;
+
+#undef ERROR
+#define ERROR(key, val) \
+ { key, #key }
+
+ErrorEntry socketTransportStatuses[] = {
+ ERROR(NS_NET_STATUS_RESOLVING_HOST, FAILURE(3)),
+ ERROR(NS_NET_STATUS_RESOLVED_HOST, FAILURE(11)),
+ ERROR(NS_NET_STATUS_CONNECTING_TO, FAILURE(7)),
+ ERROR(NS_NET_STATUS_CONNECTED_TO, FAILURE(4)),
+ ERROR(NS_NET_STATUS_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..4179397b0f
--- /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;
+ };
+
+ 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..80fdd68d9c
--- /dev/null
+++ b/netwerk/base/DashboardTypes.h
@@ -0,0 +1,179 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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;
+ bool tcp;
+};
+
+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.tcp == b.tcp;
+}
+
+struct HalfOpenSockets {
+ bool speculative;
+};
+
+struct DNSCacheEntries {
+ nsCString hostname;
+ nsTArray<nsCString> hostaddr;
+ uint16_t family;
+ int64_t expiration;
+ nsCString netInterface;
+ bool TRR;
+ nsCString originAttributesSuffix;
+};
+
+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<HalfOpenSockets> halfOpens;
+ 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(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.host);
+ WriteParam(aMsg, aParam.sent);
+ WriteParam(aMsg, aParam.received);
+ WriteParam(aMsg, aParam.port);
+ WriteParam(aMsg, aParam.active);
+ WriteParam(aMsg, aParam.tcp);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->host) &&
+ ReadParam(aMsg, aIter, &aResult->sent) &&
+ ReadParam(aMsg, aIter, &aResult->received) &&
+ ReadParam(aMsg, aIter, &aResult->port) &&
+ ReadParam(aMsg, aIter, &aResult->active) &&
+ ReadParam(aMsg, aIter, &aResult->tcp);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::DNSCacheEntries> {
+ typedef mozilla::net::DNSCacheEntries paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.hostname);
+ WriteParam(aMsg, aParam.hostaddr);
+ WriteParam(aMsg, aParam.family);
+ WriteParam(aMsg, aParam.expiration);
+ WriteParam(aMsg, aParam.netInterface);
+ WriteParam(aMsg, aParam.TRR);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->hostname) &&
+ ReadParam(aMsg, aIter, &aResult->hostaddr) &&
+ ReadParam(aMsg, aIter, &aResult->family) &&
+ ReadParam(aMsg, aIter, &aResult->expiration) &&
+ ReadParam(aMsg, aIter, &aResult->netInterface) &&
+ ReadParam(aMsg, aIter, &aResult->TRR);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::HalfOpenSockets> {
+ typedef mozilla::net::HalfOpenSockets paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.speculative);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->speculative);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::HttpConnInfo> {
+ typedef mozilla::net::HttpConnInfo paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.ttl);
+ WriteParam(aMsg, aParam.rtt);
+ WriteParam(aMsg, aParam.protocolVersion);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->ttl) &&
+ ReadParam(aMsg, aIter, &aResult->rtt) &&
+ ReadParam(aMsg, aIter, &aResult->protocolVersion);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::HttpRetParams> {
+ typedef mozilla::net::HttpRetParams paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.host);
+ WriteParam(aMsg, aParam.active);
+ WriteParam(aMsg, aParam.idle);
+ WriteParam(aMsg, aParam.halfOpens);
+ WriteParam(aMsg, aParam.counter);
+ WriteParam(aMsg, aParam.port);
+ WriteParam(aMsg, aParam.httpVersion);
+ WriteParam(aMsg, aParam.ssl);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->host) &&
+ ReadParam(aMsg, aIter, &aResult->active) &&
+ ReadParam(aMsg, aIter, &aResult->idle) &&
+ ReadParam(aMsg, aIter, &aResult->halfOpens) &&
+ ReadParam(aMsg, aIter, &aResult->counter) &&
+ ReadParam(aMsg, aIter, &aResult->port) &&
+ ReadParam(aMsg, aIter, &aResult->httpVersion) &&
+ ReadParam(aMsg, aIter, &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..a76925a732
--- /dev/null
+++ b/netwerk/base/DefaultURI.cpp
@@ -0,0 +1,534 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+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) {
+ 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) {
+ *_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) {
+ 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::GetFilePath(nsACString& aFilePath) {
+ aFilePath = mURL->FilePath();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetQuery(nsACString& aQuery) {
+ aQuery = mURL->Query();
+ return NS_OK;
+}
+
+NS_IMETHODIMP DefaultURI::GetDisplayHost(nsACString& aDisplayHost) {
+ // At the moment it doesn't seem useful to decode the hostname if it happens
+ // to contain punycode.
+ return GetHost(aDisplayHost);
+}
+
+NS_IMETHODIMP DefaultURI::GetDisplayHostPort(nsACString& aDisplayHostPort) {
+ // At the moment it doesn't seem useful to decode the hostname if it happens
+ // to contain punycode.
+ return GetHostPort(aDisplayHostPort);
+}
+
+NS_IMETHODIMP DefaultURI::GetDisplaySpec(nsACString& aDisplaySpec) {
+ // At the moment it doesn't seem useful to decode the hostname if it happens
+ // to contain punycode.
+ return GetSpec(aDisplaySpec);
+}
+
+NS_IMETHODIMP DefaultURI::GetDisplayPrePath(nsACString& aDisplayPrePath) {
+ // At the moment it doesn't seem useful to decode the hostname if it happens
+ // to contain punycode.
+ return GetPrePath(aDisplayPrePath);
+}
+
+NS_IMETHODIMP DefaultURI::Mutate(nsIURIMutator** _retval) {
+ RefPtr<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();
+ }
+
+ nsAutoCString pathQueryRef(aPathQueryRef);
+ if (!StringBeginsWith(pathQueryRef, "/"_ns)) {
+ pathQueryRef.Insert('/', 0);
+ }
+
+ RefPtr<MozURL> url;
+ mMutator->Finalize(getter_AddRefs(url));
+ mMutator = Nothing();
+
+ auto result = MozURL::Mutator::FromSpec(pathQueryRef, url);
+ if (result.isErr()) {
+ return result.unwrapErr();
+ }
+ mMutator = Some(result.unwrap());
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetRef(const nsACString& aRef, nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetRef(aRef);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetFilePath(const nsACString& aFilePath,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetFilePath(aFilePath);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetQuery(const nsACString& aQuery,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mMutator->SetQuery(aQuery);
+ return mMutator->GetStatus();
+}
+
+NS_IMETHODIMP
+DefaultURI::Mutator::SetQueryWithEncoding(const nsACString& aQuery,
+ const mozilla::Encoding* aEncoding,
+ nsIURIMutator** aMutator) {
+ ASSIGN_AND_ADDREF_THIS(aMutator);
+ if (!mMutator.isSome()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ // we only support UTF-8 for DefaultURI
+ mMutator->SetQuery(aQuery);
+ return mMutator->GetStatus();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/DefaultURI.h b/netwerk/base/DefaultURI.h
new file mode 100644
index 0000000000..e737067565
--- /dev/null
+++ b/netwerk/base/DefaultURI.h
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DefaultURI_h__
+#define DefaultURI_h__
+
+#include "nsIURI.h"
+#include "nsISerializable.h"
+#include "nsISizeOf.h"
+#include "nsIURIMutator.h"
+#include "mozilla/net/MozURL.h"
+
+namespace mozilla {
+namespace net {
+
+class DefaultURI : public nsIURI, public nsISerializable, public nsISizeOf {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSISERIALIZABLE
+
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ class Mutator final : public nsIURIMutator, public nsISerializable {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURISETSPEC
+ NS_DECL_NSIURISETTERS
+ NS_DECL_NSIURIMUTATOR
+
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ MOZ_ASSERT_UNREACHABLE("nsIURIMutator.write() should never be called");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override;
+
+ explicit Mutator() = default;
+
+ private:
+ virtual ~Mutator() = default;
+ void Init(DefaultURI* aFrom) { mMutator = Some(aFrom->mURL->Mutate()); }
+
+ Maybe<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..af902801c3
--- /dev/null
+++ b/netwerk/base/EventTokenBucket.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 "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() events = %d\n",
+ this, mEvents.GetSize()));
+ mFineGrainResetTimerArmed = false;
+
+ // If we are currently processing events then wait for the queue to drain
+ // before trying to reset back to normal timers again
+ if (!mEvents.GetSize()) WantNormalTimers();
+}
+
+#endif
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/EventTokenBucket.h b/netwerk/base/EventTokenBucket.h
new file mode 100644
index 0000000000..cab8937400
--- /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* event);
+ void SetRate(uint32_t eventsPerSecond, uint32_t burstSize);
+
+ void DispatchEvents();
+ void UpdateTimer();
+ void UpdateCredits();
+
+ const static uint64_t kUsecPerSec = 1000000;
+ const static uint64_t kUsecPerMsec = 1000;
+ const static uint64_t kMaxHz = 10000;
+
+ uint64_t
+ mUnitCost; // usec of credit needed for 1 event (from eventsPerSecond)
+ uint64_t mMaxCredit; // usec mCredit limit (from busrtSize)
+ uint64_t mCredit; // usec of accumulated credit.
+
+ bool mPaused;
+ bool mStopped;
+ 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..6fb92a1917
--- /dev/null
+++ b/netwerk/base/FuzzyLayer.cpp
@@ -0,0 +1,340 @@
+/* -*- 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 "nsDataHashtable.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;
+} NetworkFuzzingBuffer;
+
+// This holds all connections we have currently open.
+static nsDataHashtable<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;
+
+ 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.Put(fd, buf);
+ fuzzingNoWaitRequired = false;
+
+ FUZZING_LOG(("[FuzzyConnect] Successfully opened connection: %p", fd));
+
+ gFuzzingConnClosed = false;
+
+ return PR_SUCCESS;
+}
+
+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 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));
+ 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.write = FuzzyWrite;
+ sFuzzyLayerMethods.recv = FuzzyRecv;
+ 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..b2bcf89201
--- /dev/null
+++ b/netwerk/base/FuzzySecurityInfo.cpp
@@ -0,0 +1,369 @@
+/* -*- 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 "mozilla/Logging.h"
+#include "mozilla/OriginAttributes.h"
+#include "nsThreadManager.h"
+
+namespace mozilla {
+namespace net {
+
+FuzzySecurityInfo::FuzzySecurityInfo() {}
+
+FuzzySecurityInfo::~FuzzySecurityInfo() {}
+
+NS_IMPL_ISUPPORTS(FuzzySecurityInfo, nsITransportSecurityInfo,
+ nsIInterfaceRequestor, nsISSLSocketControl)
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetErrorCode(int32_t* state) {
+ *state = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetSecurityState(uint32_t* state) {
+ *state = 0;
+ 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) {
+ NS_ENSURE_ARG_POINTER(aServerCert);
+ // This method is called by nsHttpChannel::ProcessSSLInformation()
+ // in order to display certain information in the console.
+ // Returning NULL is okay here and handled by the caller.
+ *aServerCert = NULL;
+ 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");
+ *aSecretKeyLength = 4096;
+ 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) {
+ NS_ENSURE_ARG_POINTER(aCertificateTransparencyStatus);
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetIsDomainMismatch(bool* aIsDomainMismatch) {
+ NS_ENSURE_ARG_POINTER(aIsDomainMismatch);
+ *aIsDomainMismatch = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetIsNotValidAtThisTime(bool* aIsNotValidAtThisTime) {
+ NS_ENSURE_ARG_POINTER(aIsNotValidAtThisTime);
+ *aIsNotValidAtThisTime = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetIsUntrusted(bool* aIsUntrusted) {
+ NS_ENSURE_ARG_POINTER(aIsUntrusted);
+ *aIsUntrusted = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetIsExtendedValidation(bool* aIsEV) {
+ NS_ENSURE_ARG_POINTER(aIsEV);
+ *aIsEV = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetIsDelegatedCredential(bool* aIsDelegCred) {
+ NS_ENSURE_ARG_POINTER(aIsDelegCred);
+ *aIsDelegCred = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetIsAcceptedEch(bool* aIsAcceptedEch) {
+ NS_ENSURE_ARG_POINTER(aIsAcceptedEch);
+ *aIsAcceptedEch = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetInterface(const nsIID& uuid, void** result) {
+ if (!NS_IsMainThread()) {
+ MOZ_CRASH("FuzzySecurityInfo::GetInterface called off the main thread");
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ nsresult rv = NS_ERROR_NO_INTERFACE;
+ if (mCallbacks) {
+ rv = mCallbacks->GetInterface(uuid, result);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aCallbacks) {
+ nsCOMPtr<nsIInterfaceRequestor> ir(mCallbacks);
+ ir.forget(aCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
+ mCallbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetProviderFlags(uint32_t* aProviderFlags) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetProviderTlsFlags(uint32_t* aProviderTlsFlags) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetKEAUsed(int16_t* aKea) {
+ // Can be ssl_kea_dh or ssl_kea_ecdh for HTTP2
+ *aKea = ssl_kea_ecdh;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetKEAKeyBits(uint32_t* aKeyBits) {
+ // Must be >= 224 for ecdh and >= 2048 for dh when using HTTP2
+ *aKeyBits = 256;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetSSLVersionUsed(int16_t* aSSLVersionUsed) {
+ // Must be >= TLS 1.2 for HTTP2
+ *aSSLVersionUsed = nsISSLSocketControl::TLS_VERSION_1_2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetSSLVersionOffered(int16_t* aSSLVersionOffered) {
+ *aSSLVersionOffered = nsISSLSocketControl::TLS_VERSION_1_2;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetMACAlgorithmUsed(int16_t* aMac) {
+ // The only valid choice for HTTP2 is SSL_MAC_AEAD
+ *aMac = nsISSLSocketControl::SSL_MAC_AEAD;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetClientCert(nsIX509Cert** aClientCert) {
+ NS_ENSURE_ARG_POINTER(aClientCert);
+ *aClientCert = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::SetClientCert(nsIX509Cert* aClientCert) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+bool FuzzySecurityInfo::GetDenyClientCert() { return false; }
+
+void FuzzySecurityInfo::SetDenyClientCert(bool aDenyClientCert) {
+ // Called by mozilla::net::nsHttpConnection::StartSpdy
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetClientCertSent(bool* arg) {
+ *arg = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetFailedVerification(bool* arg) {
+ *arg = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetNegotiatedNPN(nsACString& aNegotiatedNPN) {
+ aNegotiatedNPN = "h2";
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetAlpnEarlySelection(nsACString& aAlpnSelected) {
+ // TODO: For now we don't support early selection
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetEarlyDataAccepted(bool* aAccepted) {
+ *aAccepted = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetResumed(bool* aResumed) {
+ *aResumed = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::DriveHandshake() { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySecurityInfo::IsAcceptableForHost(const nsACString& hostname,
+ bool* _retval) {
+ NS_ENSURE_ARG(_retval);
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::TestJoinConnection(const nsACString& npnProtocol,
+ const nsACString& hostname, int32_t port,
+ bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::JoinConnection(const nsACString& npnProtocol,
+ const nsACString& hostname, int32_t port,
+ bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::ProxyStartSSL() { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySecurityInfo::StartTLS() { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySecurityInfo::SetNPNList(nsTArray<nsCString>& protocolArray) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetEsniTxt(nsACString& aEsniTxt) { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySecurityInfo::SetEsniTxt(const nsACString& aEsniTxt) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetEchConfig(nsACString& aEchConfig) { return NS_OK; }
+
+NS_IMETHODIMP
+FuzzySecurityInfo::SetEchConfig(const nsACString& aEchConfig) {
+ MOZ_CRASH("Unused");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetRetryEchConfig(nsACString& aEchConfig) { return NS_OK; }
+
+void FuzzySecurityInfo::SerializeToIPC(IPC::Message* aMsg) {
+ MOZ_CRASH("Unused");
+}
+
+bool FuzzySecurityInfo::DeserializeFromIPC(const IPC::Message* aMsg,
+ PickleIterator* aIter) {
+ MOZ_CRASH("Unused");
+ return false;
+}
+
+NS_IMETHODIMP
+FuzzySecurityInfo::GetPeerId(nsACString& aResult) {
+ aResult.Assign(""_ns);
+ return NS_OK;
+}
+
+NS_IMETHODIMP FuzzySecurityInfo::SetIsBuiltCertChainRootBuiltInRoot(
+ bool aIsBuiltInRoot) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP FuzzySecurityInfo::GetIsBuiltCertChainRootBuiltInRoot(
+ bool* aIsBuiltInRoot) {
+ *aIsBuiltInRoot = false;
+ 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..eda5260e88
--- /dev/null
+++ b/netwerk/base/FuzzySecurityInfo.h
@@ -0,0 +1,44 @@
+/* -*- 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 "prerror.h"
+#include "sslproto.h"
+#include "sslt.h"
+
+#include "nsCOMPtr.h"
+#include "nsISSLSocketControl.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsITransportSecurityInfo.h"
+
+namespace mozilla {
+namespace net {
+
+class FuzzySecurityInfo final : public nsITransportSecurityInfo,
+ public nsIInterfaceRequestor,
+ public nsISSLSocketControl {
+ public:
+ FuzzySecurityInfo();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORTSECURITYINFO
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSISSLSOCKETCONTROL
+
+ protected:
+ virtual ~FuzzySecurityInfo();
+
+ private:
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+
+}; // class FuzzySecurityInfo
+
+} // namespace net
+} // namespace mozilla
+
+#endif // FuzzySecurityInfo_h__
diff --git a/netwerk/base/IOActivityMonitor.cpp b/netwerk/base/IOActivityMonitor.cpp
new file mode 100644
index 0000000000..eefc623217
--- /dev/null
+++ b/netwerk/base/IOActivityMonitor.cpp
@@ -0,0 +1,475 @@
+/* -*- 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/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 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.AppendPrintf("fd://%d", 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(gInstance);
+ if (!IsActive()) {
+ 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);
+}
+
+// static
+NS_IMETHODIMP
+IOActivityMonitor::GetName(nsACString& aName) {
+ aName.AssignLiteral("IOActivityMonitor");
+ return NS_OK;
+}
+
+bool IOActivityMonitor::IsActive() { return gInstance != nullptr; }
+
+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;
+ }
+ 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(gInstance);
+ if (!mon) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ return mon->ShutdownInternal();
+}
+
+nsresult IOActivityMonitor::ShutdownInternal() {
+ mozilla::MutexAutoLock lock(mLock);
+ mActivities.Clear();
+ gInstance = nullptr;
+ return NS_OK;
+}
+
+nsresult IOActivityMonitor::MonitorSocket(PRFileDesc* aFd) {
+ RefPtr<IOActivityMonitor> mon(gInstance);
+ if (!IsActive()) {
+ 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(gInstance);
+ if (!IsActive()) {
+ 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();
+ if (auto entry = mActivities.Lookup(aLocation)) {
+ // already registered
+ entry.Data().mTx += aTx;
+ entry.Data().mRx += aRx;
+ return true;
+ }
+ // 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;
+
+ if (NS_WARN_IF(!mActivities.Put(aLocation, activity, fallible))) {
+ return false;
+ }
+ return true;
+}
+
+nsresult IOActivityMonitor::Write(const nsACString& aLocation,
+ uint32_t aAmount) {
+ RefPtr<IOActivityMonitor> mon(gInstance);
+ if (!mon) {
+ return NS_ERROR_FAILURE;
+ }
+ return mon->WriteInternal(aLocation, aAmount);
+}
+
+nsresult IOActivityMonitor::Write(PRFileDesc* fd, uint32_t aAmount) {
+ RefPtr<IOActivityMonitor> mon(gInstance);
+ 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(gInstance);
+ if (!mon) {
+ return NS_ERROR_FAILURE;
+ }
+ return mon->Read(FileDesc2Location(fd), aAmount);
+}
+
+nsresult IOActivityMonitor::Read(const nsACString& aLocation,
+ uint32_t aAmount) {
+ RefPtr<IOActivityMonitor> mon(gInstance);
+ 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..719ea1f3ab
--- /dev/null
+++ b/netwerk/base/IOActivityMonitor.h
@@ -0,0 +1,79 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.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"
+
+typedef nsDataHashtable<nsCStringHashKey, dom::IOActivityDataDictionary>
+ Activities;
+
+// 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;
+ 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;
+};
+
+} // 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/LoadContextInfo.cpp b/netwerk/base/LoadContextInfo.cpp
new file mode 100644
index 0000000000..f96246eb40
--- /dev/null
+++ b/netwerk/base/LoadContextInfo.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 "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::HandleValue 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..bf6c4ca0ed
--- /dev/null
+++ b/netwerk/base/LoadContextInfo.h
@@ -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/. */
+
+#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* aLoadContext,
+ 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..3c2f4283f8
--- /dev/null
+++ b/netwerk/base/LoadInfo.cpp
@@ -0,0 +1,1871 @@
+/* -*- 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 "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/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 "nsIInterfaceRequestorUtils.h"
+#include "nsIScriptElement.h"
+#include "nsISupportsImpl.h"
+#include "nsISupportsUtils.h"
+#include "nsIXPConnect.h"
+#include "nsDocShell.h"
+#include "nsGlobalWindow.h"
+#include "nsMixedContentBlocker.h"
+#include "nsQueryObject.h"
+#include "nsRedirectHistoryEntry.h"
+#include "nsSandboxFlags.h"
+#include "nsICookieService.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace net {
+
+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,
+ nsIPrincipal* aTriggeringPrincipal,
+ const OriginAttributes& aOriginAttributes, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags) {
+ return MakeAndAddRef<LoadInfo>(aBrowsingContext, aTriggeringPrincipal,
+ aOriginAttributes, aSecurityFlags,
+ aSandboxFlags);
+}
+
+/* static */ already_AddRefed<LoadInfo> LoadInfo::CreateForFrame(
+ dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags) {
+ return MakeAndAddRef<LoadInfo>(aBrowsingContext, aTriggeringPrincipal,
+ 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,
+ 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)
+ : mLoadingPrincipal(aLoadingContext ? aLoadingContext->NodePrincipal()
+ : aLoadingPrincipal),
+ mTriggeringPrincipal(aTriggeringPrincipal ? aTriggeringPrincipal
+ : mLoadingPrincipal.get()),
+ mClientInfo(aLoadingClientInfo),
+ mController(aController),
+ mLoadingContext(do_GetWeakReference(aLoadingContext)),
+ mSecurityFlags(aSecurityFlags),
+ mSandboxFlags(aSandboxFlags),
+ mTriggeringSandboxFlags(0),
+ mInternalContentPolicyType(aContentPolicyType) {
+ 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();
+
+ // The top-level-storage-area-principal is not null only for the first
+ // level of iframes (null for top-level contexts, and null for
+ // sub-iframes). If we are loading a sub-document resource, we must
+ // calculate what the top-level-storage-area-principal will be for the
+ // new context.
+ if (externalType != ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ mTopLevelStorageAreaPrincipal =
+ innerWindow->GetTopLevelStorageAreaPrincipal();
+ } else if (bc->IsTop()) {
+ Document* doc = innerWindow->GetExtantDoc();
+ if (!doc || (!doc->StorageAccessSandboxed())) {
+ mTopLevelStorageAreaPrincipal = innerWindow->GetPrincipal();
+ }
+
+ // If this is the first level iframe, innerWindow is our top-level
+ // principal.
+ if (!mTopLevelPrincipal) {
+ 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 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 (nsContentUtils::IsUpgradableDisplayType(externalType)) {
+ if (mLoadingPrincipal->SchemeIs("https")) {
+ if (StaticPrefs::security_mixed_content_upgrade_display_content()) {
+ mBrowserUpgradeInsecureRequests = true;
+ } else {
+ mBrowserWouldUpgradeInsecureRequests = true;
+ }
+ }
+ }
+ }
+ mOriginAttributes = mLoadingPrincipal->OriginAttributesRef();
+
+ // We need to do this after inheriting the document's origin attributes
+ // above, in case the loading principal ends up being the system principal.
+ if (aLoadingContext) {
+ nsCOMPtr<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);
+ }
+ }
+ }
+
+ // For chrome docshell, the mPrivateBrowsingId remains 0 even its
+ // UsePrivateBrowsing() is true, so we only update the mPrivateBrowsingId in
+ // origin attributes if the type of the docshell is content.
+ if (aLoadingContext) {
+ nsCOMPtr<nsIDocShell> docShell = aLoadingContext->OwnerDoc()->GetDocShell();
+ if (docShell) {
+ if (docShell->GetBrowsingContext()->IsChrome()) {
+ MOZ_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0,
+ "chrome docshell shouldn't have mPrivateBrowsingId set.");
+ }
+ }
+ }
+
+ // in case this is a loadinfo for a parser generated script, then we store
+ // that bit of information so CSP strict-dynamic can query it.
+ if (!nsContentUtils::IsPreloadType(mInternalContentPolicyType)) {
+ nsCOMPtr<nsIScriptElement> script = do_QueryInterface(aLoadingContext);
+ if (script && script->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER) {
+ mParserCreatedScript = true;
+ }
+ }
+}
+
+/* Constructor takes an outer window, but no loadingNode or loadingPrincipal.
+ * This constructor should only be used for TYPE_DOCUMENT loads, since they
+ * have a null loadingNode and loadingPrincipal.
+ */
+LoadInfo::LoadInfo(nsPIDOMWindowOuter* aOuterWindow,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsISupports* aContextForTopLevelLoad,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags)
+ : mTriggeringPrincipal(aTriggeringPrincipal),
+ mContextForTopLevelLoad(do_GetWeakReference(aContextForTopLevelLoad)),
+ mSecurityFlags(aSecurityFlags),
+ mSandboxFlags(aSandboxFlags),
+ mTriggeringSandboxFlags(0),
+ 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();
+ // mTopLevelStorageAreaPrincipal is always null for top-level document
+ // loading.
+ }
+
+ // 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.
+ mCookieJarSettings = CookieJarSettings::Create();
+}
+
+LoadInfo::LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIPrincipal* aTriggeringPrincipal,
+ const OriginAttributes& aOriginAttributes,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags)
+ : mTriggeringPrincipal(aTriggeringPrincipal),
+ mSecurityFlags(aSecurityFlags),
+ mSandboxFlags(aSandboxFlags),
+ mTriggeringSandboxFlags(0),
+ 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
+
+ // 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();
+}
+
+LoadInfo::LoadInfo(dom::WindowGlobalParent* aParentWGP,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsContentPolicyType aContentPolicyType,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags)
+ : mTriggeringPrincipal(aTriggeringPrincipal),
+ 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();
+ }
+ }
+ }
+
+ // The top-level-storage-area-principal is not null only for the first
+ // level of iframes (null for top-level contexts, and null for
+ // sub-iframes). If we are loading a sub-document resource, we must
+ // calculate what the top-level-storage-area-principal will be for the
+ // new context.
+ if (parentBC->IsTop()) {
+ if (!Document::StorageAccessSandboxed(aParentWGP->SandboxFlags())) {
+ mTopLevelStorageAreaPrincipal = aParentWGP->DocumentPrincipal();
+ }
+
+ // If this is the first level iframe, embedder WindowGlobalParent's document
+ // principal is our top-level principal.
+ if (!mTopLevelPrincipal) {
+ 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 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();
+ }
+}
+
+// Used for TYPE_FRAME or TYPE_IFRAME load.
+LoadInfo::LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags)
+ : LoadInfo(aBrowsingContext->GetParentWindowContext(), aTriggeringPrincipal,
+ InternalContentPolicyTypeForFrame(aBrowsingContext),
+ aSecurityFlags, aSandboxFlags) {
+ mFrameBrowsingContextID = aBrowsingContext->Id();
+}
+
+LoadInfo::LoadInfo(const LoadInfo& rhs)
+ : mLoadingPrincipal(rhs.mLoadingPrincipal),
+ mTriggeringPrincipal(rhs.mTriggeringPrincipal),
+ mPrincipalToInherit(rhs.mPrincipalToInherit),
+ mSandboxedLoadingPrincipal(rhs.mSandboxedLoadingPrincipal),
+ mTopLevelPrincipal(rhs.mTopLevelPrincipal),
+ mTopLevelStorageAreaPrincipal(rhs.mTopLevelStorageAreaPrincipal),
+ mResultPrincipalURI(rhs.mResultPrincipalURI),
+ mCookieJarSettings(rhs.mCookieJarSettings),
+ mCspToInherit(rhs.mCspToInherit),
+ mClientInfo(rhs.mClientInfo),
+ // mReservedClientSource must be handled specially during redirect
+ // mReservedClientInfo must be handled specially during redirect
+ // mInitialClientInfo must be handled specially during redirect
+ mController(rhs.mController),
+ mPerformanceStorage(rhs.mPerformanceStorage),
+ mLoadingContext(rhs.mLoadingContext),
+ mContextForTopLevelLoad(rhs.mContextForTopLevelLoad),
+ mSecurityFlags(rhs.mSecurityFlags),
+ mSandboxFlags(rhs.mSandboxFlags),
+ mTriggeringSandboxFlags(rhs.mTriggeringSandboxFlags),
+ mInternalContentPolicyType(rhs.mInternalContentPolicyType),
+ mTainting(rhs.mTainting),
+ mBlockAllMixedContent(rhs.mBlockAllMixedContent),
+ mUpgradeInsecureRequests(rhs.mUpgradeInsecureRequests),
+ mBrowserUpgradeInsecureRequests(rhs.mBrowserUpgradeInsecureRequests),
+ mBrowserDidUpgradeInsecureRequests(
+ rhs.mBrowserDidUpgradeInsecureRequests),
+ mBrowserWouldUpgradeInsecureRequests(
+ rhs.mBrowserWouldUpgradeInsecureRequests),
+ mForceAllowDataURI(rhs.mForceAllowDataURI),
+ mAllowInsecureRedirectToDataURI(rhs.mAllowInsecureRedirectToDataURI),
+ mBypassCORSChecks(rhs.mBypassCORSChecks),
+ mSkipContentPolicyCheckForWebRequest(
+ rhs.mSkipContentPolicyCheckForWebRequest),
+ mOriginalFrameSrcLoad(rhs.mOriginalFrameSrcLoad),
+ mForceInheritPrincipalDropped(rhs.mForceInheritPrincipalDropped),
+ mInnerWindowID(rhs.mInnerWindowID),
+ mBrowsingContextID(rhs.mBrowsingContextID),
+ 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),
+ // mServiceWorkerTaintingSynthesized must be handled specially during
+ // redirect
+ mServiceWorkerTaintingSynthesized(false),
+ mDocumentHasUserInteracted(rhs.mDocumentHasUserInteracted),
+ mAllowListFutureDocumentsCreatedFromThisRedirectChain(
+ rhs.mAllowListFutureDocumentsCreatedFromThisRedirectChain),
+ mCspNonce(rhs.mCspNonce),
+ mSkipContentSniffing(rhs.mSkipContentSniffing),
+ mHttpsOnlyStatus(rhs.mHttpsOnlyStatus),
+ mHasValidUserGestureActivation(rhs.mHasValidUserGestureActivation),
+ mAllowDeprecatedSystemRequests(rhs.mAllowDeprecatedSystemRequests),
+ mIsInDevToolsContext(rhs.mIsInDevToolsContext),
+ mParserCreatedScript(rhs.mParserCreatedScript),
+ mHasStoragePermission(rhs.mHasStoragePermission),
+ mIsFromProcessingFrameAttributes(rhs.mIsFromProcessingFrameAttributes),
+ mLoadingEmbedderPolicy(rhs.mLoadingEmbedderPolicy) {}
+
+LoadInfo::LoadInfo(
+ nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit, nsIPrincipal* aSandboxedLoadingPrincipal,
+ nsIPrincipal* aTopLevelPrincipal,
+ nsIPrincipal* aTopLevelStorageAreaPrincipal, nsIURI* aResultPrincipalURI,
+ nsICookieJarSettings* aCookieJarSettings,
+ nsIContentSecurityPolicy* aCspToInherit,
+ const Maybe<ClientInfo>& aClientInfo,
+ const Maybe<ClientInfo>& aReservedClientInfo,
+ const Maybe<ClientInfo>& aInitialClientInfo,
+ const Maybe<ServiceWorkerDescriptor>& aController,
+ nsSecurityFlags aSecurityFlags, uint32_t aSandboxFlags,
+ uint32_t aTriggeringSandboxFlags, nsContentPolicyType aContentPolicyType,
+ LoadTainting aTainting, bool aBlockAllMixedContent,
+ bool aUpgradeInsecureRequests, bool aBrowserUpgradeInsecureRequests,
+ bool aBrowserDidUpgradeInsecureRequests,
+ bool aBrowserWouldUpgradeInsecureRequests, bool aForceAllowDataURI,
+ bool aAllowInsecureRedirectToDataURI, bool aBypassCORSChecks,
+ bool aSkipContentPolicyCheckForWebRequest, bool aOriginalFrameSrcLoad,
+ bool aForceInheritPrincipalDropped, uint64_t aInnerWindowID,
+ uint64_t aBrowsingContextID, uint64_t aFrameBrowsingContextID,
+ bool aInitialSecurityCheckDone, bool aIsThirdPartyContext,
+ 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,
+ const nsAString& aCspNonce, bool aSkipContentSniffing,
+ uint32_t aHttpsOnlyStatus, bool aHasValidUserGestureActivation,
+ bool aAllowDeprecatedSystemRequests, bool aIsInDevToolsContext,
+ bool aParserCreatedScript, bool aHasStoragePermission,
+ uint32_t aRequestBlockingReason, nsINode* aLoadingContext,
+ nsILoadInfo::CrossOriginEmbedderPolicy aLoadingEmbedderPolicy)
+ : mLoadingPrincipal(aLoadingPrincipal),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mPrincipalToInherit(aPrincipalToInherit),
+ mTopLevelPrincipal(aTopLevelPrincipal),
+ mTopLevelStorageAreaPrincipal(aTopLevelStorageAreaPrincipal),
+ mResultPrincipalURI(aResultPrincipalURI),
+ mCookieJarSettings(aCookieJarSettings),
+ mCspToInherit(aCspToInherit),
+ mClientInfo(aClientInfo),
+ mReservedClientInfo(aReservedClientInfo),
+ mInitialClientInfo(aInitialClientInfo),
+ mController(aController),
+ mLoadingContext(do_GetWeakReference(aLoadingContext)),
+ mSecurityFlags(aSecurityFlags),
+ mSandboxFlags(aSandboxFlags),
+ mTriggeringSandboxFlags(aTriggeringSandboxFlags),
+ mInternalContentPolicyType(aContentPolicyType),
+ mTainting(aTainting),
+ mBlockAllMixedContent(aBlockAllMixedContent),
+ mUpgradeInsecureRequests(aUpgradeInsecureRequests),
+ mBrowserUpgradeInsecureRequests(aBrowserUpgradeInsecureRequests),
+ mBrowserDidUpgradeInsecureRequests(aBrowserDidUpgradeInsecureRequests),
+ mBrowserWouldUpgradeInsecureRequests(
+ aBrowserWouldUpgradeInsecureRequests),
+ mForceAllowDataURI(aForceAllowDataURI),
+ mAllowInsecureRedirectToDataURI(aAllowInsecureRedirectToDataURI),
+ mBypassCORSChecks(aBypassCORSChecks),
+ 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),
+ mCspNonce(aCspNonce),
+ mSkipContentSniffing(aSkipContentSniffing),
+ mHttpsOnlyStatus(aHttpsOnlyStatus),
+ mHasValidUserGestureActivation(aHasValidUserGestureActivation),
+ mAllowDeprecatedSystemRequests(aAllowDeprecatedSystemRequests),
+ mIsInDevToolsContext(aIsInDevToolsContext),
+ mParserCreatedScript(aParserCreatedScript),
+ mHasStoragePermission(aHasStoragePermission),
+ mIsFromProcessingFrameAttributes(false),
+ mLoadingEmbedderPolicy(aLoadingEmbedderPolicy) {
+ // 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)
+
+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) {
+ NS_IF_ADDREF(*aLoadingPrincipal = mLoadingPrincipal);
+ 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) {
+ NS_IF_ADDREF(*aPrincipalToInherit = mPrincipalToInherit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) {
+ MOZ_ASSERT(aPrincipalToInherit, "must be a valid principal to inherit");
+ mPrincipalToInherit = aPrincipalToInherit;
+ return NS_OK;
+}
+
+nsIPrincipal* LoadInfo::PrincipalToInherit() { return mPrincipalToInherit; }
+
+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);
+}
+
+nsIPrincipal* LoadInfo::GetSandboxedLoadingPrincipal() {
+ if (!(mSandboxFlags & SANDBOXED_ORIGIN)) {
+ return nullptr;
+ }
+
+ if (!mSandboxedLoadingPrincipal) {
+ if (mLoadingPrincipal) {
+ mSandboxedLoadingPrincipal =
+ NullPrincipal::CreateWithInheritedAttributes(mLoadingPrincipal);
+ } else {
+ OriginAttributes attrs(mOriginAttributes);
+ mSandboxedLoadingPrincipal = NullPrincipal::Create(attrs);
+ }
+ }
+ MOZ_ASSERT(mSandboxedLoadingPrincipal);
+
+ return mSandboxedLoadingPrincipal;
+}
+
+nsIPrincipal* LoadInfo::GetTopLevelPrincipal() { return mTopLevelPrincipal; }
+
+nsIPrincipal* LoadInfo::GetTopLevelStorageAreaPrincipal() {
+ return mTopLevelStorageAreaPrincipal;
+}
+
+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::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;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetIsThirdPartyContextToTopWindow(
+ bool aIsThirdPartyContextToTopWindow) {
+ mIsThirdPartyContextToTopWindow = 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) {
+ if (StaticPrefs::network_cookieJarSettings_unblocked_for_testing()) {
+ return CookieJarSettings::Create();
+ }
+
+ // 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 CookieJarSettings::Create();
+ }
+
+ return CookieJarSettings::GetBlockingAll();
+}
+
+} // namespace
+
+NS_IMETHODIMP
+LoadInfo::GetCookieJarSettings(nsICookieJarSettings** aCookieJarSettings) {
+ if (!mCookieJarSettings) {
+ mCookieJarSettings = CreateCookieJarSettings(mInternalContentPolicyType);
+ }
+
+ 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::GetHasStoragePermission(bool* aHasStoragePermission) {
+ *aHasStoragePermission = mHasStoragePermission;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetHasStoragePermission(bool aHasStoragePermission) {
+ mHasStoragePermission = aHasStoragePermission;
+ 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::SetBypassCORSChecks(bool aBypassCORSChecks) {
+ mBypassCORSChecks = aBypassCORSChecks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetBypassCORSChecks(bool* aBypassCORSChecks) {
+ *aBypassCORSChecks = mBypassCORSChecks;
+ 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::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::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;
+}
+
+NS_IMETHODIMP
+LoadInfo::AppendRedirectHistoryEntry(nsIRedirectHistoryEntry* aEntry,
+ bool aIsInternalRedirect) {
+ NS_ENSURE_ARG(aEntry);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mRedirectChainIncludingInternalRedirects.AppendElement(aEntry);
+ if (!aIsInternalRedirect) {
+ mRedirectChain.AppendElement(aEntry);
+ }
+ 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::RootedObject 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::GetCspNonce(nsAString& aCspNonce) {
+ aCspNonce = mCspNonce;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetCspNonce(const nsAString& aCspNonce) {
+ MOZ_ASSERT(!mInitialSecurityCheckDone,
+ "setting the nonce is only allowed before any sec checks");
+ mCspNonce = aCspNonce;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetSkipContentSniffing(bool* aSkipContentSniffing) {
+ *aSkipContentSniffing = mSkipContentSniffing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetSkipContentSniffing(bool aSkipContentSniffing) {
+ mSkipContentSniffing = aSkipContentSniffing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetHttpsOnlyStatus(uint32_t* aHttpsOnlyStatus) {
+ *aHttpsOnlyStatus = mHttpsOnlyStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetHttpsOnlyStatus(uint32_t aHttpsOnlyStatus) {
+ mHttpsOnlyStatus = aHttpsOnlyStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::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::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::GetResultPrincipalURI(nsIURI** aURI) {
+ NS_IF_ADDREF(*aURI = mResultPrincipalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::SetResultPrincipalURI(nsIURI* aURI) {
+ mResultPrincipalURI = 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;
+}
+
+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() &&
+ mReservedClientInfo.ref() == aClientInfo) {
+ return;
+ }
+ 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) {
+ NS_IF_ADDREF(*aCSPEventListener = mCSPEventListener);
+ 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;
+}
+
+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();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h
new file mode 100644
index 0000000000..5efa7332fa
--- /dev/null
+++ b/netwerk/base/LoadInfo.h
@@ -0,0 +1,342 @@
+/* -*- 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 "nsILoadInfo.h"
+#include "nsIPrincipal.h"
+#include "nsIWeakReferenceUtils.h" // for nsWeakPtr
+#include "nsIURI.h"
+#include "nsContentUtils.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
+
+class nsDocShell;
+class nsICookieJarSettings;
+class nsINode;
+class nsPIDOMWindowOuter;
+
+namespace mozilla {
+
+namespace dom {
+class PerformanceStorage;
+class XMLHttpRequestMainThread;
+class CanonicalBrowsingContext;
+class WindowGlobalParent;
+} // namespace dom
+
+namespace net {
+class LoadInfoArgs;
+class LoadInfo;
+} // namespace net
+
+namespace ipc {
+// we have to forward declare that function so we can use it as a friend.
+nsresult LoadInfoArgsToLoadInfo(
+ const Maybe<mozilla::net::LoadInfoArgs>& aLoadInfoArgs,
+ nsINode* aCspToInheritLoadingContext, net::LoadInfo** outLoadInfo);
+} // namespace ipc
+
+namespace net {
+
+typedef nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>> RedirectHistoryArray;
+
+/**
+ * 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,
+ nsIPrincipal* aTriggeringPrincipal,
+ 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, 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);
+
+ // 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, nsIPrincipal* aTriggeringPrincipal,
+ nsISupports* aContextForTopLevelLoad, nsSecurityFlags aSecurityFlags,
+ uint32_t aSandboxFlags);
+
+ private:
+ // Use factory function CreateForDocument
+ // Used for TYPE_DOCUMENT load.
+ LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIPrincipal* aTriggeringPrincipal,
+ 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, 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,
+ 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;
+ }
+
+ 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* aSandboxedLoadingPrincipal,
+ nsIPrincipal* aTopLevelPrincipal,
+ nsIPrincipal* aTopLevelStorageAreaPrincipal, nsIURI* aResultPrincipalURI,
+ nsICookieJarSettings* aCookieJarSettings,
+ nsIContentSecurityPolicy* aCspToInherit,
+ 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, nsContentPolicyType aContentPolicyType,
+ LoadTainting aTainting, bool aBlockAllMixedContent,
+ bool aUpgradeInsecureRequests, bool aBrowserUpgradeInsecureRequests,
+ bool aBrowserDidUpgradeInsecureRequests,
+ bool aBrowserWouldUpgradeInsecureRequests, bool aForceAllowDataURI,
+ bool aAllowInsecureRedirectToDataURI, bool aBypassCORSChecks,
+ bool aSkipContentPolicyCheckForWebRequest, bool aOriginalFrameSrcLoad,
+ bool aForceInheritPrincipalDropped, uint64_t aInnerWindowID,
+ uint64_t aBrowsingContextID, uint64_t aFrameBrowsingContextID,
+ bool aInitialSecurityCheckDone, bool aIsThirdPartyContext,
+ 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,
+ const nsAString& aCspNonce, bool aSkipContentSniffing,
+ uint32_t aHttpsOnlyStatus, bool aHasValidUserGestureActivation,
+ bool aAllowDeprecatedSystemRequests, bool aIsInDevToolsContext,
+ bool aParserCreatedScript, bool aHasStoragePermission,
+ uint32_t aRequestBlockingReason, nsINode* aLoadingContext,
+ nsILoadInfo::CrossOriginEmbedderPolicy aLoadingEmbedderPolicy);
+ LoadInfo(const LoadInfo& rhs);
+
+ NS_IMETHOD GetRedirects(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aRedirects,
+ const RedirectHistoryArray& aArra);
+
+ friend nsresult mozilla::ipc::LoadInfoArgsToLoadInfo(
+ const Maybe<mozilla::net::LoadInfoArgs>& aLoadInfoArgs,
+ nsINode* aCspToInheritLoadingContext, net::LoadInfo** outLoadInfo);
+
+ ~LoadInfo() = default;
+
+ void ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow);
+ void ComputeIsThirdPartyContext(dom::WindowGlobalParent* aGlobal);
+
+ // This function is the *only* function which can change the securityflags
+ // of a loadinfo. It only exists because of the XHR code. Don't call it
+ // from anywhere else!
+ void SetIncludeCookiesSecFlag();
+ friend class mozilla::dom::XMLHttpRequestMainThread;
+
+ // nsDocShell::OpenInitializedChannel needs to update the loadInfo with
+ // the correct browsingContext.
+ friend class ::nsDocShell;
+ void UpdateBrowsingContextID(uint64_t aBrowsingContextID) {
+ mBrowsingContextID = aBrowsingContextID;
+ }
+ void UpdateFrameBrowsingContextID(uint64_t aFrameBrowsingContextID) {
+ mFrameBrowsingContextID = aFrameBrowsingContextID;
+ }
+
+ // 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> mSandboxedLoadingPrincipal;
+ nsCOMPtr<nsIPrincipal> mTopLevelPrincipal;
+ nsCOMPtr<nsIPrincipal> mTopLevelStorageAreaPrincipal;
+ nsCOMPtr<nsIURI> mResultPrincipalURI;
+ nsCOMPtr<nsICSPEventListener> mCSPEventListener;
+ nsCOMPtr<nsICookieJarSettings> mCookieJarSettings;
+ nsCOMPtr<nsIContentSecurityPolicy> mCspToInherit;
+
+ 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;
+ 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 mBypassCORSChecks = false;
+ bool mSkipContentPolicyCheckForWebRequest = false;
+ bool mOriginalFrameSrcLoad = false;
+ bool mForceInheritPrincipalDropped = false;
+ uint64_t mInnerWindowID = 0;
+ uint64_t mBrowsingContextID = 0;
+ uint64_t mFrameBrowsingContextID = 0;
+ bool mInitialSecurityCheckDone = false;
+ // NB: TYPE_DOCUMENT implies !third-party.
+ bool mIsThirdPartyContext = false;
+ bool mIsThirdPartyContextToTopWindow = true;
+ 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;
+ nsString mCspNonce;
+ bool mSkipContentSniffing = false;
+ uint32_t mHttpsOnlyStatus = nsILoadInfo::HTTPS_ONLY_UNINITIALIZED;
+ bool mHasValidUserGestureActivation = false;
+ bool mAllowDeprecatedSystemRequests = false;
+ bool mIsInDevToolsContext = false;
+ bool mParserCreatedScript = false;
+ bool mHasStoragePermission = false;
+
+ // Is true if this load was triggered by processing the attributes of the
+ // browsing context container.
+ // See nsILoadInfo.isFromProcessingFrameAttributes
+ bool mIsFromProcessingFrameAttributes = 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;
+};
+
+} // 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..e01d5cd6a3
--- /dev/null
+++ b/netwerk/base/LoadTainting.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LoadTainting_h
+#define mozilla_LoadTainting_h
+
+namespace mozilla {
+
+// Define an enumeration to reflect the concept of response tainting from the
+// the fetch spec:
+//
+// https://fetch.spec.whatwg.org/#concept-request-response-tainting
+//
+// Roughly the tainting means:
+//
+// * Basic: the request resulted in a same-origin or non-http load
+// * CORS: the request resulted in a cross-origin load with CORS headers
+// * Opaque: the request resulted in a cross-origin load without CORS headers
+//
+// The enumeration is purposefully designed such that more restrictive tainting
+// corresponds to a higher integral value.
+//
+// NOTE: Checking the tainting is not currently adequate. You *must* still
+// check the final URL and CORS mode on the channel.
+//
+// These values are currently only set on the channel LoadInfo when the request
+// was initiated through fetch() or when a service worker interception occurs.
+// In the future we should set the tainting value within necko so that it is
+// consistently applied. Once that is done consumers can replace checks against
+// the final URL and CORS mode with checks against tainting.
+enum class LoadTainting : uint8_t { Basic = 0, CORS = 1, Opaque = 2 };
+
+} // namespace mozilla
+
+#endif // mozilla_LoadTainting_h
diff --git a/netwerk/base/MemoryDownloader.cpp b/netwerk/base/MemoryDownloader.cpp
new file mode 100644
index 0000000000..1ff1c9d1e3
--- /dev/null
+++ b/netwerk/base/MemoryDownloader.cpp
@@ -0,0 +1,72 @@
+/* -*- 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, nullptr, 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..6337d439ea
--- /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
+
+ typedef mozilla::UniquePtr<FallibleTArray<uint8_t>> Data;
+
+ class IObserver : public nsISupports {
+ public:
+ // Note: aData may be null if (and only if) aStatus indicates failure.
+ virtual void OnDownloadComplete(MemoryDownloader* aDownloader,
+ nsIRequest* aRequest, nsISupports* aCtxt,
+ nsresult aStatus, Data aData) = 0;
+ };
+
+ explicit MemoryDownloader(IObserver* aObserver);
+
+ private:
+ virtual ~MemoryDownloader() = 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.jsm b/netwerk/base/NetUtil.jsm
new file mode 100644
index 0000000000..f901b9805b
--- /dev/null
+++ b/netwerk/base/NetUtil.jsm
@@ -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/. */
+
+var EXPORTED_SYMBOLS = ["NetUtil"];
+
+/**
+ * Necko utilities
+ */
+
+// //////////////////////////////////////////////////////////////////////////////
+// // Constants
+
+const PR_UINT32_MAX = 0xffffffff;
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+// //////////////////////////////////////////////////////////////////////////////
+// // NetUtil Object
+
+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 "OS.File" methods instead.
+ *
+ * @param aSource
+ * This argument can be one of the following:
+ * - An options object that will be passed to NetUtil.newChannel.
+ * - An existing nsIChannel.
+ * - An existing nsIInputStream.
+ * Using an nsIURI, nsIFile, or string spec directly is deprecated.
+ * @param aCallback
+ * The callback function that will be notified upon completion. It
+ * will get these arguments:
+ * 1) An nsIInputStream containing the data from aSource, if any.
+ * 2) The status code from opening the source.
+ * 3) Reference to the nsIRequest.
+ */
+ asyncFetch: function NetUtil_asyncFetch(aSource, aCallback) {
+ if (!aSource || !aCallback) {
+ let exception = new Components.Exception(
+ "Must have a source and a callback",
+ Cr.NS_ERROR_INVALID_ARG,
+ Components.stack.caller
+ );
+ throw exception;
+ }
+
+ // Create a pipe that will create our output stream that we can use once
+ // we have gotten all the data.
+ let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(true, true, 0, PR_UINT32_MAX, null);
+
+ // Create a listener that will give data to the pipe's output stream.
+ let listener = Cc[
+ "@mozilla.org/network/simple-stream-listener;1"
+ ].createInstance(Ci.nsISimpleStreamListener);
+ listener.init(pipe.outputStream, {
+ onStartRequest(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;
+ },
+
+ /**
+ * 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..4e92cb4d69
--- /dev/null
+++ b/netwerk/base/NetworkConnectivityService.cpp
@@ -0,0 +1,526 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NetworkConnectivityService.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "xpcpublic.h"
+#include "nsSocketTransport2.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsINetworkLinkService.h"
+#include "mozilla/StaticPrefs_network.h"
+
+static 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()
+ : mNAT64(UNKNOWN), mLock("nat64prefixes") {}
+
+// static
+already_AddRefed<NetworkConnectivityService>
+NetworkConnectivityService::GetSingleton() {
+ if (!XRE_IsParentProcess()) {
+ return nullptr;
+ }
+
+ 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;
+ PRNetAddr prAddr{};
+
+ nsresult rv = Preferences::GetCString(
+ "network.connectivity-service.nat64-prefix", nat64PrefixPref);
+ if (NS_FAILED(rv) || nat64PrefixPref.IsEmpty() ||
+ PR_StringToNetAddr(nat64PrefixPref.get(), &prAddr) != PR_SUCCESS ||
+ prAddr.raw.family != PR_AF_INET6) {
+ return false;
+ }
+
+ PRNetAddrToNetAddr(&prAddr, prefix);
+ return true;
+}
+
+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 = aRecord ? 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;
+ }
+
+ 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;
+}
+
+static inline already_AddRefed<nsIChannel> 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;
+ 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);
+
+ rv = channel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE);
+ 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);
+ }
+
+ 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 (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..190f67a73f
--- /dev/null
+++ b/netwerk/base/NetworkConnectivityService.h
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NetworkConnectivityService_h_
+#define NetworkConnectivityService_h_
+
+#include "nsINetworkConnectivityService.h"
+#include "nsIObserver.h"
+#include "nsIDNSListener.h"
+#include "nsIStreamListener.h"
+#include "mozilla/net/DNS.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);
+
+ // 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
+ ConnectivityState mDNSv4 = nsINetworkConnectivityService::UNKNOWN;
+ ConnectivityState mDNSv6 = nsINetworkConnectivityService::UNKNOWN;
+
+ ConnectivityState mIPv4 = nsINetworkConnectivityService::UNKNOWN;
+ ConnectivityState mIPv6 = nsINetworkConnectivityService::UNKNOWN;
+
+ 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;
+};
+
+} // 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..7b6461b5f8
--- /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() {}
+
+ 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..b5c486dad8
--- /dev/null
+++ b/netwerk/base/NetworkDataCountLayer.h
@@ -0,0 +1,26 @@
+/* -*- 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"
+
+namespace mozilla {
+namespace net {
+
+nsresult AttachNetworkDataCountLayer(PRFileDesc* fd);
+
+// Get amount of sent bytes.
+void NetworkDataCountSent(PRFileDesc* fd, uint64_t& sentBytes);
+
+// Get amount of received bytes.
+void NetworkDataCountReceived(PRFileDesc* fd, uint64_t& receivedBytes);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // NetworkDataCountLayer_h__
diff --git a/netwerk/base/NetworkInfoServiceCocoa.cpp b/netwerk/base/NetworkInfoServiceCocoa.cpp
new file mode 100644
index 0000000000..702617b046
--- /dev/null
+++ b/netwerk/base/NetworkInfoServiceCocoa.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <net/if.h>
+#include <netdb.h>
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ScopeExit.h"
+
+#include "NetworkInfoServiceImpl.h"
+
+namespace mozilla {
+namespace net {
+
+static nsresult ListInterfaceAddresses(int aFd, const char* aIface,
+ AddrMapType& aAddrMap);
+
+nsresult DoListAddresses(AddrMapType& aAddrMap) {
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto autoCloseSocket = MakeScopeExit([&] { close(fd); });
+
+ struct ifconf ifconf;
+ /* 16k of space should be enough to list all interfaces. Worst case, if it's
+ * not then we will error out and fail to list addresses. This should only
+ * happen on pathological machines with way too many interfaces.
+ */
+ char buf[16384];
+
+ ifconf.ifc_len = sizeof(buf);
+ ifconf.ifc_buf = buf;
+ if (ioctl(fd, SIOCGIFCONF, &ifconf) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ struct ifreq* ifreq = ifconf.ifc_req;
+ int i = 0;
+ while (i < ifconf.ifc_len) {
+ size_t len = IFNAMSIZ + ifreq->ifr_addr.sa_len;
+
+ DebugOnly<nsresult> rv =
+ ListInterfaceAddresses(fd, ifreq->ifr_name, aAddrMap);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ListInterfaceAddresses failed");
+
+ ifreq = (struct ifreq*)((char*)ifreq + len);
+ i += len;
+ }
+
+ autoCloseSocket.release();
+ return NS_OK;
+}
+
+static nsresult ListInterfaceAddresses(int aFd, const char* aInterface,
+ AddrMapType& aAddrMap) {
+ struct ifreq ifreq;
+ memset(&ifreq, 0, sizeof(struct ifreq));
+ strncpy(ifreq.ifr_name, aInterface, IFNAMSIZ - 1);
+ if (ioctl(aFd, SIOCGIFADDR, &ifreq) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ char host[128];
+ int family;
+ switch (family = ifreq.ifr_addr.sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ getnameinfo(&ifreq.ifr_addr, sizeof(ifreq.ifr_addr), host, sizeof(host),
+ nullptr, 0, NI_NUMERICHOST);
+ break;
+ case AF_UNSPEC:
+ return NS_OK;
+ default:
+ // Unknown family.
+ return NS_OK;
+ }
+
+ nsCString ifaceStr;
+ ifaceStr.AssignASCII(aInterface);
+
+ nsCString addrStr;
+ addrStr.AssignASCII(host);
+
+ aAddrMap.Put(ifaceStr, addrStr);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/NetworkInfoServiceImpl.h b/netwerk/base/NetworkInfoServiceImpl.h
new file mode 100644
index 0000000000..6f89f33a68
--- /dev/null
+++ b/netwerk/base/NetworkInfoServiceImpl.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsString.h"
+#include "nsDataHashtable.h"
+
+namespace mozilla {
+namespace net {
+
+typedef nsDataHashtable<nsCStringHashKey, nsCString> AddrMapType;
+
+nsresult DoListAddresses(AddrMapType& aAddrMap);
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/NetworkInfoServiceLinux.cpp b/netwerk/base/NetworkInfoServiceLinux.cpp
new file mode 100644
index 0000000000..9b42ad6dbd
--- /dev/null
+++ b/netwerk/base/NetworkInfoServiceLinux.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <net/if.h>
+#include <netdb.h>
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ScopeExit.h"
+
+#include "NetworkInfoServiceImpl.h"
+
+namespace mozilla::net {
+
+static nsresult ListInterfaceAddresses(int aFd, const char* aIface,
+ AddrMapType& aAddrMap);
+
+nsresult DoListAddresses(AddrMapType& aAddrMap) {
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto autoCloseSocket = MakeScopeExit([&] { close(fd); });
+
+ struct ifconf ifconf;
+ /* 16k of space should be enough to list all interfaces. Worst case, if it's
+ * not then we will error out and fail to list addresses. This should only
+ * happen on pathological machines with way too many interfaces.
+ */
+ char buf[16384];
+
+ ifconf.ifc_len = sizeof(buf);
+ ifconf.ifc_buf = buf;
+ if (ioctl(fd, SIOCGIFCONF, &ifconf) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ struct ifreq* ifreq = ifconf.ifc_req;
+ int i = 0;
+ while (i < ifconf.ifc_len) {
+ size_t len = sizeof(struct ifreq);
+
+ DebugOnly<nsresult> rv =
+ ListInterfaceAddresses(fd, ifreq->ifr_name, aAddrMap);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ListInterfaceAddresses failed");
+
+ ifreq = (struct ifreq*)((char*)ifreq + len);
+ i += len;
+ }
+
+ return NS_OK;
+}
+
+static nsresult ListInterfaceAddresses(int aFd, const char* aInterface,
+ AddrMapType& aAddrMap) {
+ struct ifreq ifreq;
+ memset(&ifreq, 0, sizeof(struct ifreq));
+ strncpy(ifreq.ifr_name, aInterface, IFNAMSIZ - 1);
+ if (ioctl(aFd, SIOCGIFADDR, &ifreq) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ char host[128];
+ switch (ifreq.ifr_addr.sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ getnameinfo(&ifreq.ifr_addr, sizeof(ifreq.ifr_addr), host, sizeof(host),
+ nullptr, 0, NI_NUMERICHOST);
+ break;
+ case AF_UNSPEC:
+ return NS_OK;
+ default:
+ // Unknown family.
+ return NS_OK;
+ }
+
+ nsCString ifaceStr;
+ ifaceStr.AssignASCII(aInterface);
+
+ nsCString addrStr;
+ addrStr.AssignASCII(host);
+
+ aAddrMap.Put(ifaceStr, addrStr);
+
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/base/NetworkInfoServiceWindows.cpp b/netwerk/base/NetworkInfoServiceWindows.cpp
new file mode 100644
index 0000000000..c67dd70801
--- /dev/null
+++ b/netwerk/base/NetworkInfoServiceWindows.cpp
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <winsock2.h>
+#include <ws2ipdef.h>
+#include <iphlpapi.h>
+
+#include "mozilla/UniquePtr.h"
+
+#include "NetworkInfoServiceImpl.h"
+
+namespace mozilla {
+namespace net {
+
+nsresult DoListAddresses(AddrMapType& aAddrMap) {
+ UniquePtr<MIB_IPADDRTABLE> ipAddrTable;
+ DWORD size = sizeof(MIB_IPADDRTABLE);
+
+ ipAddrTable.reset((MIB_IPADDRTABLE*)malloc(size));
+ if (!ipAddrTable) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DWORD retVal = GetIpAddrTable(ipAddrTable.get(), &size, 0);
+ if (retVal == ERROR_INSUFFICIENT_BUFFER) {
+ ipAddrTable.reset((MIB_IPADDRTABLE*)malloc(size));
+ if (!ipAddrTable) {
+ return NS_ERROR_FAILURE;
+ }
+ retVal = GetIpAddrTable(ipAddrTable.get(), &size, 0);
+ }
+ if (retVal != NO_ERROR) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (DWORD i = 0; i < ipAddrTable->dwNumEntries; i++) {
+ int index = ipAddrTable->table[i].dwIndex;
+ uint32_t addrVal = (uint32_t)ipAddrTable->table[i].dwAddr;
+
+ nsCString indexString;
+ indexString.AppendInt(index, 10);
+
+ nsCString addrString;
+ addrString.AppendPrintf("%d.%d.%d.%d", (addrVal >> 0) & 0xff,
+ (addrVal >> 8) & 0xff, (addrVal >> 16) & 0xff,
+ (addrVal >> 24) & 0xff);
+
+ aAddrMap.Put(indexString, addrString);
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/PartiallySeekableInputStream.cpp b/netwerk/base/PartiallySeekableInputStream.cpp
new file mode 100644
index 0000000000..de1e61c6a5
--- /dev/null
+++ b/netwerk/base/PartiallySeekableInputStream.cpp
@@ -0,0 +1,437 @@
+/* -*- 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 "PartiallySeekableInputStream.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "nsISeekableStream.h"
+#include "nsStreamUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ADDREF(PartiallySeekableInputStream);
+NS_IMPL_RELEASE(PartiallySeekableInputStream);
+
+NS_INTERFACE_MAP_BEGIN(PartiallySeekableInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
+ NS_INTERFACE_MAP_ENTRY(nsITellableStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream,
+ mWeakCloneableInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
+ mWeakIPCSerializableInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, mWeakAsyncInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback,
+ mWeakAsyncInputStream)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength,
+ mWeakInputStreamLength)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength,
+ mWeakAsyncInputStreamLength)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLengthCallback,
+ mWeakAsyncInputStreamLength)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END
+
+PartiallySeekableInputStream::PartiallySeekableInputStream(
+ already_AddRefed<nsIInputStream> aInputStream, uint64_t aBufferSize)
+ : mInputStream(std::move(aInputStream)),
+ mWeakCloneableInputStream(nullptr),
+ mWeakIPCSerializableInputStream(nullptr),
+ mWeakAsyncInputStream(nullptr),
+ mWeakInputStreamLength(nullptr),
+ mWeakAsyncInputStreamLength(nullptr),
+ mBufferSize(aBufferSize),
+ mPos(0),
+ mClosed(false),
+ mMutex("PartiallySeekableInputStream::mMutex") {
+ Init();
+}
+
+PartiallySeekableInputStream::PartiallySeekableInputStream(
+ already_AddRefed<nsIInputStream> aClonedBaseStream,
+ PartiallySeekableInputStream* aClonedFrom)
+ : mInputStream(std::move(aClonedBaseStream)),
+ mWeakCloneableInputStream(nullptr),
+ mWeakIPCSerializableInputStream(nullptr),
+ mWeakAsyncInputStream(nullptr),
+ mWeakInputStreamLength(nullptr),
+ mWeakAsyncInputStreamLength(nullptr),
+ mCachedBuffer(aClonedFrom->mCachedBuffer.Clone()),
+ mBufferSize(aClonedFrom->mBufferSize),
+ mPos(aClonedFrom->mPos),
+ mClosed(aClonedFrom->mClosed),
+ mMutex("PartiallySeekableInputStream::mMutex") {
+ Init();
+}
+
+void PartiallySeekableInputStream::Init() {
+ MOZ_ASSERT(mInputStream);
+
+#ifdef DEBUG
+ nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(mInputStream);
+ MOZ_ASSERT(!seekableStream);
+#endif
+
+ nsCOMPtr<nsICloneableInputStream> cloneableStream =
+ do_QueryInterface(mInputStream);
+ if (cloneableStream && SameCOMIdentity(mInputStream, cloneableStream)) {
+ mWeakCloneableInputStream = cloneableStream;
+ }
+
+ nsCOMPtr<nsIIPCSerializableInputStream> serializableStream =
+ do_QueryInterface(mInputStream);
+ if (serializableStream && SameCOMIdentity(mInputStream, serializableStream)) {
+ mWeakIPCSerializableInputStream = serializableStream;
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
+ do_QueryInterface(mInputStream);
+ if (asyncInputStream && SameCOMIdentity(mInputStream, asyncInputStream)) {
+ mWeakAsyncInputStream = asyncInputStream;
+ }
+
+ nsCOMPtr<nsIInputStreamLength> inputStreamLength =
+ do_QueryInterface(mInputStream);
+ if (inputStreamLength && SameCOMIdentity(mInputStream, inputStreamLength)) {
+ mWeakInputStreamLength = inputStreamLength;
+ }
+
+ nsCOMPtr<nsIAsyncInputStreamLength> asyncInputStreamLength =
+ do_QueryInterface(mInputStream);
+ if (asyncInputStreamLength &&
+ SameCOMIdentity(mInputStream, asyncInputStreamLength)) {
+ mWeakAsyncInputStreamLength = asyncInputStreamLength;
+ }
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::Close() {
+ mInputStream->Close();
+ mCachedBuffer.Clear();
+ mPos = 0;
+ mClosed = true;
+ return NS_OK;
+}
+
+// nsIInputStream interface
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::Available(uint64_t* aLength) {
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ nsresult rv = mInputStream->Available(aLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (mPos < mCachedBuffer.Length()) {
+ *aLength += mCachedBuffer.Length() - mPos;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::Read(char* aBuffer, uint32_t aCount,
+ uint32_t* aReadCount) {
+ *aReadCount = 0;
+
+ if (mClosed) {
+ return NS_OK;
+ }
+
+ uint32_t byteRead = 0;
+
+ if (mPos < mCachedBuffer.Length()) {
+ // We are reading from the cached buffer.
+ byteRead = XPCOM_MIN(mCachedBuffer.Length() - mPos, (uint64_t)aCount);
+ memcpy(aBuffer, mCachedBuffer.Elements() + mPos, byteRead);
+ *aReadCount = byteRead;
+ mPos += byteRead;
+ }
+
+ if (byteRead < aCount) {
+ MOZ_ASSERT(mPos >= mCachedBuffer.Length());
+ MOZ_ASSERT_IF(mPos > mCachedBuffer.Length(),
+ mCachedBuffer.Length() == mBufferSize);
+
+ // We can read from the stream.
+ uint32_t byteWritten;
+ nsresult rv =
+ mInputStream->Read(aBuffer + byteRead, aCount - byteRead, &byteWritten);
+ if (NS_WARN_IF(NS_FAILED(rv)) || byteWritten == 0) {
+ return rv;
+ }
+
+ *aReadCount += byteWritten;
+
+ // Maybe we have to cache something.
+ if (mPos < mBufferSize) {
+ uint32_t size = XPCOM_MIN(mPos + byteWritten, mBufferSize);
+ mCachedBuffer.SetLength(size);
+ memcpy(mCachedBuffer.Elements() + mPos, aBuffer + byteRead, size - mPos);
+ }
+
+ mPos += byteWritten;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure, uint32_t aCount,
+ uint32_t* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::IsNonBlocking(bool* aNonBlocking) {
+ return mInputStream->IsNonBlocking(aNonBlocking);
+}
+
+// nsICloneableInputStream interface
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::GetCloneable(bool* aCloneable) {
+ NS_ENSURE_STATE(mWeakCloneableInputStream);
+
+ return mWeakCloneableInputStream->GetCloneable(aCloneable);
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::Clone(nsIInputStream** aResult) {
+ NS_ENSURE_STATE(mWeakCloneableInputStream);
+
+ nsCOMPtr<nsIInputStream> clonedStream;
+ nsresult rv = mWeakCloneableInputStream->Clone(getter_AddRefs(clonedStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIInputStream> stream =
+ new PartiallySeekableInputStream(clonedStream.forget(), this);
+
+ stream.forget(aResult);
+ return NS_OK;
+}
+
+// nsIAsyncInputStream interface
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::CloseWithStatus(nsresult aStatus) {
+ NS_ENSURE_STATE(mWeakAsyncInputStream);
+
+ return mWeakAsyncInputStream->CloseWithStatus(aStatus);
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ if (mClosed) {
+ if (aCallback) {
+ if (aEventTarget) {
+ nsCOMPtr<nsIInputStreamCallback> callable = NS_NewInputStreamReadyEvent(
+ "PartiallySeekableInputStream::OnInputStreamReady", aCallback,
+ aEventTarget);
+ callable->OnInputStreamReady(this);
+ } else {
+ aCallback->OnInputStreamReady(this);
+ }
+ }
+
+ return NS_OK;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mAsyncWaitCallback && aCallback) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mAsyncWaitCallback = aCallback;
+ }
+
+ NS_ENSURE_STATE(mWeakAsyncInputStream);
+ nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr;
+ return mWeakAsyncInputStream->AsyncWait(callback, aFlags, aRequestedCount,
+ aEventTarget);
+}
+
+// nsIInputStreamCallback
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ MOZ_ASSERT(mWeakAsyncInputStream);
+ MOZ_ASSERT(mWeakAsyncInputStream == 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);
+}
+
+// nsIIPCSerializableInputStream
+
+void PartiallySeekableInputStream::Serialize(
+ mozilla::ipc::InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors, bool aDelayedStart,
+ uint32_t aMaxSize, uint32_t* aSizeUsed,
+ mozilla::ipc::ParentToChildStreamActorManager* aManager) {
+ SerializeInternal(aParams, aFileDescriptors, aDelayedStart, aMaxSize,
+ aSizeUsed, aManager);
+}
+
+void PartiallySeekableInputStream::Serialize(
+ mozilla::ipc::InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors, bool aDelayedStart,
+ uint32_t aMaxSize, uint32_t* aSizeUsed,
+ mozilla::ipc::ChildToParentStreamActorManager* aManager) {
+ SerializeInternal(aParams, aFileDescriptors, aDelayedStart, aMaxSize,
+ aSizeUsed, aManager);
+}
+
+template <typename M>
+void PartiallySeekableInputStream::SerializeInternal(
+ mozilla::ipc::InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors, bool aDelayedStart,
+ uint32_t aMaxSize, uint32_t* aSizeUsed, M* aManager) {
+ MOZ_ASSERT(mWeakIPCSerializableInputStream);
+ MOZ_DIAGNOSTIC_ASSERT(mCachedBuffer.IsEmpty());
+ mozilla::ipc::InputStreamHelper::SerializeInputStream(
+ mInputStream, aParams, aFileDescriptors, aDelayedStart, aMaxSize,
+ aSizeUsed, aManager);
+}
+
+bool PartiallySeekableInputStream::Deserialize(
+ const mozilla::ipc::InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors) {
+ MOZ_CRASH("This method should never be called!");
+ return false;
+}
+
+// nsISeekableStream
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::Seek(int32_t aWhence, int64_t aOffset) {
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ int64_t offset;
+
+ switch (aWhence) {
+ case NS_SEEK_SET:
+ offset = aOffset;
+ break;
+ case NS_SEEK_CUR:
+ offset = mPos + aOffset;
+ break;
+ case NS_SEEK_END: {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ default:
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (offset < 0) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if ((uint64_t)offset >= mCachedBuffer.Length() || mPos > mBufferSize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ mPos = offset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::Tell(int64_t* aResult) {
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ *aResult = mPos;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::SetEOF() { return Close(); }
+
+// nsIInputStreamLength
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::Length(int64_t* aLength) {
+ NS_ENSURE_STATE(mWeakInputStreamLength);
+ return mWeakInputStreamLength->Length(aLength);
+}
+
+// nsIAsyncInputStreamLength
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::AsyncLengthWait(
+ nsIInputStreamLengthCallback* aCallback, nsIEventTarget* aEventTarget) {
+ if (mClosed) {
+ if (aCallback) {
+ const RefPtr<PartiallySeekableInputStream> self = this;
+ const nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback;
+ nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
+ "PartiallySeekableInputStream::OnInputStreamLengthReady",
+ [self, callback] { callback->OnInputStreamLengthReady(self, -1); });
+ if (aEventTarget) {
+ aEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ } else {
+ runnable->Run();
+ }
+ }
+ return NS_OK;
+ }
+
+ NS_ENSURE_STATE(mWeakAsyncInputStreamLength);
+
+ nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback ? this : nullptr;
+ {
+ MutexAutoLock lock(mMutex);
+ mAsyncInputStreamLengthCallback = aCallback;
+ }
+
+ return mWeakAsyncInputStreamLength->AsyncLengthWait(callback, aEventTarget);
+}
+
+NS_IMETHODIMP
+PartiallySeekableInputStream::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);
+ }
+
+ return callback->OnInputStreamLengthReady(this, aLength);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/PartiallySeekableInputStream.h b/netwerk/base/PartiallySeekableInputStream.h
new file mode 100644
index 0000000000..92ad4114c5
--- /dev/null
+++ b/netwerk/base/PartiallySeekableInputStream.h
@@ -0,0 +1,91 @@
+/* -*- 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 PartiallySeekableInputStream_h
+#define PartiallySeekableInputStream_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+#include "nsCOMPtr.h"
+#include "nsIAsyncInputStream.h"
+#include "nsICloneableInputStream.h"
+#include "nsIInputStreamLength.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsISeekableStream.h"
+
+namespace mozilla {
+namespace net {
+
+// A wrapper for making a stream seekable for the first |aBufferSize| bytes.
+// Note that this object takes the ownership of the underlying stream.
+
+class PartiallySeekableInputStream final : public nsISeekableStream,
+ public nsIAsyncInputStream,
+ public nsICloneableInputStream,
+ public nsIIPCSerializableInputStream,
+ public nsIInputStreamCallback,
+ public nsIInputStreamLength,
+ public nsIAsyncInputStreamLength,
+ public nsIInputStreamLengthCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSITELLABLESTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSICLONEABLEINPUTSTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIINPUTSTREAMLENGTH
+ NS_DECL_NSIASYNCINPUTSTREAMLENGTH
+ NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK
+
+ explicit PartiallySeekableInputStream(
+ already_AddRefed<nsIInputStream> aInputStream,
+ uint64_t aBufferSize = 4096);
+
+ private:
+ PartiallySeekableInputStream(
+ already_AddRefed<nsIInputStream> aClonedBaseStream,
+ PartiallySeekableInputStream* aClonedFrom);
+
+ ~PartiallySeekableInputStream() = default;
+
+ void Init();
+
+ template <typename M>
+ void SerializeInternal(mozilla::ipc::InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors,
+ bool aDelayedStart, uint32_t aMaxSize,
+ uint32_t* aSizeUsed, M* aManager);
+
+ nsCOMPtr<nsIInputStream> mInputStream;
+
+ // Raw pointers because these are just QI of mInputStream.
+ nsICloneableInputStream* mWeakCloneableInputStream;
+ nsIIPCSerializableInputStream* mWeakIPCSerializableInputStream;
+ nsIAsyncInputStream* mWeakAsyncInputStream;
+ nsIInputStreamLength* mWeakInputStreamLength;
+ nsIAsyncInputStreamLength* mWeakAsyncInputStreamLength;
+
+ // Protected by mutex.
+ nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback;
+
+ // Protected by mutex.
+ nsCOMPtr<nsIInputStreamLengthCallback> mAsyncInputStreamLengthCallback;
+
+ nsTArray<char> mCachedBuffer;
+
+ uint64_t mBufferSize;
+ uint64_t mPos;
+ bool mClosed;
+
+ Mutex mMutex;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // PartiallySeekableInputStream_h
diff --git a/netwerk/base/PollableEvent.cpp b/netwerk/base/PollableEvent.cpp
new file mode 100644
index 0000000000..b483ee6cd0
--- /dev/null
+++ b/netwerk/base/PollableEvent.cpp
@@ -0,0 +1,401 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSocketTransportService2.h"
+#include "PollableEvent.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Logging.h"
+#include "prerror.h"
+#include "prio.h"
+#include "private/pprio.h"
+#include "prnetdb.h"
+
+#ifdef XP_WIN
+# include "ShutdownLayer.h"
+#else
+# include <fcntl.h>
+# define USEPIPE 1
+#endif
+
+namespace mozilla {
+namespace net {
+
+#ifndef USEPIPE
+static PRDescIdentity sPollableEventLayerIdentity;
+static PRIOMethods sPollableEventLayerMethods;
+static PRIOMethods* sPollableEventLayerMethodsPtr = nullptr;
+
+static void LazyInitSocket() {
+ MOZ_ASSERT(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()
+ : mWriteFD(nullptr),
+ mReadFD(nullptr),
+ mSignaled(false),
+ mWriteFailed(false),
+ mSignalTimestampAdjusted(false) {
+ 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..feb637f6cc
--- /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;
+ PRFileDesc* mReadFD;
+ bool mSignaled;
+ // true when PR_Write to the socket pair has failed (status < 1)
+ bool mWriteFailed;
+ // 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;
+ // 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..95aeef06f4
--- /dev/null
+++ b/netwerk/base/Predictor.cpp
@@ -0,0 +1,2445 @@
+/* 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;
+
+static const uint32_t STARTUP_WINDOW = 5U * 60U; // 5min
+
+// Version of metadata entries we expect
+static const uint32_t METADATA_VERSION = 1;
+
+// Flags available in entries
+// FLAG_PREFETCHABLE - we have determined that this item is eligible for
+// prefetch
+static const uint32_t FLAG_PREFETCHABLE = 1 << 0;
+
+// We save 12 bits in the "flags" section of our metadata for actual flags, the
+// rest are to keep track of a rolling count of which loads a resource has been
+// used on to determine if we can prefetch that resource or not;
+static const uint8_t kRollingLoadOffset = 12;
+static const int32_t kMaxPrefetchRollingLoadCount = 20;
+static const uint32_t kFlagsMask = ((1 << kRollingLoadOffset) - 1);
+
+// ID Extensions for cache entries
+#define PREDICTOR_ORIGIN_EXTENSION "predictor-origin"
+
+// Get the full origin (scheme, host, port) out of a URI (maybe should be part
+// of nsIURI instead?)
+static nsresult ExtractOrigin(nsIURI* uri, nsIURI** originUri) {
+ nsAutoCString s;
+ nsresult rv = nsContentUtils::GetASCIIOrigin(uri, s);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_NewURI(originUri, s);
+}
+
+// All URIs we get passed *must* be http or https if they're not null. This
+// helps ensure that.
+static bool IsNullOrHttp(nsIURI* uri) {
+ if (!uri) {
+ return true;
+ }
+
+ return uri->SchemeIs("http") || uri->SchemeIs("https");
+}
+
+// Listener for the speculative DNS requests we'll fire off, which just ignores
+// the result (since we're just trying to warm the cache). This also exists to
+// reduce round-trips to the main thread, by being something threadsafe the
+// Predictor can use.
+
+NS_IMPL_ISUPPORTS(Predictor::DNSListener, nsIDNSListener);
+
+NS_IMETHODIMP
+Predictor::DNSListener::OnLookupComplete(nsICancelable* request,
+ nsIDNSRecord* rec, nsresult status) {
+ return NS_OK;
+}
+
+// Class to proxy important information from the initial predictor call through
+// the cache API and back into the internals of the predictor. We can't use the
+// predictor itself, as it may have multiple actions in-flight, and each action
+// has different parameters.
+NS_IMPL_ISUPPORTS(Predictor::Action, nsICacheEntryOpenCallback);
+
+Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason,
+ nsIURI* targetURI, nsIURI* sourceURI,
+ nsINetworkPredictorVerifier* verifier,
+ Predictor* predictor)
+ : mFullUri(fullUri),
+ mPredict(predict),
+ mTargetURI(targetURI),
+ mSourceURI(sourceURI),
+ mVerifier(verifier),
+ mStackCount(0),
+ mPredictor(predictor) {
+ mStartTime = TimeStamp::Now();
+ if (mPredict) {
+ mPredictReason = reason.mPredict;
+ } else {
+ mLearnReason = reason.mLearn;
+ }
+}
+
+Predictor::Action::Action(bool fullUri, bool predict, Predictor::Reason reason,
+ nsIURI* targetURI, nsIURI* sourceURI,
+ nsINetworkPredictorVerifier* verifier,
+ Predictor* predictor, uint8_t stackCount)
+ : mFullUri(fullUri),
+ mPredict(predict),
+ mTargetURI(targetURI),
+ mSourceURI(sourceURI),
+ mVerifier(verifier),
+ mStackCount(stackCount),
+ mPredictor(predictor) {
+ mStartTime = TimeStamp::Now();
+ if (mPredict) {
+ mPredictReason = reason.mPredict;
+ } else {
+ mLearnReason = reason.mLearn;
+ }
+}
+
+NS_IMETHODIMP
+Predictor::Action::OnCacheEntryCheck(nsICacheEntry* entry,
+ nsIApplicationCache* appCache,
+ uint32_t* result) {
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Action::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
+ nsIApplicationCache* appCache,
+ nsresult result) {
+ MOZ_ASSERT(NS_IsMainThread(), "Got cache entry off main thread!");
+
+ nsAutoCString targetURI, sourceURI;
+ mTargetURI->GetAsciiSpec(targetURI);
+ if (mSourceURI) {
+ mSourceURI->GetAsciiSpec(sourceURI);
+ }
+ PREDICTOR_LOG(
+ ("OnCacheEntryAvailable %p called. entry=%p mFullUri=%d mPredict=%d "
+ "mPredictReason=%d mLearnReason=%d mTargetURI=%s "
+ "mSourceURI=%s mStackCount=%d isNew=%d result=0x%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()
+ : mInitialized(false),
+ mStartupTime(0),
+ mLastStartupTime(0),
+ mStartupCount(1) {
+ 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(nsISupports* aOuter, const nsIID& aIID,
+ void** aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ if (aOuter != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ RefPtr<Predictor> svc = new Predictor();
+ if (IsNeckoChild()) {
+ 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::HandleValue 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, false, 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);
+ if (verifier) {
+ PREDICTOR_LOG((" sending verification"));
+ verifier->OnPredictPreconnect(targetURI);
+ }
+}
+
+// This is the driver for prediction based on a new pageload.
+static const uint8_t MAX_PAGELOAD_DEPTH = 10;
+bool Predictor::PredictForPageload(nsICacheEntry* entry, nsIURI* targetURI,
+ uint8_t stackCount, bool fullUri,
+ nsINetworkPredictorVerifier* verifier) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ PREDICTOR_LOG(("Predictor::PredictForPageload"));
+
+ if (stackCount > MAX_PAGELOAD_DEPTH) {
+ PREDICTOR_LOG((" exceeded recursion depth!"));
+ return false;
+ }
+
+ uint32_t lastLoad;
+ nsresult rv = entry->GetLastFetched(&lastLoad);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ int32_t globalDegradation = CalculateGlobalDegradation(lastLoad);
+ PREDICTOR_LOG((" globalDegradation = %d", globalDegradation));
+
+ int32_t loadCount;
+ rv = entry->GetFetchCount(&loadCount);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<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, false, 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);
+ 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::HandleValue 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(TaskCategory::Other, runnable.forget());
+
+ return NS_OK;
+ }
+
+ PREDICTOR_LOG((" called on parent process"));
+
+ if (!mInitialized) {
+ PREDICTOR_LOG((" not initialized"));
+ return NS_OK;
+ }
+
+ if (!StaticPrefs::network_predictor_enabled()) {
+ PREDICTOR_LOG((" not enabled"));
+ return NS_OK;
+ }
+
+ if (originAttributes.mPrivateBrowsingId > 0) {
+ // Don't want to do anything in PB mode
+ PREDICTOR_LOG((" in PB mode"));
+ return NS_OK;
+ }
+
+ if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+ PREDICTOR_LOG((" got non-HTTP[S] URI"));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<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, false,
+ 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);
+
+ int32_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, static_cast<uint32_t>(loadCount));
+ }
+
+ // Update the rolling load count to mark this sub-resource as seen on the
+ // most-recent pageload so it can be eligible for prefetch (assuming all
+ // the other stars align).
+ flags |= (1 << kRollingLoadOffset);
+
+ nsCString newValue;
+ MakeMetadataEntry(hitCount, lastLoad, flags, newValue);
+ rv = entry->SetMetaDataElement(key.BeginReading(), newValue.BeginReading());
+ PREDICTOR_LOG(
+ (" SetMetaDataElement -> 0x%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,
+ nsIApplicationCache* appCache,
+ uint32_t* result) {
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Predictor::Resetter::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
+ nsIApplicationCache* appCache,
+ nsresult result) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_FAILED(result)) {
+ // This can happen when we've tried to open an entry that doesn't exist for
+ // some non-reset operation, and then get reset shortly thereafter (as
+ // happens in some of our tests).
+ --mEntriesToVisit;
+ if (!mEntriesToVisit) {
+ Complete();
+ }
+ return NS_OK;
+ }
+
+ entry->VisitMetaData(this);
+ nsTArray<nsCString> keysToDelete = 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, int32_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, false, 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], false, getter_AddRefs(cacheDiskStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ urisToVisit[i]->GetAsciiSpec(u);
+ cacheDiskStorage->AsyncOpenURI(urisToVisit[i], ""_ns,
+ nsICacheStorage::OPEN_READONLY |
+ nsICacheStorage::OPEN_SECRETLY |
+ nsICacheStorage::CHECK_MULTITHREADED,
+ this);
+ }
+
+ return NS_OK;
+}
+
+void Predictor::Resetter::Complete() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ PREDICTOR_LOG(("COULD NOT GET OBSERVER SERVICE!"));
+ return;
+ }
+
+ obs->NotifyObservers(nullptr, "predictor-reset-complete", nullptr);
+}
+
+// Helper functions to make using the predictor easier from native code
+
+static 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);
+ 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, false,
+ 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,
+ nsIApplicationCache* appCache,
+ 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, nsIApplicationCache* appCache,
+ nsresult result) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // This is being opened read-only, so isNew should always be false
+ MOZ_ASSERT(!isNew);
+
+ PREDICTOR_LOG(("CacheabilityAction::OnCacheEntryAvailable this=%p", this));
+ if (NS_FAILED(result)) {
+ // Nothing to do
+ PREDICTOR_LOG((" nothing to do result=%" 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..5cdbba9294
--- /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(nsISupports* outer, const nsIID& iid, void** result);
+
+ // Used to update whether a particular URI was cacheable or not.
+ // sourceURI and targetURI are the same as the arguments to Learn
+ // and httpStatus is the status code we got while loading targetURI.
+ static void UpdateCacheability(nsIURI* sourceURI, nsIURI* targetURI,
+ uint32_t httpStatus,
+ nsHttpRequestHead& requestHead,
+ nsHttpResponseHead* reqponseHead,
+ nsILoadContextInfo* lci, 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;
+
+ nsTArray<nsCString> mKeysToOperateOn;
+ nsTArray<nsCString> mValuesToOperateOn;
+
+ nsCOMPtr<nsICacheStorageService> mCacheStorageService;
+
+ nsCOMPtr<nsISpeculativeConnect> mSpeculativeService;
+
+ nsCOMPtr<nsIURI> mStartupURI;
+ uint32_t mStartupTime;
+ uint32_t mLastStartupTime;
+ int32_t mStartupCount;
+
+ 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/ProxyAutoConfig.cpp b/netwerk/base/ProxyAutoConfig.cpp
new file mode 100644
index 0000000000..6635d89de8
--- /dev/null
+++ b/netwerk/base/ProxyAutoConfig.cpp
@@ -0,0 +1,1048 @@
+/* -*- 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/CompilationAndEvaluation.h" // JS::Compile
+#include "js/ContextOptions.h"
+#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/net/DNS.h"
+#include "mozilla/Utf8.h" // mozilla::Utf8Unit
+#include "nsServiceManagerUtils.h"
+#include "nsNetCID.h"
+
+#if defined(XP_MACOSX)
+# include "nsMacUtilsImpl.h"
+#endif
+
+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[] =
+ "function dnsDomainIs(host, domain) {\n"
+ " return (host.length >= domain.length &&\n"
+ " host.substring(host.length - domain.length) == domain);\n"
+ "}\n"
+ ""
+ "function dnsDomainLevels(host) {\n"
+ " return host.split('.').length - 1;\n"
+ "}\n"
+ ""
+ "function isValidIpAddress(ipchars) {\n"
+ " var matches = "
+ "/^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipchars);\n"
+ " if (matches == null) {\n"
+ " return false;\n"
+ " } else if (matches[1] > 255 || matches[2] > 255 || \n"
+ " matches[3] > 255 || matches[4] > 255) {\n"
+ " return false;\n"
+ " }\n"
+ " return true;\n"
+ "}\n"
+ ""
+ "function convert_addr(ipchars) {\n"
+ " var bytes = ipchars.split('.');\n"
+ " var result = ((bytes[0] & 0xff) << 24) |\n"
+ " ((bytes[1] & 0xff) << 16) |\n"
+ " ((bytes[2] & 0xff) << 8) |\n"
+ " (bytes[3] & 0xff);\n"
+ " return result;\n"
+ "}\n"
+ ""
+ "function isInNet(ipaddr, pattern, maskstr) {\n"
+ " if (!isValidIpAddress(pattern) || !isValidIpAddress(maskstr)) {\n"
+ " return false;\n"
+ " }\n"
+ " if (!isValidIpAddress(ipaddr)) {\n"
+ " ipaddr = dnsResolve(ipaddr);\n"
+ " if (ipaddr == null) {\n"
+ " return false;\n"
+ " }\n"
+ " }\n"
+ " var host = convert_addr(ipaddr);\n"
+ " var pat = convert_addr(pattern);\n"
+ " var mask = convert_addr(maskstr);\n"
+ " return ((host & mask) == (pat & mask));\n"
+ " \n"
+ "}\n"
+ ""
+ "function isPlainHostName(host) {\n"
+ " return (host.search('\\\\.') == -1);\n"
+ "}\n"
+ ""
+ "function isResolvable(host) {\n"
+ " var ip = dnsResolve(host);\n"
+ " return (ip != null);\n"
+ "}\n"
+ ""
+ "function localHostOrDomainIs(host, hostdom) {\n"
+ " return (host == hostdom) ||\n"
+ " (hostdom.lastIndexOf(host + '.', 0) == 0);\n"
+ "}\n"
+ ""
+ "function shExpMatch(url, pattern) {\n"
+ " pattern = pattern.replace(/\\./g, '\\\\.');\n"
+ " pattern = pattern.replace(/\\*/g, '.*');\n"
+ " pattern = pattern.replace(/\\?/g, '.');\n"
+ " var newRe = new RegExp('^'+pattern+'$');\n"
+ " return newRe.test(url);\n"
+ "}\n"
+ ""
+ "var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n"
+ "var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, "
+ "AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11};\n"
+ ""
+ "function weekdayRange() {\n"
+ " function getDay(weekday) {\n"
+ " if (weekday in wdays) {\n"
+ " return wdays[weekday];\n"
+ " }\n"
+ " return -1;\n"
+ " }\n"
+ " var date = new Date();\n"
+ " var argc = arguments.length;\n"
+ " var wday;\n"
+ " if (argc < 1)\n"
+ " return false;\n"
+ " if (arguments[argc - 1] == 'GMT') {\n"
+ " argc--;\n"
+ " wday = date.getUTCDay();\n"
+ " } else {\n"
+ " wday = date.getDay();\n"
+ " }\n"
+ " var wd1 = getDay(arguments[0]);\n"
+ " var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n"
+ " return (wd1 == -1 || wd2 == -1) ? false\n"
+ " : (wd1 <= wd2) ? (wd1 <= wday && wday "
+ "<= wd2)\n"
+ " : (wd2 >= wday || wday "
+ ">= wd1);\n"
+ "}\n"
+ ""
+ "function dateRange() {\n"
+ " function getMonth(name) {\n"
+ " if (name in months) {\n"
+ " return months[name];\n"
+ " }\n"
+ " return -1;\n"
+ " }\n"
+ " var date = new Date();\n"
+ " var argc = arguments.length;\n"
+ " if (argc < 1) {\n"
+ " return false;\n"
+ " }\n"
+ " var isGMT = (arguments[argc - 1] == 'GMT');\n"
+ "\n"
+ " if (isGMT) {\n"
+ " argc--;\n"
+ " }\n"
+ " // function will work even without explict handling of this case\n"
+ " if (argc == 1) {\n"
+ " var tmp = parseInt(arguments[0]);\n"
+ " if (isNaN(tmp)) {\n"
+ " return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==\n"
+ " getMonth(arguments[0]));\n"
+ " } else if (tmp < 32) {\n"
+ " return ((isGMT ? date.getUTCDate() : date.getDate()) == "
+ "tmp);\n"
+ " } else { \n"
+ " return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) "
+ "==\n"
+ " tmp);\n"
+ " }\n"
+ " }\n"
+ " var year = date.getFullYear();\n"
+ " var date1, date2;\n"
+ " date1 = new Date(year, 0, 1, 0, 0, 0);\n"
+ " date2 = new Date(year, 11, 31, 23, 59, 59);\n"
+ " var adjustMonth = false;\n"
+ " for (var i = 0; i < (argc >> 1); i++) {\n"
+ " var tmp = parseInt(arguments[i]);\n"
+ " if (isNaN(tmp)) {\n"
+ " var mon = getMonth(arguments[i]);\n"
+ " date1.setMonth(mon);\n"
+ " } else if (tmp < 32) {\n"
+ " adjustMonth = (argc <= 2);\n"
+ " date1.setDate(tmp);\n"
+ " } else {\n"
+ " date1.setFullYear(tmp);\n"
+ " }\n"
+ " }\n"
+ " for (var i = (argc >> 1); i < argc; i++) {\n"
+ " var tmp = parseInt(arguments[i]);\n"
+ " if (isNaN(tmp)) {\n"
+ " var mon = getMonth(arguments[i]);\n"
+ " date2.setMonth(mon);\n"
+ " } else if (tmp < 32) {\n"
+ " date2.setDate(tmp);\n"
+ " } else {\n"
+ " date2.setFullYear(tmp);\n"
+ " }\n"
+ " }\n"
+ " if (adjustMonth) {\n"
+ " date1.setMonth(date.getMonth());\n"
+ " date2.setMonth(date.getMonth());\n"
+ " }\n"
+ " if (isGMT) {\n"
+ " var tmp = date;\n"
+ " tmp.setFullYear(date.getUTCFullYear());\n"
+ " tmp.setMonth(date.getUTCMonth());\n"
+ " tmp.setDate(date.getUTCDate());\n"
+ " tmp.setHours(date.getUTCHours());\n"
+ " tmp.setMinutes(date.getUTCMinutes());\n"
+ " tmp.setSeconds(date.getUTCSeconds());\n"
+ " date = tmp;\n"
+ " }\n"
+ " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n"
+ " : (date2 >= date) || (date >= date1);\n"
+ "}\n"
+ ""
+ "function timeRange() {\n"
+ " var argc = arguments.length;\n"
+ " var date = new Date();\n"
+ " var isGMT= false;\n"
+ ""
+ " if (argc < 1) {\n"
+ " return false;\n"
+ " }\n"
+ " if (arguments[argc - 1] == 'GMT') {\n"
+ " isGMT = true;\n"
+ " argc--;\n"
+ " }\n"
+ "\n"
+ " var hour = isGMT ? date.getUTCHours() : date.getHours();\n"
+ " var date1, date2;\n"
+ " date1 = new Date();\n"
+ " date2 = new Date();\n"
+ "\n"
+ " if (argc == 1) {\n"
+ " return (hour == arguments[0]);\n"
+ " } else if (argc == 2) {\n"
+ " return ((arguments[0] <= hour) && (hour <= arguments[1]));\n"
+ " } else {\n"
+ " switch (argc) {\n"
+ " case 6:\n"
+ " date1.setSeconds(arguments[2]);\n"
+ " date2.setSeconds(arguments[5]);\n"
+ " case 4:\n"
+ " var middle = argc >> 1;\n"
+ " date1.setHours(arguments[0]);\n"
+ " date1.setMinutes(arguments[1]);\n"
+ " date2.setHours(arguments[middle]);\n"
+ " date2.setMinutes(arguments[middle + 1]);\n"
+ " if (middle == 2) {\n"
+ " date2.setSeconds(59);\n"
+ " }\n"
+ " break;\n"
+ " default:\n"
+ " throw 'timeRange: bad number of arguments'\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " if (isGMT) {\n"
+ " date.setFullYear(date.getUTCFullYear());\n"
+ " date.setMonth(date.getUTCMonth());\n"
+ " date.setDate(date.getUTCDate());\n"
+ " date.setHours(date.getUTCHours());\n"
+ " date.setMinutes(date.getUTCMinutes());\n"
+ " date.setSeconds(date.getUTCSeconds());\n"
+ " }\n"
+ " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n"
+ " : (date2 >= date) || (date >= date1);\n"
+ "\n"
+ "}\n"
+ "";
+
+// sRunning is defined for the helper functions only while the
+// Javascript engine is running and the PAC object cannot be deleted
+// or reset.
+static 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);
+ 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;
+
+ private:
+ ~PACResolver() = default;
+};
+NS_IMPL_ISUPPORTS(PACResolver, nsIDNSListener, nsITimerCallback, nsINamed)
+
+static void PACLogToConsole(nsString& aMessage) {
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (!consoleService) return;
+
+ consoleService->LogStringMessage(aMessage.get());
+}
+
+// Javascript errors and warnings are logged to the main error console
+static void PACLogErrorOrWarning(const nsAString& aKind,
+ JSErrorReport* aReport) {
+ nsString formattedMessage(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 nsCString& aHostName, NetAddr* aNetAddr,
+ unsigned int aTimeout) {
+ if (!GetRunning()) {
+ NS_WARNING("PACResolve without a running ProxyAutoConfig object");
+ return false;
+ }
+
+ return GetRunning()->ResolveAddress(aHostName, aNetAddr, aTimeout);
+}
+
+ProxyAutoConfig::ProxyAutoConfig()
+ : mJSContext(nullptr),
+ mJSNeedsSetup(false),
+ mShutdown(true),
+ mIncludePath(false),
+ mExtraHeapSize(0) {
+ MOZ_COUNT_CTOR(ProxyAutoConfig);
+}
+
+bool ProxyAutoConfig::ResolveAddress(const nsCString& aHostName,
+ NetAddr* aNetAddr, unsigned int aTimeout) {
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns) return false;
+
+ RefPtr<PACResolver> helper = new PACResolver(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.
+ uint32_t flags =
+ nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+ nsIDNSService::GetFlagsFromTRRMode(nsIRequest::TRR_DISABLED_MODE);
+
+ if (NS_FAILED(dns->AsyncResolveNative(
+ aHostName, nsIDNSService::RESOLVE_TYPE_DEFAULT, flags, nullptr,
+ helper, GetCurrentEventTarget(), 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([&, 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);
+ if (!rec || NS_FAILED(rec->GetNextAddr(0, aNetAddr))) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool PACResolveToString(const nsCString& aHostName,
+ nsCString& aDottedDecimal,
+ unsigned int aTimeout) {
+ NetAddr netAddr;
+ if (!PACResolve(aHostName, &netAddr, aTimeout)) return false;
+
+ char dottedDecimal[128];
+ if (!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;
+
+ JS::Rooted<JSString*> arg1(cx, JS::ToString(cx, args[0]));
+ if (!arg1) return false;
+
+ nsAutoJSString hostName;
+ nsAutoCString dottedDecimal;
+
+ if (!hostName.init(cx, arg1)) return false;
+ if (PACResolveToString(NS_ConvertUTF16toUTF8(hostName), dottedDecimal, 0)) {
+ JSString* dottedDecimalString = JS_NewStringCopyZ(cx, dottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ args.rval().setString(dottedDecimalString);
+ } else {
+ args.rval().setNull();
+ }
+
+ return true;
+}
+
+// myIpAddress() javascript implementation
+static bool PACMyIpAddress(JSContext* cx, unsigned int argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ if (NS_IsMainThread()) {
+ NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?");
+ return false;
+ }
+
+ if (!GetRunning()) {
+ NS_WARNING("PAC myIPAddress without a running ProxyAutoConfig object");
+ return false;
+ }
+
+ return GetRunning()->MyIPAddress(args);
+}
+
+// proxyAlert(msg) javascript implementation
+static bool PACProxyAlert(JSContext* cx, unsigned int argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "alert", 1)) return false;
+
+ JS::Rooted<JSString*> arg1(cx, JS::ToString(cx, args[0]));
+ if (!arg1) return false;
+
+ nsAutoJSString message;
+ if (!message.init(cx, arg1)) return false;
+
+ nsAutoString alertMessage;
+ alertMessage.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);
+
+ if (!JS::InitSelfHostedCode(mContext)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ JS::RealmOptions options;
+ options.creationOptions().setNewCompartmentInSystemZone();
+ options.behaviors().setClampAndJitterTime(false);
+ mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr,
+ JS::DontFireOnNewGlobalHook, options);
+ if (!mGlobal) {
+ JS_ClearPendingException(mContext);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ JS::Rooted<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::Init(const nsCString& aPACURI,
+ const nsCString& aPACScriptData,
+ bool aIncludePath, uint32_t aExtraHeapSize,
+ nsIEventTarget* 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;
+ mConcatenatedPACData.Append(aPACScriptData);
+
+ mIncludePath = aIncludePath;
+ mExtraHeapSize = aExtraHeapSize;
+ mMainThreadEventTarget = aEventTarget;
+
+ if (!GetRunning()) return SetupJS();
+
+ mJSNeedsSetup = true;
+ return NS_OK;
+}
+
+nsresult ProxyAutoConfig::SetupJS() {
+ mJSNeedsSetup = false;
+ MOZ_ASSERT(!GetRunning(), "JIT is running");
+
+#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;
+}
+
+nsresult ProxyAutoConfig::GetProxyForURI(const nsCString& aTestURI,
+ const nsCString& aTestHost,
+ nsACString& result) {
+ if (mJSNeedsSetup) SetupJS();
+
+ if (!mJSContext || !mJSContext->IsOK()) return NS_ERROR_NOT_AVAILABLE;
+
+ JSContext* cx = mJSContext->Context();
+ 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.get(), aTestURI.Length(), &schemePos,
+ &schemeLen, &authorityPos, &authorityLen,
+ &pathPos, &pathLen);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ if (pathLen) {
+ // cut off the path but leave the initial slash
+ pathLen--;
+ }
+ aTestURI.Left(clensedURI, aTestURI.Length() - pathLen);
+ }
+ }
+
+ JS::RootedString uriString(cx, JS_NewStringCopyZ(cx, clensedURI.get()));
+ JS::RootedString hostString(cx, JS_NewStringCopyZ(cx, aTestHost.get()));
+
+ if (uriString && hostString) {
+ JS::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 nsCString& hostName,
+ unsigned int timeout,
+ const JS::CallArgs& aArgs,
+ bool* aResult) {
+ *aResult = false;
+
+ NetAddr remoteAddress;
+ nsAutoCString localDottedDecimal;
+ JSContext* cx = mJSContext->Context();
+
+ if (PACResolve(hostName, &remoteAddress, timeout) &&
+ SrcAddress(&remoteAddress, localDottedDecimal)) {
+ JSString* dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ *aResult = true;
+ aArgs.rval().setString(dottedDecimalString);
+ }
+ return true;
+}
+
+bool ProxyAutoConfig::MyIPAddress(const JS::CallArgs& aArgs) {
+ nsAutoCString remoteDottedDecimal;
+ nsAutoCString localDottedDecimal;
+ JSContext* cx = mJSContext->Context();
+ JS::RootedValue v(cx);
+ JS::Rooted<JSObject*> global(cx, mJSContext->Global());
+
+ bool useMultihomedDNS =
+ JS_GetProperty(cx, global, "pacUseMultihomedDNS", &v) &&
+ !v.isUndefined() && ToBoolean(v);
+
+ // first, lookup the local address of a socket connected
+ // to the host of uri being resolved by the pac file. This is
+ // v6 safe.. but is the last step like that
+ bool rvalAssigned = false;
+ if (useMultihomedDNS) {
+ if (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+ } else {
+ // we can still do the fancy multi homing thing if the host is a literal
+ PRNetAddr tempAddr;
+ memset(&tempAddr, 0, sizeof(PRNetAddr));
+ if ((PR_StringToNetAddr(mRunningHost.get(), &tempAddr) == PR_SUCCESS) &&
+ (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) ||
+ rvalAssigned)) {
+ return rvalAssigned;
+ }
+ }
+
+ // next, look for a route to a public internet address that doesn't need DNS.
+ // This is the google anycast dns address, but it doesn't matter if it
+ // remains operable (as we don't contact it) as long as the address stays
+ // in commonly routed IP address space.
+ remoteDottedDecimal.AssignLiteral("8.8.8.8");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // finally, use the old algorithm based on the local hostname
+ nsAutoCString hostName;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ // without multihomedDNS use such a short timeout that we are basically
+ // just looking at the cache for raw dotted decimals
+ uint32_t timeout = useMultihomedDNS ? kTimeout : 1;
+ if (dns && NS_SUCCEEDED(dns->GetMyHostName(hostName)) &&
+ PACResolveToString(hostName, localDottedDecimal, timeout)) {
+ JSString* dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ aArgs.rval().setString(dottedDecimalString);
+ return true;
+ }
+
+ // next try a couple RFC 1918 variants.. maybe there is a
+ // local route
+ remoteDottedDecimal.AssignLiteral("192.168.0.1");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // more RFC 1918
+ remoteDottedDecimal.AssignLiteral("10.0.0.1");
+ if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) ||
+ rvalAssigned) {
+ return rvalAssigned;
+ }
+
+ // who knows? let's fallback to localhost
+ localDottedDecimal.AssignLiteral("127.0.0.1");
+ JSString* dottedDecimalString =
+ JS_NewStringCopyZ(cx, localDottedDecimal.get());
+ if (!dottedDecimalString) {
+ return false;
+ }
+
+ aArgs.rval().setString(dottedDecimalString);
+ return true;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/ProxyAutoConfig.h b/netwerk/base/ProxyAutoConfig.h
new file mode 100644
index 0000000000..33f7c8c85d
--- /dev/null
+++ b/netwerk/base/ProxyAutoConfig.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ProxyAutoConfig_h__
+#define ProxyAutoConfig_h__
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+class nsIEventTarget;
+class nsITimer;
+namespace JS {
+class CallArgs;
+} // namespace JS
+
+namespace mozilla {
+namespace net {
+
+class JSContextWrapper;
+union NetAddr;
+
+// The ProxyAutoConfig class is meant to be created and run on a
+// non main thread. It synchronously resolves PAC files by blocking that
+// thread and running nested event loops. GetProxyForURI is not re-entrant.
+
+class ProxyAutoConfig {
+ public:
+ ProxyAutoConfig();
+ ~ProxyAutoConfig();
+
+ nsresult Init(const nsCString& aPACURI, const nsCString& aPACScriptData,
+ bool aIncludePath, uint32_t aExtraHeapSize,
+ nsIEventTarget* aEventTarget);
+ void SetThreadLocalIndex(uint32_t index);
+ void Shutdown();
+ void GC();
+ bool MyIPAddress(const JS::CallArgs& aArgs);
+ bool ResolveAddress(const nsCString& aHostName, NetAddr* aNetAddr,
+ unsigned int aTimeout);
+
+ /**
+ * Get the proxy string for the specified URI. The proxy string is
+ * given by the following:
+ *
+ * result = proxy-spec *( proxy-sep proxy-spec )
+ * proxy-spec = direct-type | proxy-type LWS proxy-host [":" proxy-port]
+ * direct-type = "DIRECT"
+ * proxy-type = "PROXY" | "HTTP" | "HTTPS" | "SOCKS" | "SOCKS4" | "SOCKS5"
+ * proxy-sep = ";" LWS
+ * proxy-host = hostname | ipv4-address-literal
+ * proxy-port = <any 16-bit unsigned integer>
+ * LWS = *( SP | HT )
+ * SP = <US-ASCII SP, space (32)>
+ * HT = <US-ASCII HT, horizontal-tab (9)>
+ *
+ * NOTE: direct-type and proxy-type are case insensitive
+ * NOTE: SOCKS implies SOCKS4
+ *
+ * Examples:
+ * "PROXY proxy1.foo.com:8080; PROXY proxy2.foo.com:8080; DIRECT"
+ * "SOCKS socksproxy"
+ * "DIRECT"
+ *
+ * XXX add support for IPv6 address literals.
+ * XXX quote whatever the official standard is for PAC.
+ *
+ * @param aTestURI
+ * The URI as an ASCII string to test.
+ * @param aTestHost
+ * The ASCII hostname to test.
+ *
+ * @param result
+ * result string as defined above.
+ */
+ nsresult GetProxyForURI(const nsCString& aTestURI, const nsCString& aTestHost,
+ nsACString& result);
+
+ private:
+ // allow 665ms for myipaddress dns queries. That's 95th percentile.
+ const static unsigned int kTimeout = 665;
+
+ // used to compile the PAC file and setup the execution context
+ nsresult SetupJS();
+
+ bool SrcAddress(const NetAddr* remoteAddress, nsCString& localAddress);
+ bool MyIPAddressTryHost(const nsCString& hostName, unsigned int timeout,
+ const JS::CallArgs& aArgs, bool* aResult);
+
+ JSContextWrapper* mJSContext;
+ bool mJSNeedsSetup;
+ bool mShutdown;
+ nsCString mConcatenatedPACData;
+ nsCString mPACURI;
+ bool mIncludePath;
+ uint32_t mExtraHeapSize;
+ nsCString mRunningHost;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // ProxyAutoConfig_h__
diff --git a/netwerk/base/RedirectChannelRegistrar.cpp b/netwerk/base/RedirectChannelRegistrar.cpp
new file mode 100644
index 0000000000..44914f9c45
--- /dev/null
+++ b/netwerk/base/RedirectChannelRegistrar.cpp
@@ -0,0 +1,89 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/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();
+ }
+ return do_AddRef(gSingleton);
+}
+
+// static
+void RedirectChannelRegistrar::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ gSingleton = nullptr;
+}
+
+NS_IMETHODIMP
+RedirectChannelRegistrar::RegisterChannel(nsIChannel* channel, uint64_t id) {
+ MutexAutoLock lock(mLock);
+
+ mRealChannels.Put(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.Put(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..a651e40650
--- /dev/null
+++ b/netwerk/base/RedirectChannelRegistrar.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 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();
+
+ // Cleanup the singleton instance on shutdown
+ static void Shutdown();
+
+ protected:
+ using ChannelHashtable = nsInterfaceHashtable<nsUint64HashKey, nsIChannel>;
+ using ParentChannelHashtable =
+ nsInterfaceHashtable<nsUint64HashKey, nsIParentChannel>;
+
+ ChannelHashtable mRealChannels;
+ ParentChannelHashtable mParentChannels;
+ Mutex mLock;
+
+ 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..ae44049c60
--- /dev/null
+++ b/netwerk/base/RequestContextService.cpp
@@ -0,0 +1,610 @@
+/* -*- 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 "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:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTCONTEXT
+ NS_DECL_NSITIMERCALLBACK
+
+ 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;
+
+ typedef nsCOMPtr<nsIRequestTailUnblockCallback> PendingTailRequest;
+ // 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)
+
+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();
+ mUntailTimer = do_CreateInstance("@mozilla.org/timer;1");
+ mUntailTimer->InitWithCallback(this, delay, nsITimer::TYPE_ONE_SHOT);
+
+ 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::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()
+ : mRCIDNamespace(0), mNextRCID(1) {
+ 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 (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
+ iter.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;
+ }
+
+ if (!mTable.Get(rcID, rc)) {
+ nsCOMPtr<nsIRequestContext> newSC = new RequestContext(rcID);
+ mTable.Put(rcID, newSC);
+ newSC.swap(*rc);
+ }
+
+ 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.Put(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..e862969ff1
--- /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;
+ uint32_t mNextRCID;
+};
+
+} // 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..46d3a3bace
--- /dev/null
+++ b/netwerk/base/SSLTokensCache.cpp
@@ -0,0 +1,414 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "mozilla/ArrayAlgorithm.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Logging.h"
+#include "nsIOService.h"
+#include "nsNSSIOLayer.h"
+#include "TransportSecurityInfo.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)
+
+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();
+ return result;
+}
+
+StaticRefPtr<SSLTokensCache> SSLTokensCache::gInstance;
+StaticMutex SSLTokensCache::sLock;
+
+uint32_t SSLTokensCache::TokenCacheRecord::Size() const {
+ uint32_t size = mToken.Length() + sizeof(mSessionCacheInfo.mEVStatus) +
+ sizeof(mSessionCacheInfo.mCertificateTransparencyStatus) +
+ mSessionCacheInfo.mServerCertBytes.Length();
+ if (mSessionCacheInfo.mSucceededCertChainBytes) {
+ for (const auto& cert : mSessionCacheInfo.mSucceededCertChainBytes.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();
+}
+
+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() : mCacheSize(0) {
+ LOG(("SSLTokensCache::SSLTokensCache"));
+}
+
+SSLTokensCache::~SSLTokensCache() { LOG(("SSLTokensCache::~SSLTokensCache")); }
+
+// static
+nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
+ uint32_t aTokenLen,
+ nsITransportSecurityInfo* aSecInfo) {
+ 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, aSecInfo, expirationTime);
+}
+
+// static
+nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
+ uint32_t aTokenLen,
+ nsITransportSecurityInfo* aSecInfo,
+ 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 (!aSecInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIX509Cert> cert;
+ aSecInfo->GetServerCert(getter_AddRefs(cert));
+ if (!cert) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsTArray<uint8_t> certBytes;
+ nsresult rv = cert->GetRawDER(certBytes);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Maybe<nsTArray<nsTArray<uint8_t>>> succeededCertChainBytes;
+ nsTArray<RefPtr<nsIX509Cert>> succeededCertArray;
+ rv = aSecInfo->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 = aSecInfo->GetIsBuiltCertChainRootBuiltInRoot(&builtInRoot);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ isBuiltCertChainRootBuiltInRoot.emplace(builtInRoot);
+ }
+
+ bool isEV;
+ rv = aSecInfo->GetIsExtendedValidation(&isEV);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint16_t certificateTransparencyStatus;
+ rv = aSecInfo->GetCertificateTransparencyStatus(
+ &certificateTransparencyStatus);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ TokenCacheRecord* rec = nullptr;
+
+ if (!gInstance->mTokenCacheRecords.Get(aKey, &rec)) {
+ rec = new TokenCacheRecord();
+ rec->mKey = aKey;
+ gInstance->mTokenCacheRecords.Put(aKey, rec);
+ gInstance->mExpirationArray.AppendElement(rec);
+ } else {
+ gInstance->mCacheSize -= rec->Size();
+ rec->Reset();
+ }
+
+ rec->mExpirationTime = aExpirationTime;
+ MOZ_ASSERT(rec->mToken.IsEmpty());
+ rec->mToken.AppendElements(aToken, aTokenLen);
+
+ rec->mSessionCacheInfo.mServerCertBytes = std::move(certBytes);
+
+ rec->mSessionCacheInfo.mSucceededCertChainBytes =
+ succeededCertChainBytes
+ ? Some(TransformIntoNewArray(
+ *succeededCertChainBytes,
+ [](auto& element) { return nsTArray(std::move(element)); }))
+ : Nothing();
+
+ if (isEV) {
+ rec->mSessionCacheInfo.mEVStatus = psm::EVStatus::EV;
+ }
+
+ rec->mSessionCacheInfo.mCertificateTransparencyStatus =
+ certificateTransparencyStatus;
+
+ rec->mSessionCacheInfo.mIsBuiltCertChainRootBuiltInRoot =
+ std::move(isBuiltCertChainRootBuiltInRoot);
+
+ gInstance->mCacheSize += rec->Size();
+
+ gInstance->LogStats();
+
+ gInstance->EvictIfNecessary();
+
+ return NS_OK;
+}
+
+// static
+nsresult SSLTokensCache::Get(const nsACString& aKey,
+ nsTArray<uint8_t>& aToken) {
+ StaticMutexAutoLock lock(sLock);
+
+ LOG(("SSLTokensCache::Get [key=%s]", PromiseFlatCString(aKey).get()));
+
+ if (!gInstance) {
+ LOG((" service not initialized"));
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ TokenCacheRecord* rec = nullptr;
+
+ if (gInstance->mTokenCacheRecords.Get(aKey, &rec)) {
+ if (rec->mToken.Length()) {
+ aToken = rec->mToken.Clone();
+ return NS_OK;
+ }
+ }
+
+ LOG((" token not found"));
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+// static
+bool SSLTokensCache::GetSessionCacheInfo(const nsACString& aKey,
+ SessionCacheInfo& aResult) {
+ StaticMutexAutoLock lock(sLock);
+
+ LOG(("SSLTokensCache::GetSessionCacheInfo [key=%s]",
+ PromiseFlatCString(aKey).get()));
+
+ if (!gInstance) {
+ LOG((" service not initialized"));
+ return false;
+ }
+
+ TokenCacheRecord* rec = nullptr;
+
+ if (gInstance->mTokenCacheRecords.Get(aKey, &rec)) {
+ aResult = rec->mSessionCacheInfo.Clone();
+ return true;
+ }
+
+ LOG((" token not found"));
+ return false;
+}
+
+// static
+nsresult SSLTokensCache::Remove(const nsACString& aKey) {
+ 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);
+}
+
+nsresult SSLTokensCache::RemoveLocked(const nsACString& aKey) {
+ sLock.AssertCurrentThreadOwns();
+
+ LOG(("SSLTokensCache::RemoveLocked [key=%s]",
+ PromiseFlatCString(aKey).get()));
+
+ UniquePtr<TokenCacheRecord> rec;
+
+ if (!mTokenCacheRecords.Remove(aKey, &rec)) {
+ LOG((" token not found"));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mCacheSize -= rec->Size();
+
+ if (!mExpirationArray.RemoveElement(rec.get())) {
+ MOZ_ASSERT(false, "token not found in mExpirationArray");
+ }
+
+ LogStats();
+
+ return NS_OK;
+}
+
+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) {
+ if (NS_FAILED(RemoveLocked(mExpirationArray[0]->mKey))) {
+ MOZ_ASSERT(false,
+ "mExpirationArray and mTokenCacheRecords are out of sync!");
+ mExpirationArray.RemoveElementAt(0);
+ }
+ }
+}
+
+void SSLTokensCache::LogStats() {
+ LOG(("SSLTokensCache::LogStats [count=%zu, cacheSize=%u]",
+ mExpirationArray.Length(), mCacheSize));
+}
+
+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"));
+ if (!StaticPrefs::network_ssl_tokens_cache_enabled()) {
+ return;
+ }
+
+ 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..9ccff1b424
--- /dev/null
+++ b/netwerk/base/SSLTokensCache.h
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SSLTokensCache_h_
+#define SSLTokensCache_h_
+
+#include "nsIMemoryReporter.h"
+#include "nsClassHashtable.h"
+#include "nsTArray.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPtr.h"
+#include "nsXULAppAPI.h"
+#include "TransportSecurityInfo.h" // For EVStatus
+
+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;
+};
+
+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, nsITransportSecurityInfo* aSecInfo);
+ static nsresult Put(const nsACString& aKey, const uint8_t* aToken,
+ uint32_t aTokenLen, nsITransportSecurityInfo* aSecInfo,
+ PRUint32 aExpirationTime);
+ static nsresult Get(const nsACString& aKey, nsTArray<uint8_t>& aToken);
+ static bool GetSessionCacheInfo(const nsACString& aKey,
+ SessionCacheInfo& aResult);
+ static nsresult Remove(const nsACString& aKey);
+ static void Clear();
+
+ private:
+ SSLTokensCache();
+ virtual ~SSLTokensCache();
+
+ nsresult RemoveLocked(const nsACString& aKey);
+
+ void EvictIfNecessary();
+ void LogStats();
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ static mozilla::StaticRefPtr<SSLTokensCache> gInstance;
+ static StaticMutex sLock;
+
+ uint32_t mCacheSize; // Actual cache size in bytes
+
+ class TokenCacheRecord {
+ public:
+ uint32_t Size() const;
+ void Reset();
+
+ nsCString mKey;
+ PRUint32 mExpirationTime;
+ nsTArray<uint8_t> mToken;
+ SessionCacheInfo mSessionCacheInfo;
+ };
+
+ nsClassHashtable<nsCStringHashKey, TokenCacheRecord> 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..1791a34b59
--- /dev/null
+++ b/netwerk/base/SimpleBuffer.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SimpleBuffer.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace net {
+
+SimpleBuffer::SimpleBuffer() : mStatus(NS_OK), mAvailable(0) {}
+
+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..c518d1b341
--- /dev/null
+++ b/netwerk/base/SimpleBuffer.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SimpleBuffer_h__
+#define SimpleBuffer_h__
+
+/*
+ This class is similar to a nsPipe except it does not have any locking, stores
+ an unbounded amount of data, can only be used on one thread, and has much
+ simpler result code semantics to deal with.
+*/
+
+#include "prtypes.h"
+#include "mozilla/LinkedList.h"
+
+namespace mozilla {
+namespace net {
+
+class SimpleBufferPage : public LinkedListElement<SimpleBufferPage> {
+ public:
+ SimpleBufferPage() : mReadOffset(0), mWriteOffset(0) {}
+ static const size_t kSimpleBufferPageSize = 32000;
+
+ private:
+ friend class SimpleBuffer;
+ char mBuffer[kSimpleBufferPageSize];
+ size_t mReadOffset;
+ size_t mWriteOffset;
+};
+
+class SimpleBuffer {
+ public:
+ SimpleBuffer();
+ ~SimpleBuffer() = default;
+
+ nsresult Write(char* stc, size_t len); // return OK or OUT_OF_MEMORY
+ size_t Read(char* dest, size_t maxLen); // return bytes read
+ size_t Available();
+ void Clear();
+
+ private:
+ NS_DECL_OWNINGTHREAD
+
+ nsresult mStatus;
+ AutoCleanLinkedList<SimpleBufferPage> mBufferList;
+ size_t mAvailable;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/SimpleChannel.cpp b/netwerk/base/SimpleChannel.cpp
new file mode 100644
index 0000000000..c390418cb7
--- /dev/null
+++ b/netwerk/base/SimpleChannel.cpp
@@ -0,0 +1,118 @@
+/* -*- 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 "nsIInputStream.h"
+#include "nsIRequest.h"
+#include "nsISupportsImpl.h"
+#include "nsNetUtil.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/PSimpleChannelChild.h"
+
+namespace mozilla {
+namespace net {
+
+SimpleChannel::SimpleChannel(UniquePtr<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) {
+ NS_ENSURE_TRUE(mCallbacks, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIRequest> req;
+ MOZ_TRY_VAR(req, mCallbacks->StartAsyncRead(listener, this));
+
+ mCallbacks = nullptr;
+
+ req.forget(request);
+ 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..6e45c5bdb4
--- /dev/null
+++ b/netwerk/base/SimpleChannel.h
@@ -0,0 +1,147 @@
+/* -*- 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 RequestOrReason = Result<nsCOMPtr<nsIRequest>, 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) 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::move(aStartAsyncRead), std::move(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::move(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..01029d74eb
--- /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"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(SimpleChannelParent, nsIParentChannel, nsIStreamListener)
+
+bool SimpleChannelParent::Init(const uint64_t& aChannelId) {
+ nsCOMPtr<nsIChannel> channel;
+ MOZ_ALWAYS_SUCCEEDS(
+ 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::NotifyFlashPluginStateChanged(
+ nsIHttpChannel::FlashPluginState aState) {
+ // 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/TCPFastOpen.h b/netwerk/base/TCPFastOpen.h
new file mode 100644
index 0000000000..df6fbcc87a
--- /dev/null
+++ b/netwerk/base/TCPFastOpen.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 TCPFASTOPEN_H__
+#define TCPFASTOPEN_H__
+
+/**
+ * This is an abstract class for TCP Fast Open - TFO (RFC7413).
+ * It is not always safe to use Fast Open. It can be use for requests that
+ * are replayable.
+ * Middle boxes can block or reset connections that use TFO, therefore a
+ * backup connection will be prepared with a delay.
+ * In case of blocking such a connection tcp socket will terminate only after
+ * a timeout, therefore a backup connection is needed. If connection is refuse
+ * the same socketTransport will retry.
+ *
+ * This is implemented by nsHalfopenSocket.
+ **/
+
+namespace mozilla {
+namespace net {
+
+class TCPFastOpen {
+ public:
+ // Check if we have a transaction that is safe to be used with TFO.
+ // Connections over TLS are always safe and some http requests (e.g. GET).
+ virtual bool FastOpenEnabled() = 0;
+ // To use TFO we need to have a transaction prepared, e.g. also have
+ // nsHttpConnection ready. This functions is call by nsSocketTransport to
+ // setup a connection.
+ virtual nsresult StartFastOpen() = 0;
+ // Inform nsHalfopenSocket whether a connection using TFO succeeded or not.
+ // This will cancel the backup connection and in case of a failure rewind
+ // the transaction.
+ virtual void SetFastOpenConnected(nsresult error, bool aWillRetry) = 0;
+ virtual void FastOpenNotSupported() = 0;
+ virtual void SetFastOpenStatus(uint8_t tfoStatus) = 0;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // TCPFASTOPEN_H__
diff --git a/netwerk/base/TCPFastOpenLayer.cpp b/netwerk/base/TCPFastOpenLayer.cpp
new file mode 100644
index 0000000000..ea5e5f11d6
--- /dev/null
+++ b/netwerk/base/TCPFastOpenLayer.cpp
@@ -0,0 +1,527 @@
+/* -*- 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 "TCPFastOpenLayer.h"
+#include "nsSocketTransportService2.h"
+#include "prmem.h"
+#include "prio.h"
+
+namespace mozilla {
+namespace net {
+
+static PRDescIdentity sTCPFastOpenLayerIdentity;
+static PRIOMethods sTCPFastOpenLayerMethods;
+static PRIOMethods* sTCPFastOpenLayerMethodsPtr = nullptr;
+
+#define TFO_MAX_PACKET_SIZE_IPV4 1460
+#define TFO_MAX_PACKET_SIZE_IPV6 1440
+#define TFO_TLS_RECORD_HEADER_SIZE 22
+
+/**
+ * For the TCP Fast Open it is necessary to send all data that can fit into the
+ * first packet on a single sendto function call. Consecutive calls will not
+ * have an effect. Therefore TCPFastOpenLayer will collect some data before
+ * calling sendto. Necko and nss will call PR_Write multiple times with small
+ * amount of data.
+ * TCPFastOpenLayer has 4 states:
+ * WAITING_FOR_CONNECT:
+ * This is before connect is call. A call of recv, send or getpeername will
+ * return PR_NOT_CONNECTED_ERROR. After connect is call the state transfers
+ * into COLLECT_DATA_FOR_FIRST_PACKET.
+ *
+ * COLLECT_DATA_FOR_FIRST_PACKET:
+ * In this state all data received by send function calls will be stored in
+ * a buffer. If transaction do not have any more data ready to be sent or
+ * the buffer is full, TCPFastOpenFinish is call. TCPFastOpenFinish sends
+ * the collected data using sendto function and the state transfers to
+ * WAITING_FOR_CONNECTCONTINUE. If an error occurs during sendto, the error
+ * is reported by the TCPFastOpenFinish return values. nsSocketTransfer is
+ * the only caller of TCPFastOpenFinish; it knows how to interpreter these
+ * errors.
+ * WAITING_FOR_CONNECTCONTINUE:
+ * connectcontinue transfers from this state to CONNECTED. Any other
+ * function (e.g. send, recv) returns PR_WOULD_BLOCK_ERROR.
+ * CONNECTED:
+ * The size of mFirstPacketBuf is 1440/1460 (RFC7413 recomends that packet
+ * does exceeds these sizes). SendTo does not have to consume all buffered
+ * data and some data can be still in mFirstPacketBuf. Before sending any
+ * new data we need to send the remaining buffered data.
+ **/
+
+class TCPFastOpenSecret {
+ public:
+ TCPFastOpenSecret()
+ : mState(WAITING_FOR_CONNECT), mFirstPacketBufLen(0), mCondition(0) {
+ this->mAddr.raw.family = 0;
+ this->mAddr.inet.family = 0;
+ this->mAddr.inet.port = 0;
+ this->mAddr.inet.ip = 0;
+ this->mAddr.ipv6.family = 0;
+ this->mAddr.ipv6.port = 0;
+ this->mAddr.ipv6.flowinfo = 0;
+ this->mAddr.ipv6.scope_id = 0;
+ this->mAddr.local.family = 0;
+ }
+
+ enum {
+ CONNECTED,
+ WAITING_FOR_CONNECTCONTINUE,
+ COLLECT_DATA_FOR_FIRST_PACKET,
+ WAITING_FOR_CONNECT,
+ SOCKET_ERROR_STATE
+ } mState;
+ PRNetAddr mAddr;
+ char mFirstPacketBuf[1460];
+ uint16_t mFirstPacketBufLen;
+ PRErrorCode mCondition;
+};
+
+static PRStatus TCPFastOpenConnect(PRFileDesc* fd, const PRNetAddr* addr,
+ PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sTCPFastOpenLayerIdentity);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ TCPFastOpenSecret* secret = reinterpret_cast<TCPFastOpenSecret*>(fd->secret);
+
+ SOCKET_LOG(("TCPFastOpenConnect state=%d.\n", secret->mState));
+
+ if (secret->mState != TCPFastOpenSecret::WAITING_FOR_CONNECT) {
+ PR_SetError(PR_IS_CONNECTED_ERROR, 0);
+ return PR_FAILURE;
+ }
+
+ // Remember the address. It will be used for sendto or connect later.
+ memcpy(&secret->mAddr, addr, sizeof(secret->mAddr));
+ secret->mState = TCPFastOpenSecret::COLLECT_DATA_FOR_FIRST_PACKET;
+
+ return PR_SUCCESS;
+}
+
+static PRInt32 TCPFastOpenSend(PRFileDesc* fd, const void* buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sTCPFastOpenLayerIdentity);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ TCPFastOpenSecret* secret = reinterpret_cast<TCPFastOpenSecret*>(fd->secret);
+
+ SOCKET_LOG(("TCPFastOpenSend state=%d.\n", secret->mState));
+
+ switch (secret->mState) {
+ case TCPFastOpenSecret::CONNECTED:
+ // Before sending new data we need to drain the data collected during tfo.
+ if (secret->mFirstPacketBufLen) {
+ SOCKET_LOG(
+ ("TCPFastOpenSend - %d bytes to drain from "
+ "mFirstPacketBufLen.\n",
+ secret->mFirstPacketBufLen));
+ PRInt32 rv = (fd->lower->methods->send)(
+ fd->lower, secret->mFirstPacketBuf, secret->mFirstPacketBufLen,
+ 0, // flags
+ PR_INTERVAL_NO_WAIT);
+ if (rv <= 0) {
+ return rv;
+ }
+ secret->mFirstPacketBufLen -= rv;
+ if (secret->mFirstPacketBufLen) {
+ memmove(secret->mFirstPacketBuf, secret->mFirstPacketBuf + rv,
+ secret->mFirstPacketBufLen);
+
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ return PR_FAILURE;
+ } // if we drained the buffer we can fall through this checks and call
+ // send for the new data
+ }
+ SOCKET_LOG(("TCPFastOpenSend sending new data.\n"));
+ return (fd->lower->methods->send)(fd->lower, buf, amount, flags, timeout);
+ case TCPFastOpenSecret::WAITING_FOR_CONNECTCONTINUE:
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ return -1;
+ case TCPFastOpenSecret::COLLECT_DATA_FOR_FIRST_PACKET: {
+ int32_t toSend = (secret->mAddr.raw.family == PR_AF_INET)
+ ? TFO_MAX_PACKET_SIZE_IPV4
+ : TFO_MAX_PACKET_SIZE_IPV6;
+ MOZ_ASSERT(secret->mFirstPacketBufLen <= toSend);
+ toSend -= secret->mFirstPacketBufLen;
+
+ SOCKET_LOG(
+ ("TCPFastOpenSend: amount of data in the buffer=%d; the amount"
+ " of additional data that can be stored=%d.\n",
+ secret->mFirstPacketBufLen, toSend));
+
+ if (!toSend) {
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ return -1;
+ }
+
+ toSend = (toSend > amount) ? amount : toSend;
+ memcpy(secret->mFirstPacketBuf + secret->mFirstPacketBufLen, buf, toSend);
+ secret->mFirstPacketBufLen += toSend;
+ return toSend;
+ }
+ case TCPFastOpenSecret::WAITING_FOR_CONNECT:
+ PR_SetError(PR_NOT_CONNECTED_ERROR, 0);
+ return -1;
+ case TCPFastOpenSecret::SOCKET_ERROR_STATE:
+ PR_SetError(secret->mCondition, 0);
+ return -1;
+ }
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ return PR_FAILURE;
+}
+
+static PRInt32 TCPFastOpenWrite(PRFileDesc* fd, const void* buf,
+ PRInt32 amount) {
+ return TCPFastOpenSend(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT);
+}
+
+static PRInt32 TCPFastOpenRecv(PRFileDesc* fd, void* buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout) {
+ MOZ_RELEASE_ASSERT(fd->identity == sTCPFastOpenLayerIdentity);
+
+ TCPFastOpenSecret* secret = reinterpret_cast<TCPFastOpenSecret*>(fd->secret);
+
+ PRInt32 rv = -1;
+ switch (secret->mState) {
+ case TCPFastOpenSecret::CONNECTED:
+
+ if (secret->mFirstPacketBufLen) {
+ // TLS will not call write before receiving data from a server,
+ // therefore we need to force sending buffered data even during recv
+ // call. Otherwise It can come to a deadlock (clients waits for
+ // response, but the request is sitting in mFirstPacketBufLen).
+ SOCKET_LOG(
+ ("TCPFastOpenRevc - %d bytes to drain from mFirstPacketBuf.\n",
+ secret->mFirstPacketBufLen));
+ PRInt32 rv = (fd->lower->methods->send)(
+ fd->lower, secret->mFirstPacketBuf, secret->mFirstPacketBufLen,
+ 0, // flags
+ PR_INTERVAL_NO_WAIT);
+ if (rv <= 0) {
+ return rv;
+ }
+ secret->mFirstPacketBufLen -= rv;
+ if (secret->mFirstPacketBufLen) {
+ memmove(secret->mFirstPacketBuf, secret->mFirstPacketBuf + rv,
+ secret->mFirstPacketBufLen);
+ }
+ }
+ rv = (fd->lower->methods->recv)(fd->lower, buf, amount, flags, timeout);
+ break;
+ case TCPFastOpenSecret::WAITING_FOR_CONNECTCONTINUE:
+ case TCPFastOpenSecret::COLLECT_DATA_FOR_FIRST_PACKET:
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ break;
+ case TCPFastOpenSecret::WAITING_FOR_CONNECT:
+ PR_SetError(PR_NOT_CONNECTED_ERROR, 0);
+ break;
+ case TCPFastOpenSecret::SOCKET_ERROR_STATE:
+ PR_SetError(secret->mCondition, 0);
+ }
+ return rv;
+}
+
+static PRInt32 TCPFastOpenRead(PRFileDesc* fd, void* buf, PRInt32 amount) {
+ return TCPFastOpenRecv(fd, buf, amount, 0, PR_INTERVAL_NO_WAIT);
+}
+
+static PRStatus TCPFastOpenConnectContinue(PRFileDesc* fd, PRInt16 out_flags) {
+ MOZ_RELEASE_ASSERT(fd->identity == sTCPFastOpenLayerIdentity);
+
+ TCPFastOpenSecret* secret = reinterpret_cast<TCPFastOpenSecret*>(fd->secret);
+
+ PRStatus rv = PR_FAILURE;
+ switch (secret->mState) {
+ case TCPFastOpenSecret::CONNECTED:
+ rv = PR_SUCCESS;
+ break;
+ case TCPFastOpenSecret::WAITING_FOR_CONNECT:
+ case TCPFastOpenSecret::COLLECT_DATA_FOR_FIRST_PACKET:
+ PR_SetError(PR_NOT_CONNECTED_ERROR, 0);
+ rv = PR_FAILURE;
+ break;
+ case TCPFastOpenSecret::WAITING_FOR_CONNECTCONTINUE:
+ rv = (fd->lower->methods->connectcontinue)(fd->lower, out_flags);
+
+ SOCKET_LOG(("TCPFastOpenConnectContinue result=%d.\n", rv));
+ secret->mState = TCPFastOpenSecret::CONNECTED;
+ break;
+ case TCPFastOpenSecret::SOCKET_ERROR_STATE:
+ PR_SetError(secret->mCondition, 0);
+ rv = PR_FAILURE;
+ }
+ return rv;
+}
+
+static PRStatus TCPFastOpenClose(PRFileDesc* fd) {
+ if (!fd) {
+ return PR_FAILURE;
+ }
+
+ PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER);
+
+ MOZ_RELEASE_ASSERT(layer && layer->identity == sTCPFastOpenLayerIdentity,
+ "TCP Fast Open Layer not on top of stack");
+
+ TCPFastOpenSecret* secret =
+ reinterpret_cast<TCPFastOpenSecret*>(layer->secret);
+ layer->secret = nullptr;
+ layer->dtor(layer);
+ delete secret;
+ return fd->methods->close(fd);
+}
+
+static PRStatus TCPFastOpenGetpeername(PRFileDesc* fd, PRNetAddr* addr) {
+ MOZ_RELEASE_ASSERT(fd);
+ MOZ_RELEASE_ASSERT(addr);
+
+ MOZ_RELEASE_ASSERT(fd->identity == sTCPFastOpenLayerIdentity);
+
+ TCPFastOpenSecret* secret = reinterpret_cast<TCPFastOpenSecret*>(fd->secret);
+ if (secret->mState == TCPFastOpenSecret::WAITING_FOR_CONNECT) {
+ PR_SetError(PR_NOT_CONNECTED_ERROR, 0);
+ return PR_FAILURE;
+ }
+
+ memcpy(addr, &secret->mAddr, sizeof(secret->mAddr));
+ return PR_SUCCESS;
+}
+
+static PRInt16 TCPFastOpenPoll(PRFileDesc* fd, PRInt16 how_flags,
+ PRInt16* p_out_flags) {
+ MOZ_RELEASE_ASSERT(fd);
+ MOZ_RELEASE_ASSERT(fd->identity == sTCPFastOpenLayerIdentity);
+
+ TCPFastOpenSecret* secret = reinterpret_cast<TCPFastOpenSecret*>(fd->secret);
+ if (secret->mFirstPacketBufLen) {
+ how_flags |= PR_POLL_WRITE;
+ }
+
+ return fd->lower->methods->poll(fd->lower, how_flags, p_out_flags);
+}
+
+nsresult AttachTCPFastOpenIOLayer(PRFileDesc* fd) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!sTCPFastOpenLayerMethodsPtr) {
+ sTCPFastOpenLayerIdentity = PR_GetUniqueIdentity("TCPFastOpen Layer");
+ sTCPFastOpenLayerMethods = *PR_GetDefaultIOMethods();
+ sTCPFastOpenLayerMethods.connect = TCPFastOpenConnect;
+ sTCPFastOpenLayerMethods.send = TCPFastOpenSend;
+ sTCPFastOpenLayerMethods.write = TCPFastOpenWrite;
+ sTCPFastOpenLayerMethods.recv = TCPFastOpenRecv;
+ sTCPFastOpenLayerMethods.read = TCPFastOpenRead;
+ sTCPFastOpenLayerMethods.connectcontinue = TCPFastOpenConnectContinue;
+ sTCPFastOpenLayerMethods.close = TCPFastOpenClose;
+ sTCPFastOpenLayerMethods.getpeername = TCPFastOpenGetpeername;
+ sTCPFastOpenLayerMethods.poll = TCPFastOpenPoll;
+ sTCPFastOpenLayerMethodsPtr = &sTCPFastOpenLayerMethods;
+ }
+
+ PRFileDesc* layer = PR_CreateIOLayerStub(sTCPFastOpenLayerIdentity,
+ sTCPFastOpenLayerMethodsPtr);
+
+ if (!layer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ TCPFastOpenSecret* secret = new TCPFastOpenSecret();
+
+ 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 TCPFastOpenFinish(PRFileDesc* fd, PRErrorCode& err,
+ bool& fastOpenNotSupported, uint8_t& tfoStatus) {
+ PRFileDesc* tfoFd = PR_GetIdentitiesLayer(fd, sTCPFastOpenLayerIdentity);
+ MOZ_RELEASE_ASSERT(tfoFd);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ TCPFastOpenSecret* secret =
+ reinterpret_cast<TCPFastOpenSecret*>(tfoFd->secret);
+
+ MOZ_ASSERT(secret->mState ==
+ TCPFastOpenSecret::COLLECT_DATA_FOR_FIRST_PACKET);
+
+ fastOpenNotSupported = false;
+ tfoStatus = TFO_NOT_TRIED;
+ PRErrorCode result = 0;
+
+ // If we do not have data to send with syn packet or nspr version does not
+ // have sendto implemented we will call normal connect.
+ // If sendto is not implemented it points to _PR_InvalidInt, therefore we
+ // check if sendto != _PR_InvalidInt. _PR_InvalidInt is exposed so we use
+ // reserved_fn_0 which also points to _PR_InvalidInt.
+ if (!secret->mFirstPacketBufLen ||
+ (tfoFd->lower->methods->sendto ==
+ (PRSendtoFN)tfoFd->lower->methods->reserved_fn_0)) {
+ // Because of the way our nsHttpTransaction dispatch work, it can happened
+ // that data has not been written into the socket.
+ // In this case we can just call connect.
+ PRInt32 rv = (tfoFd->lower->methods->connect)(tfoFd->lower, &secret->mAddr,
+ PR_INTERVAL_NO_WAIT);
+ if (rv == PR_SUCCESS) {
+ result = PR_IS_CONNECTED_ERROR;
+ } else {
+ result = PR_GetError();
+ }
+ if (tfoFd->lower->methods->sendto ==
+ (PRSendtoFN)tfoFd->lower->methods->reserved_fn_0) {
+ // sendto is not implemented, it is equal to _PR_InvalidInt!
+ // We will disable Fast Open.
+ SOCKET_LOG(("TCPFastOpenFinish - sendto not implemented.\n"));
+ fastOpenNotSupported = true;
+ tfoStatus = TFO_DISABLED;
+ }
+ } else {
+ // We have some data ready in the buffer we will send it with the syn
+ // packet.
+ PRInt32 rv = (tfoFd->lower->methods->sendto)(
+ tfoFd->lower, secret->mFirstPacketBuf, secret->mFirstPacketBufLen,
+ 0, // flags
+ &secret->mAddr, PR_INTERVAL_NO_WAIT);
+
+ SOCKET_LOG(("TCPFastOpenFinish - sendto result=%d.\n", rv));
+ if (rv > 0) {
+ result = PR_IN_PROGRESS_ERROR;
+ secret->mFirstPacketBufLen -= rv;
+ if (secret->mFirstPacketBufLen) {
+ memmove(secret->mFirstPacketBuf, secret->mFirstPacketBuf + rv,
+ secret->mFirstPacketBufLen);
+ }
+ tfoStatus = TFO_DATA_SENT;
+ } else {
+ result = PR_GetError();
+ SOCKET_LOG(("TCPFastOpenFinish - sendto error=%d.\n", result));
+
+ if (result ==
+ PR_NOT_TCP_SOCKET_ERROR) { // SendTo will return
+ // PR_NOT_TCP_SOCKET_ERROR if TCP Fast
+ // Open is turned off on Linux.
+ // We can call connect again.
+ fastOpenNotSupported = true;
+ rv = (tfoFd->lower->methods->connect)(tfoFd->lower, &secret->mAddr,
+ PR_INTERVAL_NO_WAIT);
+
+ if (rv == PR_SUCCESS) {
+ result = PR_IS_CONNECTED_ERROR;
+ } else {
+ result = PR_GetError();
+ }
+ tfoStatus = TFO_DISABLED;
+ } else {
+ tfoStatus = TFO_TRIED;
+ }
+ }
+ }
+
+ if (result == PR_IN_PROGRESS_ERROR) {
+ secret->mState = TCPFastOpenSecret::WAITING_FOR_CONNECTCONTINUE;
+ } else {
+ // If the error is not PR_IN_PROGRESS_ERROR, change the state to CONNECT so
+ // that recv/send can perform recv/send on the next lower layer and pick up
+ // the real error. This is really important!
+ // The result can also be PR_IS_CONNECTED_ERROR, that should change the
+ // state to CONNECT anyway.
+ secret->mState = TCPFastOpenSecret::CONNECTED;
+ }
+ err = result;
+}
+
+/* This function returns the size of the remaining free space in the
+ * first_packet_buffer. This will be used by transactions with a tls layer. For
+ * other transactions it is not necessary. The tls transactions make a tls
+ * record before writing to this layer and if the record is too big the part
+ * that does not have place in the mFirstPacketBuf will be saved on the tls
+ * layer. During TFO we cannot send more than TFO_MAX_PACKET_SIZE_IPV4/6 bytes,
+ * so if we have a big tls record, this record is encrypted with 0RTT key,
+ * tls-early-data can be rejected and than we still need to send the rest of the
+ * record.
+ */
+int32_t TCPFastOpenGetBufferSizeLeft(PRFileDesc* fd) {
+ PRFileDesc* tfoFd = PR_GetIdentitiesLayer(fd, sTCPFastOpenLayerIdentity);
+ MOZ_RELEASE_ASSERT(tfoFd);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ TCPFastOpenSecret* secret =
+ reinterpret_cast<TCPFastOpenSecret*>(tfoFd->secret);
+
+ if (secret->mState != TCPFastOpenSecret::COLLECT_DATA_FOR_FIRST_PACKET) {
+ return 0;
+ }
+
+ int32_t sizeLeft = (secret->mAddr.raw.family == PR_AF_INET)
+ ? TFO_MAX_PACKET_SIZE_IPV4
+ : TFO_MAX_PACKET_SIZE_IPV6;
+ MOZ_ASSERT(secret->mFirstPacketBufLen <= sizeLeft);
+ sizeLeft -= secret->mFirstPacketBufLen;
+
+ SOCKET_LOG(("TCPFastOpenGetBufferSizeLeft=%d.\n", sizeLeft));
+
+ return (sizeLeft > TFO_TLS_RECORD_HEADER_SIZE)
+ ? sizeLeft - TFO_TLS_RECORD_HEADER_SIZE
+ : 0;
+}
+
+bool TCPFastOpenGetCurrentBufferSize(PRFileDesc* fd) {
+ PRFileDesc* tfoFd = PR_GetIdentitiesLayer(fd, sTCPFastOpenLayerIdentity);
+ MOZ_RELEASE_ASSERT(tfoFd);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ TCPFastOpenSecret* secret =
+ reinterpret_cast<TCPFastOpenSecret*>(tfoFd->secret);
+
+ return secret->mFirstPacketBufLen;
+}
+
+bool TCPFastOpenFlushBuffer(PRFileDesc* fd) {
+ PRFileDesc* tfoFd = PR_GetIdentitiesLayer(fd, sTCPFastOpenLayerIdentity);
+ MOZ_RELEASE_ASSERT(tfoFd);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ TCPFastOpenSecret* secret =
+ reinterpret_cast<TCPFastOpenSecret*>(tfoFd->secret);
+ MOZ_ASSERT(secret->mState == TCPFastOpenSecret::CONNECTED);
+
+ if (secret->mFirstPacketBufLen) {
+ SOCKET_LOG(
+ ("TCPFastOpenFlushBuffer - %d bytes to drain from "
+ "mFirstPacketBufLen.\n",
+ secret->mFirstPacketBufLen));
+ PRInt32 rv = (tfoFd->lower->methods->send)(
+ tfoFd->lower, secret->mFirstPacketBuf, secret->mFirstPacketBufLen,
+ 0, // flags
+ PR_INTERVAL_NO_WAIT);
+ if (rv <= 0) {
+ PRErrorCode err = PR_GetError();
+ if (err == PR_WOULD_BLOCK_ERROR) {
+ // We still need to send this data.
+ return true;
+ }
+ // There is an error, let nsSocketTransport pick it up properly.
+ secret->mCondition = err;
+ secret->mState = TCPFastOpenSecret::SOCKET_ERROR_STATE;
+ return false;
+ }
+
+ secret->mFirstPacketBufLen -= rv;
+ if (secret->mFirstPacketBufLen) {
+ memmove(secret->mFirstPacketBuf, secret->mFirstPacketBuf + rv,
+ secret->mFirstPacketBufLen);
+ }
+ }
+ return secret->mFirstPacketBufLen;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/TCPFastOpenLayer.h b/netwerk/base/TCPFastOpenLayer.h
new file mode 100644
index 0000000000..597615cabe
--- /dev/null
+++ b/netwerk/base/TCPFastOpenLayer.h
@@ -0,0 +1,94 @@
+/* -*- 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 TCPFastOpenLayer_h__
+#define TCPFastOpenLayer_h__
+
+#include "prerror.h"
+
+namespace mozilla {
+namespace net {
+
+/**
+ * This layer must be placed just above PR-tcp socket, i.e. it must be under
+ * nss layer.
+ * At the beginning of TCPFastOpenLayer.cpp there is explanation what this
+ * layer do.
+ **/
+
+typedef enum {
+ TFO_NOT_SET, // This is only as a control.
+ // A connection not using TFO will have the TFO state set upon
+ // connection creation (in nsHalfOpenSocket::SetupConn).
+ // A connection using TFO will have the TFO state set after
+ // the connection is established or canceled.
+ TFO_UNKNOWN, // This is before the primary socket is built, i.e. before
+ // TCPFastOpenFinish is called.
+ TFO_DISABLED, // tfo is disabled because of a tfo error on a previous
+ // connection to the host (i.e. !mEnt->mUseFastOpen).
+ // If TFO is not supported by the OS, it is disabled by
+ // the pref or too many consecutive errors occurred, this value
+ // is not reported. This is set before StartFastOpen is called.
+ TFO_DISABLED_CONNECT, // Connection is using CONNECT. This is set before
+ // StartFastOpen is called.
+ // The following 3 are set just after TCPFastOpenFinish.
+ TFO_NOT_TRIED, // For some reason TCPFastOpenLayer does not have any data to
+ // send with the syn packet. This should never happen.
+ TFO_TRIED, // TCP has sent a TFO cookie request.
+ TFO_DATA_SENT, // On Linux, TCP has send data as well. (On Linux we do not
+ // know whether data has been accepted).
+ // On Windows, TCP has send data or only a TFO cookie request
+ // and the data or TFO cookie has been accepted by the server.
+ // The following value is only used on windows and is set after
+ // PR_ConnectContinue. That is the point when we know if TFO data was been
+ // accepted.
+ TFO_DATA_COOKIE_NOT_ACCEPTED, // This is only on Windows. TFO data or TFO
+ // cookie request has not been accepted.
+ // The following 3 are set during socket error recover
+ // (nsSocketTransport::RecoverFromError).
+ TFO_FAILED_CONNECTION_REFUSED,
+ TFO_FAILED_NET_TIMEOUT,
+ TFO_FAILED_UNKNOW_ERROR,
+ // The following 4 are set when backup connection finishes before the primary
+ // connection.
+ TFO_FAILED_BACKUP_CONNECTION_TFO_NOT_TRIED,
+ TFO_FAILED_BACKUP_CONNECTION_TFO_TRIED,
+ TFO_FAILED_BACKUP_CONNECTION_TFO_DATA_SENT,
+ TFO_FAILED_BACKUP_CONNECTION_TFO_DATA_COOKIE_NOT_ACCEPTED,
+ // The following 4 are set when the recovery connection fails as well.
+ TFO_FAILED_CONNECTION_REFUSED_NO_TFO_FAILED_TOO,
+ TFO_FAILED_NET_TIMEOUT_NO_TFO_FAILED_TOO,
+ TFO_FAILED_UNKNOW_ERROR_NO_TFO_FAILED_TOO,
+ TFO_FAILED_BACKUP_CONNECTION_NO_TFO_FAILED_TOO,
+ TFO_BACKUP_CONN, // This is a backup conn, for a halfOpenSock that was used
+ // TFO.
+ TFO_INIT_FAILED, // nsHalfOpenSocket::SetupConn failed.
+ TFO_UNKNOWN_RESOLVING, // There is a high number of TFO_UNKNOWN state
+ // reported. Let's split them depending on the socket
+ // transport state: TFO_UNKNOWN_RESOLVING,
+ // TFO_UNKNOWN_RESOLVED, TFO_UNKNOWN_CONNECTING and
+ // TFO_UNKNOWN_CONNECTED..
+ TFO_UNKNOWN_RESOLVED,
+ TFO_UNKNOWN_CONNECTING,
+ TFO_UNKNOWN_CONNECTED,
+ TFO_FAILED,
+ TFO_HTTP // TFO is disabled for non-secure connections.
+} TFOResult;
+
+nsresult AttachTCPFastOpenIOLayer(PRFileDesc* fd);
+
+// Get the result of TCP Fast Open.
+void TCPFastOpenFinish(PRFileDesc* fd, PRErrorCode& err,
+ bool& fastOpenNotSupported, uint8_t& tfoStatus);
+
+int32_t TCPFastOpenGetBufferSizeLeft(PRFileDesc* fd);
+
+bool TCPFastOpenGetCurrentBufferSize(PRFileDesc* fd);
+bool TCPFastOpenFlushBuffer(PRFileDesc* fd);
+} // namespace net
+} // namespace mozilla
+
+#endif // TCPFastOpenLayer_h__
diff --git a/netwerk/base/TLSServerSocket.cpp b/netwerk/base/TLSServerSocket.cpp
new file mode 100644
index 0000000000..ff4049b316
--- /dev/null
+++ b/netwerk/base/TLSServerSocket.cpp
@@ -0,0 +1,448 @@
+/* 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
+//-----------------------------------------------------------------------------
+
+TLSServerSocket::TLSServerSocket() : mServerCert(nullptr) {}
+
+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<nsISupports> infoSupports =
+ NS_ISUPPORTS_CAST(nsITLSServerConnectionInfo*, info);
+ rv = trans->InitWithConnectedSocket(aClientFD, &aClientAddr, infoSupports);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mCondition = rv;
+ return;
+ }
+
+ // Override the default peer certificate validation, so that server consumers
+ // can make their own choice after the handshake completes.
+ SSL_AuthCertificateHook(aClientFD, AuthCertificateHook, nullptr);
+ // Once the TLS handshake has completed, the server consumer is notified and
+ // has access to various TLS state details.
+ // It's safe to pass info here because the socket transport holds it as
+ // |mSecInfo| which keeps it alive for the lifetime of the socket.
+ SSL_HandshakeCallback(aClientFD, TLSServerConnectionInfo::HandshakeCallback,
+ info);
+
+ // Notify the consumer of the new client so it can manage the streams.
+ // Security details aren't known yet. The security observer will be notified
+ // later when they are ready.
+ nsCOMPtr<nsIServerSocket> serverSocket =
+ do_QueryInterface(NS_ISUPPORTS_CAST(nsITLSServerSocket*, this));
+ mListener->OnSocketAccepted(serverSocket, trans);
+}
+
+nsresult TLSServerSocket::OnSocketListen() {
+ if (NS_WARN_IF(!mServerCert)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ UniqueCERTCertificate cert(mServerCert->GetCert());
+ if (NS_WARN_IF(!cert)) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ UniqueSECKEYPrivateKey key(PK11_FindKeyByAnyCert(cert.get(), nullptr));
+ if (NS_WARN_IF(!key)) {
+ return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
+ }
+
+ SSLKEAType certKEA = NSS_FindCertKEAType(cert.get());
+
+ nsresult rv =
+ MapSECStatus(SSL_ConfigSecureServer(mFD, cert.get(), key.get(), certKEA));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// static
+SECStatus TLSServerSocket::AuthCertificateHook(void* arg, PRFileDesc* fd,
+ PRBool checksig,
+ PRBool isServer) {
+ // Allow any client cert here, server consumer code can decide whether it's
+ // okay after being notified of the new client socket.
+ return SECSuccess;
+}
+
+//-----------------------------------------------------------------------------
+// TLSServerSocket::nsITLSServerSocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+TLSServerSocket::GetServerCert(nsIX509Cert** aCert) {
+ if (NS_WARN_IF(!aCert)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aCert = mServerCert;
+ NS_IF_ADDREF(*aCert);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::SetServerCert(nsIX509Cert* aCert) {
+ // If AsyncListen was already called (and set mListener), it's too late to set
+ // this.
+ if (NS_WARN_IF(mListener)) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ mServerCert = aCert;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerSocket::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)
+
+TLSServerConnectionInfo::TLSServerConnectionInfo()
+ : mServerSocket(nullptr),
+ mTransport(nullptr),
+ mPeerCert(nullptr),
+ mTlsVersionUsed(TLS_VERSION_UNKNOWN),
+ mKeyLength(0),
+ mMacLength(0),
+ mLock("TLSServerConnectionInfo.mLock"),
+ mSecurityObserver(nullptr) {}
+
+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 = mServerSocket;
+ NS_IF_ADDREF(*aSocket);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetStatus(nsITLSClientStatus** aStatus) {
+ if (NS_WARN_IF(!aStatus)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aStatus = this;
+ NS_IF_ADDREF(*aStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetPeerCert(nsIX509Cert** aCert) {
+ if (NS_WARN_IF(!aCert)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aCert = mPeerCert;
+ NS_IF_ADDREF(*aCert);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetTlsVersionUsed(int16_t* aTlsVersionUsed) {
+ if (NS_WARN_IF(!aTlsVersionUsed)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aTlsVersionUsed = mTlsVersionUsed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetCipherName(nsACString& aCipherName) {
+ aCipherName.Assign(mCipherName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetKeyLength(uint32_t* aKeyLength) {
+ if (NS_WARN_IF(!aKeyLength)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aKeyLength = mKeyLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSServerConnectionInfo::GetMacLength(uint32_t* aMacLength) {
+ if (NS_WARN_IF(!aMacLength)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ *aMacLength = mMacLength;
+ return NS_OK;
+}
+
+// static
+void TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD, void* aArg) {
+ RefPtr<TLSServerConnectionInfo> info =
+ static_cast<TLSServerConnectionInfo*>(aArg);
+ nsISocketTransport* transport = info->mTransport;
+ // No longer needed outside this function, so clear the weak ref
+ info->mTransport = nullptr;
+ nsresult rv = info->HandshakeCallback(aFD);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ transport->Close(rv);
+ }
+}
+
+nsresult TLSServerConnectionInfo::HandshakeCallback(PRFileDesc* aFD) {
+ nsresult rv;
+
+ UniqueCERTCertificate clientCert(SSL_PeerCertificate(aFD));
+ if (clientCert) {
+ nsCOMPtr<nsIX509CertDB> certDB =
+ do_GetService(NS_X509CERTDB_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIX509Cert> clientCertPSM;
+ 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..5baba8ee4a
--- /dev/null
+++ b/netwerk/base/TLSServerSocket.h
@@ -0,0 +1,77 @@
+/* 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 "nsITLSServerSocket.h"
+#include "nsServerSocket.h"
+#include "nsString.h"
+#include "mozilla/Mutex.h"
+#include "seccomon.h"
+
+namespace mozilla {
+namespace net {
+
+class TLSServerSocket final : public nsServerSocket, public nsITLSServerSocket {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_FORWARD_NSISERVERSOCKET(nsServerSocket::)
+ NS_DECL_NSITLSSERVERSOCKET
+
+ // Override methods from nsServerSocket
+ virtual void CreateClientTransport(PRFileDesc* clientFD,
+ const NetAddr& clientAddr) override;
+ virtual nsresult SetSocketDefaults() override;
+ virtual nsresult OnSocketListen() override;
+
+ TLSServerSocket();
+
+ private:
+ virtual ~TLSServerSocket() = default;
+
+ static SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd,
+ PRBool checksig, PRBool isServer);
+
+ nsCOMPtr<nsIX509Cert> mServerCert;
+};
+
+class TLSServerConnectionInfo : public nsITLSServerConnectionInfo,
+ public nsITLSClientStatus {
+ friend class TLSServerSocket;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITLSSERVERCONNECTIONINFO
+ NS_DECL_NSITLSCLIENTSTATUS
+
+ TLSServerConnectionInfo();
+
+ private:
+ virtual ~TLSServerConnectionInfo();
+
+ static void HandshakeCallback(PRFileDesc* aFD, void* aArg);
+ nsresult HandshakeCallback(PRFileDesc* aFD);
+
+ RefPtr<TLSServerSocket> mServerSocket;
+ // Weak ref to the transport, to avoid cycles since the transport holds a
+ // reference to the TLSServerConnectionInfo object. This is not handed out to
+ // anyone, and is only used in HandshakeCallback to close the transport in
+ // case of an error. After this, it's set to nullptr.
+ nsISocketTransport* mTransport;
+ nsCOMPtr<nsIX509Cert> mPeerCert;
+ int16_t mTlsVersionUsed;
+ nsCString mCipherName;
+ uint32_t mKeyLength;
+ uint32_t mMacLength;
+ // lock protects access to mSecurityObserver
+ mozilla::Mutex mLock;
+ nsCOMPtr<nsITLSServerSecurityObserver> mSecurityObserver;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_TLSServerSocket_h
diff --git a/netwerk/base/TRRLoadInfo.cpp b/netwerk/base/TRRLoadInfo.cpp
new file mode 100644
index 0000000000..970a873bc5
--- /dev/null
+++ b/netwerk/base/TRRLoadInfo.cpp
@@ -0,0 +1,682 @@
+/* -*- 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;
+}
+
+nsIPrincipal* TRRLoadInfo::GetSandboxedLoadingPrincipal() { return nullptr; }
+
+nsIPrincipal* TRRLoadInfo::GetTopLevelPrincipal() { return nullptr; }
+
+nsIPrincipal* TRRLoadInfo::GetTopLevelStorageAreaPrincipal() { return nullptr; }
+
+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::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::GetHasStoragePermission(bool* aHasStoragePermission) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetHasStoragePermission(bool aHasStoragePermission) {
+ 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::SetBypassCORSChecks(bool aBypassCORSChecks) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetBypassCORSChecks(bool* aBypassCORSChecks) {
+ 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::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::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(nsIRedirectHistoryEntry* aEntry,
+ 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::GetCspNonce(nsAString& aCspNonce) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetCspNonce(const nsAString& aCspNonce) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetSkipContentSniffing(bool* aSkipContentSniffing) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::SetSkipContentSniffing(bool aSkipContentSniffing) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsTopLevelLoad(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::GetIsFromProcessingFrameAttributes(
+ bool* aIsFromProcessingFrameAttributes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+TRRLoadInfo::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::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::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::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;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/TRRLoadInfo.h b/netwerk/base/TRRLoadInfo.h
new file mode 100644
index 0000000000..bd18dc7562
--- /dev/null
+++ b/netwerk/base/TRRLoadInfo.h
@@ -0,0 +1,52 @@
+/* -*- 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;
+ Maybe<mozilla::dom::ClientInfo> mClientInfo;
+ Maybe<mozilla::dom::ClientInfo> mReservedClientInfo;
+ Maybe<mozilla::dom::ClientInfo> mInitialClientInfo;
+ Maybe<mozilla::dom::ServiceWorkerDescriptor> mController;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_TRRLoadInfo_h
diff --git a/netwerk/base/ThrottleQueue.cpp b/netwerk/base/ThrottleQueue.cpp
new file mode 100644
index 0000000000..535ed8d169
--- /dev/null
+++ b/netwerk/base/ThrottleQueue.cpp
@@ -0,0 +1,399 @@
+/* -*- 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 "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::Read(char* aBuf, uint32_t aCount, uint32_t* aResult) {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ uint32_t realCount;
+ nsresult rv = mQueue->Available(aCount, &realCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (realCount == 0) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = mStream->Read(aBuf, realCount, aResult);
+ if (NS_SUCCEEDED(rv) && *aResult > 0) {
+ mQueue->RecordRead(*aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult) {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ uint32_t realCount;
+ nsresult rv = mQueue->Available(aCount, &realCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (realCount == 0) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = mStream->ReadSegments(aWriter, aClosure, realCount, aResult);
+ if (NS_SUCCEEDED(rv) && *aResult > 0) {
+ mQueue->RecordRead(*aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Seek(int32_t aWhence, int64_t aOffset) {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mStream);
+ if (!sstream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return sstream->Seek(aWhence, aOffset);
+}
+
+NS_IMETHODIMP
+ThrottleInputStream::Tell(int64_t* aResult) {
+ if (NS_FAILED(mClosedStatus)) {
+ return mClosedStatus;
+ }
+
+ nsCOMPtr<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()
+ : mMeanBytesPerSecond(0),
+ mMaxBytesPerSecond(0),
+ mBytesProcessed(0),
+ mTimerArmed(false) {
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> sts;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_SUCCEEDED(rv))
+ sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) mTimer = 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 = thisSliceBytes;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::Init(uint32_t aMeanBytesPerSecond, uint32_t aMaxBytesPerSecond) {
+ // Can be called on any thread.
+ if (aMeanBytesPerSecond == 0 || aMaxBytesPerSecond == 0 ||
+ aMaxBytesPerSecond < aMeanBytesPerSecond) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mMeanBytesPerSecond = aMeanBytesPerSecond;
+ mMaxBytesPerSecond = aMaxBytesPerSecond;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::BytesProcessed(uint64_t* aResult) {
+ *aResult = mBytesProcessed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::WrapStream(nsIInputStream* aInputStream,
+ nsIAsyncInputStream** aResult) {
+ nsCOMPtr<nsIAsyncInputStream> result =
+ new ThrottleInputStream(aInputStream, this);
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ThrottleQueue::Notify(nsITimer* aTimer) {
+ MOZ_ASSERT(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) == mAsyncEvents.NoIndex) {
+ mAsyncEvents.AppendElement(aStream);
+
+ if (!mTimerArmed) {
+ uint32_t ms = 1000;
+ if (mReadEvents.Length() > 0) {
+ TimeStamp t = mReadEvents[0].mTime + TimeDuration::FromSeconds(1);
+ TimeStamp now = TimeStamp::Now();
+
+ if (t > now) {
+ ms = static_cast<uint32_t>((t - now).ToMilliseconds());
+ } else {
+ ms = 1;
+ }
+ }
+
+ if (NS_SUCCEEDED(
+ mTimer->InitWithCallback(this, ms, nsITimer::TYPE_ONE_SHOT))) {
+ mTimerArmed = true;
+ }
+ }
+ }
+}
+
+void ThrottleQueue::DequeueStream(ThrottleInputStream* aStream) {
+ MOZ_ASSERT(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..49885e8887
--- /dev/null
+++ b/netwerk/base/ThrottleQueue.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ThrottleQueue_h
+#define mozilla_net_ThrottleQueue_h
+
+#include "mozilla/TimeStamp.h"
+#include "nsINamed.h"
+#include "nsIThrottledInputChannel.h"
+#include "nsITimer.h"
+
+namespace mozilla {
+namespace net {
+
+class ThrottleInputStream;
+
+/**
+ * An implementation of nsIInputChannelThrottleQueue that can be used
+ * to throttle uploads. This class is not thread-safe.
+ * Initialization and calls to WrapStream may be done on any thread;
+ * but otherwise, after creation, it can only be used on the socket
+ * thread. It currently throttles with a one second granularity, so
+ * may be a bit choppy.
+ */
+
+class ThrottleQueue : 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;
+ };
+
+ nsTArray<ThrottleEntry> mReadEvents;
+ uint32_t mMeanBytesPerSecond;
+ uint32_t mMaxBytesPerSecond;
+ uint64_t mBytesProcessed;
+
+ nsTArray<RefPtr<ThrottleInputStream>> mAsyncEvents;
+ nsCOMPtr<nsITimer> mTimer;
+ bool mTimerArmed;
+};
+
+} // 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..1983958cc2
--- /dev/null
+++ b/netwerk/base/Tickler.cpp
@@ -0,0 +1,245 @@
+/* -*- 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() {
+ 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..88553732ac
--- /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;
+ 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/http-sfv/Cargo.toml b/netwerk/base/http-sfv/Cargo.toml
new file mode 100644
index 0000000000..2d97e77732
--- /dev/null
+++ b/netwerk/base/http-sfv/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "http_sfv"
+version = "0.1.0"
+authors = ["barabass <yalyna.ts@gmail.com>"]
+edition = "2018"
+
+# 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.8.0"
+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..5a70948757
--- /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 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..283d0111ec
--- /dev/null
+++ b/netwerk/base/http-sfv/src/lib.rs
@@ -0,0 +1,870 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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);
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVService)]
+#[refcnt = "atomic"]
+struct InitSFVService {}
+
+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 = ThinVec::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<RefPtr<nsISFVItem>>, params: *const nsISFVParams) -> *const nsISFVInnerList);
+ fn new_inner_list(
+ &self,
+ items: &thin_vec::ThinVec<RefPtr<nsISFVItem>>,
+ params: &nsISFVParams,
+ ) -> Result<RefPtr<nsISFVInnerList>, nsresult> {
+ Ok(RefPtr::new(
+ SFVInnerList::new(items, params).coerce::<nsISFVInnerList>(),
+ ))
+ }
+
+ xpcom_method!(new_list => NewList(members: *const thin_vec::ThinVec<RefPtr<nsISFVItemOrInnerList>>) -> *const nsISFVList);
+ fn new_list(
+ &self,
+ members: &thin_vec::ThinVec<RefPtr<nsISFVItemOrInnerList>>,
+ ) -> Result<RefPtr<nsISFVList>, 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>(),
+ ))
+ }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVInteger, nsISFVBareItem)]
+#[refcnt = "nonatomic"]
+struct InitSFVInteger {
+ 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() -> i64);
+ fn get_type(&self) -> Result<i64, nsresult> {
+ Ok(nsISFVBareItem::INTEGER)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVBool, nsISFVBareItem)]
+#[refcnt = "nonatomic"]
+struct InitSFVBool {
+ 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() -> i64);
+ fn get_type(&self) -> Result<i64, nsresult> {
+ Ok(nsISFVBareItem::BOOL)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVString, nsISFVBareItem)]
+#[refcnt = "nonatomic"]
+struct InitSFVString {
+ 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() -> i64);
+ fn get_type(&self) -> Result<i64, nsresult> {
+ Ok(nsISFVBareItem::STRING)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVToken, nsISFVBareItem)]
+#[refcnt = "nonatomic"]
+struct InitSFVToken {
+ 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() -> i64);
+ fn get_type(&self) -> Result<i64, nsresult> {
+ Ok(nsISFVBareItem::TOKEN)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVByteSeq, nsISFVBareItem)]
+#[refcnt = "nonatomic"]
+struct InitSFVByteSeq {
+ 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() -> i64);
+ fn get_type(&self) -> Result<i64, nsresult> {
+ Ok(nsISFVBareItem::BYTE_SEQUENCE)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVDecimal, nsISFVBareItem)]
+#[refcnt = "nonatomic"]
+struct InitSFVDecimal {
+ 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() -> i64);
+ fn get_type(&self) -> Result<i64, nsresult> {
+ Ok(nsISFVBareItem::DECIMAL)
+ }
+
+ fn from_bare_item_interface(obj: &nsISFVBareItem) -> &Self {
+ unsafe { ::std::mem::transmute(obj) }
+ }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVParams)]
+#[refcnt = "nonatomic"]
+struct InitSFVParams {
+ 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) }
+ }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVItem, nsISFVItemOrInnerList)]
+#[refcnt = "nonatomic"]
+struct InitSFVItem {
+ 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) }
+ }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVInnerList, nsISFVItemOrInnerList)]
+#[refcnt = "nonatomic"]
+struct InitSFVInnerList {
+ items: RefCell<thin_vec::ThinVec<RefPtr<nsISFVItem>>>,
+ params: RefPtr<nsISFVParams>,
+}
+
+impl SFVInnerList {
+ fn new(
+ items: &thin_vec::ThinVec<RefPtr<nsISFVItem>>,
+ params: &nsISFVParams,
+ ) -> RefPtr<SFVInnerList> {
+ SFVInnerList::allocate(InitSFVInnerList {
+ items: RefCell::new((*items).clone()),
+ params: RefPtr::new(params),
+ })
+ }
+
+ xpcom_method!(
+ get_items => GetItems() -> thin_vec::ThinVec<RefPtr<nsISFVItem>>
+ );
+
+ fn get_items(&self) -> Result<thin_vec::ThinVec<RefPtr<nsISFVItem>>, nsresult> {
+ let items = self.items.borrow().clone();
+ Ok(items)
+ }
+
+ #[allow(non_snake_case)]
+ unsafe fn SetItems(&self, value: *const thin_vec::ThinVec<RefPtr<nsISFVItem>>) -> nsresult {
+ if value.is_null() {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *self.items.borrow_mut() = (*value).clone();
+ 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) }
+ }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVList, nsISFVSerialize)]
+#[refcnt = "nonatomic"]
+struct InitSFVList {
+ members: RefCell<thin_vec::ThinVec<RefPtr<nsISFVItemOrInnerList>>>,
+}
+
+impl SFVList {
+ fn new(members: &thin_vec::ThinVec<RefPtr<nsISFVItemOrInnerList>>) -> RefPtr<SFVList> {
+ SFVList::allocate(InitSFVList {
+ members: RefCell::new((*members).clone()),
+ })
+ }
+
+ xpcom_method!(
+ get_members => GetMembers() -> thin_vec::ThinVec<RefPtr<nsISFVItemOrInnerList>>
+ );
+
+ fn get_members(&self) -> Result<thin_vec::ThinVec<RefPtr<nsISFVItemOrInnerList>>, nsresult> {
+ Ok(self.members.borrow().clone())
+ }
+
+ #[allow(non_snake_case)]
+ unsafe fn SetMembers(
+ &self,
+ value: *const thin_vec::ThinVec<RefPtr<nsISFVItemOrInnerList>>,
+ ) -> nsresult {
+ if value.is_null() {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *self.members.borrow_mut() = (*value).clone();
+ 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();
+ for interface_entry in self.get_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 = ThinVec::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();
+
+ for interface_entry in self.get_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))
+ }
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsISFVDictionary, nsISFVSerialize)]
+#[refcnt = "nonatomic"]
+struct InitSFVDictionary {
+ 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: i64 = -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 = ThinVec::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/moz.build b/netwerk/base/moz.build
new file mode 100644
index 0000000000..a165bc5d0e
--- /dev/null
+++ b/netwerk/base/moz.build
@@ -0,0 +1,331 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ "mozIThirdPartyUtil.idl",
+ "nsIApplicationCache.idl",
+ "nsIApplicationCacheChannel.idl",
+ "nsIApplicationCacheContainer.idl",
+ "nsIApplicationCacheService.idl",
+ "nsIArrayBufferInputStream.idl",
+ "nsIAsyncStreamCopier.idl",
+ "nsIAsyncStreamCopier2.idl",
+ "nsIAsyncVerifyRedirectCallback.idl",
+ "nsIAuthInformation.idl",
+ "nsIAuthModule.idl",
+ "nsIAuthPrompt.idl",
+ "nsIAuthPrompt2.idl",
+ "nsIAuthPromptAdapterFactory.idl",
+ "nsIAuthPromptCallback.idl",
+ "nsIAuthPromptProvider.idl",
+ "nsIBackgroundFileSaver.idl",
+ "nsIBufferedStreams.idl",
+ "nsIByteRangeRequest.idl",
+ "nsICacheInfoChannel.idl",
+ "nsICachingChannel.idl",
+ "nsICancelable.idl",
+ "nsICaptivePortalService.idl",
+ "nsIChannel.idl",
+ "nsIChannelEventSink.idl",
+ "nsIChildChannel.idl",
+ "nsIClassifiedChannel.idl",
+ "nsIClassOfService.idl",
+ "nsIContentSniffer.idl",
+ "nsIDashboard.idl",
+ "nsIDashboardEventNotifier.idl",
+ "nsIDeprecationWarner.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",
+ "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",
+ "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 += [
+ "CaptivePortalService.h",
+ "Dashboard.h",
+ "DashboardTypes.h",
+ "DefaultURI.h",
+ "IOActivityMonitor.h",
+ "MemoryDownloader.h",
+ "NetworkConnectivityService.h",
+ "PartiallySeekableInputStream.h",
+ "Predictor.h",
+ "PrivateBrowsingChannel.h",
+ "RedirectChannelRegistrar.h",
+ "RequestContextService.h",
+ "SimpleChannelParent.h",
+ "SSLTokensCache.h",
+ "TCPFastOpen.h",
+ "ThrottleQueue.h",
+]
+
+UNIFIED_SOURCES += [
+ "ArrayBufferInputStream.cpp",
+ "BackgroundFileSaver.cpp",
+ "CaptivePortalService.cpp",
+ "Dashboard.cpp",
+ "DefaultURI.cpp",
+ "EventTokenBucket.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",
+ "PartiallySeekableInputStream.cpp",
+ "PollableEvent.cpp",
+ "Predictor.cpp",
+ "ProxyAutoConfig.cpp",
+ "RedirectChannelRegistrar.cpp",
+ "RequestContextService.cpp",
+ "SimpleBuffer.cpp",
+ "SimpleChannel.cpp",
+ "SimpleChannelParent.cpp",
+ "SSLTokensCache.cpp",
+ "TCPFastOpenLayer.cpp",
+ "ThrottleQueue.cpp",
+ "Tickler.cpp",
+ "TLSServerSocket.cpp",
+ "TRRLoadInfo.cpp",
+]
+
+if CONFIG["FUZZING"]:
+ SOURCES += [
+ "FuzzyLayer.cpp",
+ "FuzzySecurityInfo.cpp",
+ ]
+
+if CONFIG["FUZZING_INTERFACES"] and CONFIG["LIBFUZZER"]:
+ include("/tools/fuzzing/libfuzzer-flags.mozbuild")
+ SOURCES += [
+ "nsMediaFragmentURIParser.cpp",
+ "nsURLHelper.cpp",
+ "nsURLParsers.cpp",
+ ]
+ SOURCES["nsMediaFragmentURIParser.cpp"].flags += libfuzzer_flags
+ SOURCES["nsURLHelper.cpp"].flags += libfuzzer_flags
+ SOURCES["nsURLParsers.cpp"].flags += libfuzzer_flags
+else:
+ UNIFIED_SOURCES += [
+ "nsMediaFragmentURIParser.cpp",
+ "nsURLHelper.cpp",
+ "nsURLParsers.cpp",
+ ]
+
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ SOURCES += [
+ "nsURLHelperWin.cpp",
+ "ShutdownLayer.cpp",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ SOURCES += [
+ "nsURLHelperOSX.cpp",
+ ]
+else:
+ SOURCES += [
+ "nsURLHelperUnix.cpp",
+ ]
+
+# nsINetworkInfoService support.
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ SOURCES += [
+ "NetworkInfoServiceWindows.cpp",
+ "nsNetworkInfoService.cpp",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ SOURCES += [
+ "NetworkInfoServiceCocoa.cpp",
+ "nsNetworkInfoService.cpp",
+ ]
+elif CONFIG["OS_ARCH"] == "Linux":
+ SOURCES += [
+ "NetworkInfoServiceLinux.cpp",
+ "nsNetworkInfoService.cpp",
+ ]
+
+EXTRA_JS_MODULES += [
+ "NetUtil.jsm",
+]
+
+DIRS += ["mozurl", "rust-helper", "http-sfv"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/docshell/base",
+ "/dom/base",
+ "/netwerk/protocol/http",
+ "/netwerk/socket",
+ "/netwerk/url-classifier",
+ "/security/manager/ssl",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ LOCAL_INCLUDES += [
+ "/xpcom/base",
+ ]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/netwerk/base/mozIThirdPartyUtil.idl b/netwerk/base/mozIThirdPartyUtil.idl
new file mode 100644
index 0000000000..dccadd7b44
--- /dev/null
+++ b/netwerk/base/mozIThirdPartyUtil.idl
@@ -0,0 +1,232 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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..1b09595def
--- /dev/null
+++ b/netwerk/base/mozurl/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "mozurl"
+version = "0.0.1"
+authors = ["Nika Layzell <nika@thelayzells.com>"]
+
+[dependencies]
+url = "2.0"
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+xpcom = { path = "../../../xpcom/rust/xpcom" }
+uuid = { version = "0.8", 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..e48a0203cf
--- /dev/null
+++ b/netwerk/base/mozurl/MozURL.h
@@ -0,0 +1,231 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozURL_h__
+#define mozURL_h__
+
+#include "mozilla/net/MozURL_ffi.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
+
+namespace mozilla {
+namespace net {
+
+// This class provides a thread-safe, immutable URL parser.
+// As long as there is RefPtr to the object, you may use it on any thread.
+// The constructor is private. One can instantiate the object by
+// calling the Init() method as such:
+//
+// RefPtr<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); }
+ nsDependentCSubstring Ref() const { return mozurl_fragment(this); }
+ bool HasFragment() const { return mozurl_has_fragment(this); }
+ nsDependentCSubstring Directory() const { return mozurl_directory(this); }
+ nsDependentCSubstring PrePath() const { return mozurl_prepath(this); }
+ nsDependentCSubstring SpecNoRef() const { return mozurl_spec_no_ref(this); }
+
+ // This matches the definition of origins and base domains in nsIPrincipal for
+ // almost all URIs (some rare file:// URIs don't match and it would be hard to
+ // fix them). It definitely matches nsIPrincipal for URIs used in quota
+ // manager and there are checks in quota manager and its clients that prevent
+ // different definitions (see QuotaManager::IsPrincipalInfoValid).
+ // See also TestMozURL.cpp which enumerates a huge pile of URIs and checks
+ // that origin and base domain definitions are in sync.
+ void Origin(nsACString& aOrigin) const { mozurl_origin(this, &aOrigin); }
+ nsresult BaseDomain(nsACString& aBaseDomain) const {
+ return mozurl_base_domain(this, &aBaseDomain);
+ }
+
+ nsresult GetCommonBase(const MozURL* aOther, MozURL** aCommon) const {
+ return mozurl_common_base(this, aOther, aCommon);
+ }
+ nsresult GetRelative(const MozURL* aOther, nsACString* aRelative) const {
+ return mozurl_relative(this, aOther, aRelative);
+ }
+
+ size_t SizeOf() { return mozurl_sizeof(this); }
+
+ class Mutator {
+ public:
+ // Calling this method will result in the creation of a new MozURL that
+ // adopts the mutator's mURL.
+ // If any of the setters failed with an error code, that error code will be
+ // returned here. It will also return an error code if Finalize is called
+ // more than once on the Mutator.
+ nsresult Finalize(MozURL** aURL) {
+ nsresult rv = GetStatus();
+ if (NS_SUCCEEDED(rv)) {
+ mURL.forget(aURL);
+ } else {
+ *aURL = nullptr;
+ }
+ return rv;
+ }
+
+ // These setter methods will return a reference to `this` so that you may
+ // chain setter operations as such:
+ //
+ // RefPtr<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.
+ nsrefcnt AddRef() { return mozurl_addref(this); }
+ nsrefcnt Release() { return 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..92ea5f3ce0
--- /dev/null
+++ b/netwerk/base/mozurl/src/lib.rs
@@ -0,0 +1,563 @@
+/* -*- 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::nsrefcnt;
+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),
+ "moz-anno" => Some(443),
+ "android" => Some(443),
+ _ => None,
+ }
+}
+
+/// A slice into the backing string. This type is only valid as long as the
+/// MozURL which it was pulled from is valid. In C++, this type implicitly
+/// converts to a nsDependentCString, and is an implementation detail.
+///
+/// This type exists because, unlike &str, this type is safe to return over FFI.
+#[repr(C)]
+pub struct SpecSlice<'a> {
+ data: *const u8,
+ len: u32,
+ _marker: PhantomData<&'a [u8]>,
+}
+
+impl<'a> From<&'a str> for SpecSlice<'a> {
+ fn from(s: &'a str) -> SpecSlice<'a> {
+ assert!(s.len() < u32::max_value() as usize);
+ SpecSlice {
+ data: s.as_ptr(),
+ len: s.len() as u32,
+ _marker: PhantomData,
+ }
+ }
+}
+
+/// The MozURL reference-counted threadsafe URL type. This type intentionally
+/// implements no XPCOM interfaces, and all method calls are non-virtual.
+#[repr(C)]
+pub struct MozURL {
+ pub url: Url,
+ refcnt: AtomicRefcnt,
+}
+
+impl MozURL {
+ pub fn from_url(url: Url) -> RefPtr<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) -> nsrefcnt {
+ url.refcnt.inc()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn mozurl_release(url: &MozURL) -> nsrefcnt {
+ let rc = url.refcnt.dec();
+ if rc == 0 {
+ Box::from_raw(url as *const MozURL as *mut MozURL);
+ }
+ rc
+}
+
+// xpcom::RefPtr support
+unsafe impl RefCounted for MozURL {
+ unsafe fn addref(&self) {
+ mozurl_addref(self);
+ }
+ unsafe fn release(&self) {
+ mozurl_release(self);
+ }
+}
+
+// Allocate a new MozURL object with a RefCnt of 1, and store a pointer to it
+// into url.
+#[no_mangle]
+pub extern "C" fn mozurl_new(
+ result: &mut *const MozURL,
+ spec: &nsACString,
+ base: Option<&MozURL>,
+) -> nsresult {
+ *result = ptr::null_mut();
+
+ let spec = try_or_malformed!(str::from_utf8(spec));
+ let url = if let Some(base) = base {
+ try_or_malformed!(base.url.join(spec))
+ } else {
+ try_or_malformed!(parser().parse(spec))
+ };
+
+ MozURL::from_url(url).forget(result);
+ NS_OK
+}
+
+/// Allocate a new MozURL object which is a clone of the original, and store a
+/// pointer to it into newurl.
+#[no_mangle]
+pub extern "C" fn mozurl_clone(url: &MozURL, newurl: &mut *const MozURL) {
+ MozURL::from_url(url.url.clone()).forget(newurl);
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_spec(url: &MozURL) -> SpecSlice {
+ url.as_ref().into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_scheme(url: &MozURL) -> SpecSlice {
+ url.scheme().into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_username(url: &MozURL) -> SpecSlice {
+ if url.cannot_be_a_base() {
+ "".into()
+ } else {
+ url.username().into()
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_password(url: &MozURL) -> SpecSlice {
+ url.password().unwrap_or("").into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_host(url: &MozURL) -> SpecSlice {
+ url.host_str().unwrap_or("").into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_port(url: &MozURL) -> i32 {
+ // NOTE: Gecko uses -1 to represent the default port.
+ url.port().map(|p| p as i32).unwrap_or(-1)
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_real_port(url: &MozURL) -> i32 {
+ url.port()
+ .or_else(|| default_port(url.scheme()))
+ .map(|p| p as i32)
+ .unwrap_or(-1)
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_host_port(url: &MozURL) -> SpecSlice {
+ (&url[Position::BeforeHost..Position::BeforePath]).into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_filepath(url: &MozURL) -> SpecSlice {
+ url.path().into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_path(url: &MozURL) -> SpecSlice {
+ (&url[Position::BeforePath..]).into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_query(url: &MozURL) -> SpecSlice {
+ url.query().unwrap_or("").into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_fragment(url: &MozURL) -> SpecSlice {
+ url.fragment().unwrap_or("").into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_spec_no_ref(url: &MozURL) -> SpecSlice {
+ (&url[..Position::AfterQuery]).into()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_has_fragment(url: &MozURL) -> bool {
+ url.fragment().is_some()
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_directory(url: &MozURL) -> SpecSlice {
+ if let Some(position) = url.path().rfind('/') {
+ url.path()[..position + 1].into()
+ } else {
+ url.path().into()
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_prepath(url: &MozURL) -> SpecSlice {
+ (&url[..Position::BeforePath]).into()
+}
+
+fn get_origin(url: &MozURL) -> Option<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 = xpcom::services::get_ThirdPartyUtil().unwrap();
+
+ let scheme = nsCString::from(url.scheme());
+
+ let mut host_str = url.host_str().unwrap_or("");
+
+ if host_str.starts_with('[') && host_str.ends_with(']') {
+ host_str = &host_str[1..host_str.len() - 1];
+ }
+
+ let host = nsCString::from(host_str);
+
+ unsafe {
+ let mut string = nsCString::new();
+ third_party_util
+ .GetBaseDomainFromSchemeHost(&*scheme, &*host, &mut *string)
+ .to_result()?;
+
+ // We know that GetBaseDomainFromSchemeHost returns AUTF8String, so just
+ // use unwrap().
+ Ok(Some(String::from_utf8(string.to_vec()).unwrap()))
+ }
+ }
+ "ws" | "wss" => Ok(Some(url.as_ref().to_owned())),
+ "file" => {
+ if unsafe { Gecko_StrictFileOriginPolicy() } {
+ Ok(Some(url.path().to_owned()))
+ } else {
+ Ok(Some("UNIVERSAL_FILE_URI_ORIGIN".to_owned()))
+ }
+ }
+ "about" | "moz-safe-about" | "indexeddb" => Ok(Some(url.as_ref().to_owned())),
+ _ => Ok(None),
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_base_domain(url: &MozURL, base_domain: &mut nsACString) -> nsresult {
+ let base_domain_str = if !url.as_ref().starts_with("about:blank") {
+ match get_base_domain(url) {
+ Ok(domain) => domain,
+ Err(rv) => return rv,
+ }
+ } else {
+ None
+ };
+
+ let base_domain_str = base_domain_str.unwrap_or_else(|| {
+ // See the comment in mozurl_origin about why we return a new uuid for
+ // "moz-nullprincipals".
+ format!("{{{}}}", Uuid::new_v4())
+ });
+
+ let mut bd = nsCString::from(base_domain_str);
+ base_domain.take_from(&mut bd);
+
+ NS_OK
+}
+
+// Helper macro for debug asserting that we're the only reference to MozURL.
+macro_rules! debug_assert_mut {
+ ($e:expr) => {
+ debug_assert_eq!($e.refcnt.get(), 1, "Cannot mutate an aliased MozURL!");
+ };
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_scheme(url: &mut MozURL, scheme: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let scheme = try_or_malformed!(str::from_utf8(scheme));
+ try_or_malformed!(quirks::set_protocol(url, scheme));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_username(url: &mut MozURL, username: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let username = try_or_malformed!(str::from_utf8(username));
+ try_or_malformed!(quirks::set_username(url, username));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_password(url: &mut MozURL, password: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let password = try_or_malformed!(str::from_utf8(password));
+ try_or_malformed!(quirks::set_password(url, password));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_host_port(url: &mut MozURL, hostport: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let hostport = try_or_malformed!(str::from_utf8(hostport));
+ try_or_malformed!(quirks::set_host(url, hostport));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_hostname(url: &mut MozURL, host: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let host = try_or_malformed!(str::from_utf8(host));
+ try_or_malformed!(quirks::set_hostname(url, host));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_port_no(url: &mut MozURL, new_port: i32) -> nsresult {
+ debug_assert_mut!(url);
+ if url.cannot_be_a_base() {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ let port = match new_port {
+ new if new < 0 || u16::max_value() as i32 <= new => None,
+ new if Some(new as u16) == default_port(url.scheme()) => None,
+ new => Some(new as u16),
+ };
+ try_or_malformed!(url.set_port(port));
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_pathname(url: &mut MozURL, path: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let path = try_or_malformed!(str::from_utf8(path));
+ quirks::set_pathname(url, path);
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_query(url: &mut MozURL, query: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let query = try_or_malformed!(str::from_utf8(query));
+ quirks::set_search(url, query);
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_set_fragment(url: &mut MozURL, fragment: &nsACString) -> nsresult {
+ debug_assert_mut!(url);
+ let fragment = try_or_malformed!(str::from_utf8(fragment));
+ quirks::set_hash(url, fragment);
+ NS_OK
+}
+
+#[no_mangle]
+pub extern "C" fn mozurl_sizeof(url: &MozURL) -> usize {
+ debug_assert_mut!(url);
+ mem::size_of::<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..95c847cdb0
--- /dev/null
+++ b/netwerk/base/nsASocketHandler.h
@@ -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/. */
+
+#ifndef nsASocketHandler_h__
+#define nsASocketHandler_h__
+
+// socket handler used by nsISocketTransportService.
+// methods are only called on the socket thread.
+
+class nsASocketHandler : public nsISupports {
+ public:
+ nsASocketHandler()
+ : mCondition(NS_OK),
+ mPollFlags(0),
+ mPollTimeout(UINT16_MAX),
+ mIsPrivate(false) {}
+
+ //
+ // this condition variable will be checked to determine if the socket
+ // handler should be detached. it must only be accessed on the socket
+ // thread.
+ //
+ nsresult mCondition;
+
+ //
+ // these flags can only be modified on the socket transport thread.
+ // the socket transport service will check these flags before calling
+ // PR_Poll.
+ //
+ uint16_t mPollFlags;
+
+ //
+ // this value specifies the maximum amount of time in seconds that may be
+ // spent waiting for activity on this socket. if this timeout is reached,
+ // then OnSocketReady will be called with outFlags = -1.
+ //
+ // the default value for this member is UINT16_MAX, which disables the
+ // timeout error checking. (i.e., a timeout value of UINT16_MAX is
+ // never reached.)
+ //
+ uint16_t mPollTimeout;
+
+ bool mIsPrivate;
+
+ //
+ // called to service a socket
+ //
+ // params:
+ // socketRef - socket identifier
+ // fd - socket file descriptor
+ // outFlags - value of PR_PollDesc::out_flags after PR_Poll returns
+ // or -1 if a timeout occurred
+ //
+ virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) = 0;
+
+ //
+ // called when a socket is no longer under the control of the socket
+ // transport service. the socket handler may close the socket at this
+ // point. after this call returns, the handler will no longer be owned
+ // by the socket transport service.
+ //
+ virtual void OnSocketDetached(PRFileDesc* fd) = 0;
+
+ //
+ // called to determine if the socket is for a local peer.
+ // when used for server sockets, indicates if it only accepts local
+ // connections.
+ //
+ virtual void IsLocal(bool* aIsLocal) = 0;
+
+ //
+ // called to determine if this socket should not be terminated when Gecko
+ // is turned offline. This is mostly useful for the debugging server
+ // socket.
+ //
+ virtual void KeepWhenOffline(bool* aKeepWhenOffline) {
+ *aKeepWhenOffline = false;
+ }
+
+ //
+ // called when global pref for keepalive has changed.
+ //
+ virtual void OnKeepaliveEnabledPrefChange(bool aEnabled) {}
+
+ //
+ // returns the number of bytes sent/transmitted over the socket
+ //
+ virtual uint64_t ByteCountSent() = 0;
+ virtual uint64_t ByteCountReceived() = 0;
+};
+
+#endif // !nsASocketHandler_h__
diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.cpp b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp
new file mode 100644
index 0000000000..d61c00414c
--- /dev/null
+++ b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp
@@ -0,0 +1,285 @@
+/* -*- 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()
+ : mFlags(0),
+ mWaitingForRedirectCallback(false),
+ mCallbackInitiated(false),
+ mExpectedCallbacks(0),
+ mResult(NS_OK) {}
+
+nsAsyncRedirectVerifyHelper::~nsAsyncRedirectVerifyHelper() {
+ NS_ASSERTION(NS_FAILED(mResult) || mExpectedCallbacks == 0,
+ "Did not receive all required callbacks!");
+}
+
+nsresult nsAsyncRedirectVerifyHelper::Init(
+ nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags,
+ nsIEventTarget* mainThreadEventTarget, bool synchronize) {
+ LOG(
+ ("nsAsyncRedirectVerifyHelper::Init() "
+ "oldChan=%p newChan=%p",
+ oldChan, newChan));
+ mOldChan = oldChan;
+ mNewChan = newChan;
+ mFlags = flags;
+ mCallbackEventTarget = NS_IsMainThread() && mainThreadEventTarget
+ ? mainThreadEventTarget
+ : GetCurrentEventTarget();
+
+ 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())
+ : GetMainThreadEventTarget()->Dispatch(runnable.forget());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (synchronize) {
+ if (!SpinEventLoopUntil([&]() { 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..7f0a16194f
--- /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();
+
+ /*
+ * 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;
+ bool mWaitingForRedirectCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackEventTarget;
+ bool mCallbackInitiated;
+ int32_t mExpectedCallbacks;
+ nsresult mResult; // value passed to callback
+
+ void InitCallback();
+
+ /**
+ * Calls back to |oldChan| as described in Init()
+ */
+ void ExplicitCallback(nsresult result);
+
+ private:
+ ~nsAsyncRedirectVerifyHelper();
+
+ bool IsOldChannelCanceled();
+};
+
+/*
+ * Helper to make the call-stack handle some control-flow for us
+ */
+class nsAsyncRedirectAutoCallback {
+ public:
+ explicit nsAsyncRedirectAutoCallback(
+ nsIAsyncVerifyRedirectCallback* aCallback)
+ : mCallback(aCallback) {
+ mResult = NS_OK;
+ }
+ ~nsAsyncRedirectAutoCallback() {
+ if (mCallback) mCallback->OnRedirectVerifyCallback(mResult);
+ }
+ /*
+ * Call this is you want it to call back with a different result-code
+ */
+ void SetResult(nsresult aRes) { mResult = aRes; }
+ /*
+ * Call this is you want to avoid the callback
+ */
+ void DontCallback() { mCallback = nullptr; }
+
+ private:
+ nsIAsyncVerifyRedirectCallback* mCallback;
+ nsresult mResult;
+};
+
+} // namespace net
+} // namespace mozilla
+#endif
diff --git a/netwerk/base/nsAsyncStreamCopier.cpp b/netwerk/base/nsAsyncStreamCopier.cpp
new file mode 100644
index 0000000000..eea2266ca6
--- /dev/null
+++ b/netwerk/base/nsAsyncStreamCopier.cpp
@@ -0,0 +1,392 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAsyncStreamCopier.h"
+#include "nsIOService.h"
+#include "nsIEventTarget.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsIBufferedStreams.h"
+#include "nsIRequestObserver.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+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(GetCurrentEventTarget()) {}
+
+ 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()
+ : mLock("nsAsyncStreamCopier.mLock"),
+ mMode(NS_ASYNCCOPY_VIA_READSEGMENTS),
+ mChunkSize(nsIOService::gDefaultSegmentSize),
+ mStatus(NS_OK),
+ mIsPending(false),
+ mCloseSource{false},
+ mCloseSink{false},
+ mShouldSniffBuffering(false) {
+ 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::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; }
+
+nsresult nsAsyncStreamCopier::InitInternal(nsIInputStream* source,
+ nsIOutputStream* sink,
+ nsIEventTarget* target,
+ uint32_t chunkSize, bool closeSource,
+ bool closeSink) {
+ NS_ASSERTION(!mSource && !mSink, "Init() called more than once");
+ if (chunkSize == 0) {
+ chunkSize = nsIOService::gDefaultSegmentSize;
+ }
+ mChunkSize = chunkSize;
+
+ mSource = source;
+ mSink = sink;
+ mCloseSource = closeSource;
+ mCloseSink = closeSink;
+
+ if (target) {
+ mTarget = target;
+ } else {
+ nsresult rv;
+ mTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIAsyncStreamCopier
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Init(nsIInputStream* source, nsIOutputStream* sink,
+ nsIEventTarget* target, bool sourceBuffered,
+ bool sinkBuffered, uint32_t chunkSize,
+ bool closeSource, bool closeSink) {
+ NS_ASSERTION(sourceBuffered || sinkBuffered,
+ "at least one stream must be buffered");
+ mMode = sourceBuffered ? NS_ASYNCCOPY_VIA_READSEGMENTS
+ : NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+
+ return InitInternal(source, sink, target, chunkSize, closeSource, closeSink);
+}
+
+//-----------------------------------------------------------------------------
+// nsIAsyncStreamCopier2
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::Init(nsIInputStream* source, nsIOutputStream* sink,
+ nsIEventTarget* target, uint32_t chunkSize,
+ bool closeSource, bool closeSink) {
+ mShouldSniffBuffering = true;
+
+ return InitInternal(source, sink, target, chunkSize, closeSource, closeSink);
+}
+
+/**
+ * Detect whether the input or the output stream is buffered,
+ * bufferize one of them if neither is buffered.
+ */
+nsresult nsAsyncStreamCopier::ApplyBufferingPolicy() {
+ // This function causes I/O, it must not be executed on the main
+ // thread.
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (NS_OutputStreamIsBuffered(mSink)) {
+ // Sink is buffered, no need to perform additional buffering
+ mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+ return NS_OK;
+ }
+ if (NS_InputStreamIsBuffered(mSource)) {
+ // Source is buffered, no need to perform additional buffering
+ mMode = NS_ASYNCCOPY_VIA_READSEGMENTS;
+ return NS_OK;
+ }
+
+ // No buffering, let's buffer the sink
+ nsresult rv;
+ nsCOMPtr<nsIBufferedOutputStream> sink =
+ do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = sink->Init(mSink, mChunkSize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mMode = NS_ASYNCCOPY_VIA_WRITESEGMENTS;
+ mSink = sink;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Both nsIAsyncStreamCopier and nsIAsyncStreamCopier2
+
+NS_IMETHODIMP
+nsAsyncStreamCopier::AsyncCopy(nsIRequestObserver* observer, nsISupports* ctx) {
+ LOG(("nsAsyncStreamCopier::AsyncCopy [this=%p observer=%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.
+ 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..fe3a15c46d
--- /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;
+
+ mozilla::Mutex mLock;
+
+ nsAsyncCopyMode mMode;
+ uint32_t mChunkSize;
+ nsresult mStatus;
+ bool mIsPending;
+ bool mCloseSource;
+ bool mCloseSink;
+ bool mShouldSniffBuffering;
+
+ friend class ProceedWithAsyncCopy;
+ friend class AsyncApplyBufferingPolicyEvent;
+};
+
+#endif // !nsAsyncStreamCopier_h__
diff --git a/netwerk/base/nsAuthInformationHolder.cpp b/netwerk/base/nsAuthInformationHolder.cpp
new file mode 100644
index 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..866586584d
--- /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 nsCString& aAuthType)
+ : mFlags(aFlags), mRealm(aRealm), mAuthType(aAuthType) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTHINFORMATION
+
+ const nsString& User() const { return mUser; }
+ const nsString& Password() const { return mPassword; }
+ const nsString& Domain() const { return mDomain; }
+
+ /**
+ * This method can be used to initialize the username when the
+ * ONLY_PASSWORD flag is set.
+ */
+ void SetUserInternal(const nsString& aUsername) { mUser = aUsername; }
+
+ private:
+ nsString mUser;
+ nsString mPassword;
+ nsString mDomain;
+
+ uint32_t mFlags;
+ nsString mRealm;
+ nsCString mAuthType;
+};
+
+#endif
diff --git a/netwerk/base/nsBase64Encoder.cpp b/netwerk/base/nsBase64Encoder.cpp
new file mode 100644
index 0000000000..2757900b4d
--- /dev/null
+++ b/netwerk/base/nsBase64Encoder.cpp
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "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::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..877d20708e
--- /dev/null
+++ b/netwerk/base/nsBaseChannel.cpp
@@ -0,0 +1,978 @@
+/* -*- 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 "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),
+ mPumpingData(false),
+ mLoadFlags(LOAD_NORMAL),
+ mQueriedProgressSink(true),
+ mSynthProgressEvents(false),
+ mAllowThreadRetargeting(true),
+ mWaitingOnAsyncRedirect(false),
+ mOpenRedirectChannel(false),
+ mRedirectFlags{0},
+ mStatus(NS_OK),
+ mContentDispositionHint(UINT32_MAX),
+ mContentLength(-1),
+ mWasOpened(false),
+ mCanceled(false) {
+ 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);
+
+ nsCOMPtr<nsIPrincipal> uriPrincipal;
+ nsIScriptSecurityManager* sm = nsContentUtils::GetSecurityManager();
+ sm->GetChannelURIPrincipal(this, getter_AddRefs(uriPrincipal));
+ bool isInternalRedirect =
+ (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE));
+
+ // nsBaseChannel hst no thing to do with HttpBaseChannel, we would not care
+ // about referrer and remote address in this case
+ nsCOMPtr<nsIRedirectHistoryEntry> entry =
+ new net::nsRedirectHistoryEntry(uriPrincipal, nullptr, ""_ns);
+
+ newLoadInfo->AppendRedirectHistoryEntry(entry, 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::PushStreamConverter(const char* fromType,
+ const char* toType,
+ bool invalidatesContentLength,
+ nsIStreamListener** result) {
+ NS_ASSERTION(mListener, "no listener");
+
+ nsresult rv;
+ nsCOMPtr<nsIStreamConverterService> scs =
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStreamListener> converter;
+ rv = scs->AsyncConvertData(fromType, toType, mListener, nullptr,
+ getter_AddRefs(converter));
+ if (NS_SUCCEEDED(rv)) {
+ mListener = converter;
+ if (invalidatesContentLength) mContentLength = -1;
+ if (result) {
+ *result = nullptr;
+ converter.swap(*result);
+ }
+ }
+ return rv;
+}
+
+nsresult nsBaseChannel::BeginPumpingData() {
+ nsresult rv;
+
+ rv = BeginAsyncRead(this, getter_AddRefs(mRequest));
+ if (NS_SUCCEEDED(rv)) {
+ 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<nsIEventTarget> 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);
+ nsCOMPtr<nsISerialEventTarget> serialTarget(do_QueryInterface(target));
+ MOZ_ASSERT(serialTarget);
+
+ promise->Then(
+ serialTarget, __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);
+ if (classifier) {
+ classifier->Start();
+ } else {
+ Cancel(NS_ERROR_OUT_OF_MEMORY);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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::Cancel(nsresult status) {
+ // Ignore redundant cancelation
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ mStatus = 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(nsISupports** aSecurityInfo) {
+ nsCOMPtr<nsISupports> securityInfo(mSecurityInfo);
+ securityInfo.forget(aSecurityInfo);
+ 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 PushStreamConverter and the StreamListener methods. However, since
+ // this typically introduces a reference cycle between this and the listener,
+ // we need to be sure to break the reference if this method does not succeed.
+ mListener = listener;
+
+ // 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);
+
+ nsAutoCString scheme;
+ mURI->GetScheme(scheme);
+
+ if (mPump && !scheme.EqualsLiteral("ftp")) {
+ // If our content type is unknown, use the content type
+ // sniffer. If the sniffer is not available for some reason, then we just
+ // keep going as-is.
+ if (NS_SUCCEEDED(mStatus) &&
+ mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
+ mPump->PeekStream(CallUnknownTypeSniffer, static_cast<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;
+ 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(nsIEventTarget* aEventTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_TRUE(mRequest, 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(nsIEventTarget** 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::GetCanceled(bool* aCanceled) {
+ *aCanceled = mCanceled;
+ return NS_OK;
+}
+
+void nsBaseChannel::SetupNeckoTarget() {
+ mNeckoTarget =
+ nsContentUtils::GetEventTargetByLoadInfo(mLoadInfo, TaskCategory::Other);
+}
diff --git a/netwerk/base/nsBaseChannel.h b/netwerk/base/nsBaseChannel.h
new file mode 100644
index 0000000000..c6e12d9b33
--- /dev/null
+++ b/netwerk/base/nsBaseChannel.h
@@ -0,0 +1,306 @@
+/* -*- 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/net/NeckoTargetHolder.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/UniquePtr.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsHashPropertyBag.h"
+#include "nsInputStreamPump.h"
+
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIStreamListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIProgressEventSink.h"
+#include "nsITransport.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "mozilla/net/PrivateBrowsingChannel.h"
+#include "nsThreadUtils.h"
+
+class nsIInputStream;
+
+//-----------------------------------------------------------------------------
+// nsBaseChannel is designed to be subclassed. The subclass is responsible for
+// implementing the OpenContentStream method, which will be called by the
+// nsIChannel::AsyncOpen and nsIChannel::Open implementations.
+//
+// nsBaseChannel implements nsIInterfaceRequestor to provide a convenient way
+// for subclasses to query both the nsIChannel::notificationCallbacks and
+// nsILoadGroup::notificationCallbacks for supported interfaces.
+//
+// nsBaseChannel implements nsITransportEventSink to support progress & status
+// notifications generated by the transport layer.
+
+class nsBaseChannel
+ : public nsHashPropertyBag,
+ public nsIChannel,
+ public nsIThreadRetargetableRequest,
+ public nsIInterfaceRequestor,
+ public nsITransportEventSink,
+ public nsIAsyncVerifyRedirectCallback,
+ public mozilla::net::PrivateBrowsingChannel<nsBaseChannel>,
+ public mozilla::net::NeckoTargetHolder,
+ protected nsIStreamListener,
+ protected nsIThreadRetargetableStreamListener {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsBaseChannel();
+
+ protected:
+ // -----------------------------------------------
+ // Methods to be implemented by the derived class:
+
+ virtual ~nsBaseChannel();
+
+ using BlockingPromise = mozilla::MozPromise<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, it may provide a request object which may be used to
+ // suspend, resume, and cancel the underlying request.
+ virtual nsresult BeginAsyncRead(nsIStreamListener* listener,
+ nsIRequest** request) {
+ 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.
+ nsISupports* SecurityInfo() { return mSecurityInfo; }
+ void SetSecurityInfo(nsISupports* info) { mSecurityInfo = info; }
+
+ // Test the load flags
+ bool HasLoadFlag(uint32_t flag) { return (mLoadFlags & flag) != 0; }
+
+ // This is a short-cut to calling nsIRequest::IsPending()
+ virtual bool Pending() const {
+ return mPumpingData || mWaitingOnAsyncRedirect;
+ }
+
+ // Helper function for querying the channel's notification callbacks.
+ template <class T>
+ void GetCallback(nsCOMPtr<T>& result) {
+ GetInterface(NS_GET_TEMPLATE_IID(T), getter_AddRefs(result));
+ }
+
+ // If a subclass does not want to feed transport-layer progress events to the
+ // base channel via nsITransportEventSink, then it may set this flag to cause
+ // the base channel to synthesize progress events when it receives data from
+ // the content stream. By default, progress events are not synthesized.
+ void EnableSynthesizedProgressEvents(bool enable) {
+ mSynthProgressEvents = enable;
+ }
+
+ // Some subclasses may wish to manually insert a stream listener between this
+ // and the channel's listener. The following methods make that possible.
+ void SetStreamListener(nsIStreamListener* listener) { mListener = listener; }
+ nsIStreamListener* StreamListener() { return mListener; }
+
+ // Pushes a new stream converter in front of the channel's stream listener.
+ // The fromType and toType values are passed to nsIStreamConverterService's
+ // AsyncConvertData method. If invalidatesContentLength is true, then the
+ // channel's content-length property will be assigned a value of -1. This is
+ // necessary when the converter changes the length of the resulting data
+ // stream, which is almost always the case for a "stream converter" ;-)
+ // This function optionally returns a reference to the new converter.
+ nsresult PushStreamConverter(const char* fromType, const char* toType,
+ bool invalidatesContentLength = true,
+ nsIStreamListener** converter = nullptr);
+
+ protected:
+ void DisallowThreadRetargeting() { mAllowThreadRetargeting = false; }
+
+ 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;
+ bool mPumpingData;
+ nsCOMPtr<nsIProgressEventSink> mProgressSink;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsCString mContentType;
+ nsCString mContentCharset;
+ uint32_t mLoadFlags;
+ bool mQueriedProgressSink;
+ bool mSynthProgressEvents;
+ bool mAllowThreadRetargeting;
+ bool mWaitingOnAsyncRedirect;
+ bool mOpenRedirectChannel;
+ uint32_t mRedirectFlags;
+
+ protected:
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsresult mStatus;
+ uint32_t mContentDispositionHint;
+ mozilla::UniquePtr<nsString> mContentDispositionFilename;
+ int64_t mContentLength;
+ bool mWasOpened;
+ bool mCanceled;
+
+ 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..e2d7cfc8d5
--- /dev/null
+++ b/netwerk/base/nsBaseContentStream.cpp
@@ -0,0 +1,124 @@
+/* -*- 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::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..b5deb0c854
--- /dev/null
+++ b/netwerk/base/nsBufferedStreams.cpp
@@ -0,0 +1,1191 @@
+/* -*- 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 mozilla::DebugOnly;
+using mozilla::Maybe;
+using mozilla::MutexAutoLock;
+using mozilla::Nothing;
+using mozilla::Some;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsBufferedStream
+
+nsBufferedStream::nsBufferedStream()
+ : mBufferSize(0),
+ mBuffer(nullptr),
+ mBufferStartOffset(0),
+ mCursor(0),
+ mFillPoint(0),
+ mStream(nullptr),
+ mBufferDisabled(false),
+ mEOF(false),
+ mGetBufferCount(0) {}
+
+nsBufferedStream::~nsBufferedStream() { Close(); }
+
+NS_IMPL_ISUPPORTS(nsBufferedStream, nsITellableStream, nsISeekableStream)
+
+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;
+ 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;
+ 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)
+
+nsBufferedInputStream::nsBufferedInputStream()
+ : nsBufferedStream(),
+ mMutex("nsBufferedInputStream::mMutex"),
+ mIsIPCSerializable(true),
+ mIsAsyncInputStream(false),
+ mIsCloneableInputStream(false),
+ mIsInputStreamLength(false),
+ mIsAsyncInputStreamLength(false) {}
+
+nsresult nsBufferedInputStream::Create(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ 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::Read(char* buf, uint32_t count, uint32_t* result) {
+ if (mBufferDisabled) {
+ if (!mStream) {
+ *result = 0;
+ return NS_OK;
+ }
+ nsresult rv = Source()->Read(buf, count, result);
+ if (NS_SUCCEEDED(rv)) {
+ mBufferStartOffset += *result; // so nsBufferedStream::Tell works
+ if (*result == 0) {
+ mEOF = true;
+ }
+ }
+ return rv;
+ }
+
+ return ReadSegments(NS_CopySegmentToBuffer, buf, count, result);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* result) {
+ *result = 0;
+
+ if (!mStream) {
+ return NS_OK;
+ }
+
+ nsresult rv = NS_OK;
+ while (count > 0) {
+ uint32_t amt = std::min(count, mFillPoint - mCursor);
+ if (amt > 0) {
+ uint32_t read = 0;
+ rv = writer(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 (NS_FAILED(rv) || mFillPoint == mCursor) {
+ break;
+ }
+ }
+ }
+ return (*result > 0) ? NS_OK : rv;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::IsNonBlocking(bool* aNonBlocking) {
+ if (mStream) {
+ return Source()->IsNonBlocking(aNonBlocking);
+ }
+ return NS_ERROR_NOT_INITIALIZED;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::Fill() {
+ if (mBufferDisabled) {
+ return NS_OK;
+ }
+ NS_ENSURE_TRUE(mStream, NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv;
+ int32_t rem = int32_t(mFillPoint - mCursor);
+ if (rem > 0) {
+ // slide the remainder down to the start of the buffer
+ // |<------------->|<--rem-->|<--->|
+ // b c f s
+ memcpy(mBuffer, mBuffer + mCursor, rem);
+ }
+ mBufferStartOffset += mCursor;
+ mFillPoint = rem;
+ mCursor = 0;
+
+ uint32_t amt;
+ rv = Source()->Read(mBuffer + mFillPoint, mBufferSize - mFillPoint, &amt);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (amt == 0) {
+ mEOF = true;
+ }
+
+ mFillPoint += amt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(char*)
+nsBufferedInputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) {
+ NS_ASSERTION(mGetBufferCount == 0, "nested GetBuffer!");
+ if (mGetBufferCount != 0) {
+ return nullptr;
+ }
+
+ if (mBufferDisabled) {
+ return nullptr;
+ }
+
+ char* buf = mBuffer + mCursor;
+ uint32_t rem = mFillPoint - mCursor;
+ if (rem == 0) {
+ if (NS_FAILED(Fill())) {
+ return nullptr;
+ }
+ buf = mBuffer + mCursor;
+ rem = mFillPoint - mCursor;
+ }
+
+ uint32_t mod = (NS_PTR_TO_INT32(buf) & aAlignMask);
+ if (mod) {
+ uint32_t pad = aAlignMask + 1 - mod;
+ if (pad > rem) {
+ return nullptr;
+ }
+
+ memset(buf, 0, pad);
+ mCursor += pad;
+ buf += pad;
+ rem -= pad;
+ }
+
+ if (aLength > rem) {
+ return nullptr;
+ }
+ mGetBufferCount++;
+ return buf;
+}
+
+NS_IMETHODIMP_(void)
+nsBufferedInputStream::PutBuffer(char* aBuffer, uint32_t aLength) {
+ NS_ASSERTION(mGetBufferCount == 1, "stray PutBuffer!");
+ if (--mGetBufferCount != 0) {
+ return;
+ }
+
+ NS_ASSERTION(mCursor + aLength <= mFillPoint, "PutBuffer botch");
+ mCursor += aLength;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::DisableBuffering() {
+ NS_ASSERTION(!mBufferDisabled, "redundant call to DisableBuffering!");
+ NS_ASSERTION(mGetBufferCount == 0,
+ "DisableBuffer call between GetBuffer and PutBuffer!");
+ if (mGetBufferCount != 0) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Empty the buffer so nsBufferedStream::Tell works.
+ mBufferStartOffset += mCursor;
+ mFillPoint = mCursor = 0;
+ mBufferDisabled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::EnableBuffering() {
+ NS_ASSERTION(mBufferDisabled, "gratuitous call to EnableBuffering!");
+ mBufferDisabled = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::GetUnbufferedStream(nsISupports** aStream) {
+ // Empty the buffer so subsequent i/o trumps any buffered data.
+ mBufferStartOffset += mCursor;
+ mFillPoint = mCursor = 0;
+
+ nsCOMPtr<nsISupports> stream = mStream;
+ stream.forget(aStream);
+ return NS_OK;
+}
+
+void nsBufferedInputStream::Serialize(
+ InputStreamParams& aParams, FileDescriptorArray& aFileDescriptors,
+ bool aDelayedStart, uint32_t aMaxSize, uint32_t* aSizeUsed,
+ mozilla::ipc::ParentToChildStreamActorManager* aManager) {
+ SerializeInternal(aParams, aFileDescriptors, aDelayedStart, aMaxSize,
+ aSizeUsed, aManager);
+}
+
+void nsBufferedInputStream::Serialize(
+ InputStreamParams& aParams, FileDescriptorArray& aFileDescriptors,
+ bool aDelayedStart, uint32_t aMaxSize, uint32_t* aSizeUsed,
+ mozilla::ipc::ChildToParentStreamActorManager* aManager) {
+ SerializeInternal(aParams, aFileDescriptors, aDelayedStart, aMaxSize,
+ aSizeUsed, aManager);
+}
+
+template <typename M>
+void nsBufferedInputStream::SerializeInternal(
+ InputStreamParams& aParams, FileDescriptorArray& aFileDescriptors,
+ bool aDelayedStart, uint32_t aMaxSize, uint32_t* aSizeUsed, M* aManager) {
+ MOZ_ASSERT(aSizeUsed);
+ *aSizeUsed = 0;
+
+ BufferedInputStreamParams params;
+
+ if (mStream) {
+ nsCOMPtr<nsIInputStream> stream = do_QueryInterface(mStream);
+ MOZ_ASSERT(stream);
+
+ InputStreamParams wrappedParams;
+ InputStreamHelper::SerializeInputStream(stream, wrappedParams,
+ aFileDescriptors, aDelayedStart,
+ aMaxSize, aSizeUsed, aManager);
+
+ params.optionalStream().emplace(wrappedParams);
+ }
+
+ params.bufferSize() = mBufferSize;
+
+ aParams = params;
+}
+
+bool nsBufferedInputStream::Deserialize(
+ const InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors) {
+ if (aParams.type() != InputStreamParams::TBufferedInputStreamParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const BufferedInputStreamParams& params =
+ aParams.get_BufferedInputStreamParams();
+ const Maybe<InputStreamParams>& wrappedParams = params.optionalStream();
+
+ nsCOMPtr<nsIInputStream> stream;
+ if (wrappedParams.isSome()) {
+ stream = InputStreamHelper::DeserializeInputStream(wrappedParams.ref(),
+ aFileDescriptors);
+ 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 (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;
+
+ // 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) {
+ 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(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ 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() {
+ 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::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;
+ }
+
+ 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;
+ }
+ 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;
+}
+
+static nsresult nsReadFromInputStream(nsIOutputStream* outStr, void* closure,
+ char* toRawSegment, uint32_t offset,
+ uint32_t count, uint32_t* readCount) {
+ nsIInputStream* fromStream = (nsIInputStream*)closure;
+ return fromStream->Read(toRawSegment, count, readCount);
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::WriteFrom(nsIInputStream* inStr, uint32_t count,
+ uint32_t* _retval) {
+ return WriteSegments(nsReadFromInputStream, inStr, count, _retval);
+}
+
+NS_IMETHODIMP
+nsBufferedOutputStream::WriteSegments(nsReadSegmentFun reader, void* closure,
+ uint32_t count, uint32_t* _retval) {
+ *_retval = 0;
+ nsresult rv;
+ while (count > 0) {
+ uint32_t left = std::min(count, mBufferSize - mCursor);
+ if (left == 0) {
+ rv = Flush();
+ if (NS_FAILED(rv)) {
+ return (*_retval > 0) ? NS_OK : rv;
+ }
+
+ continue;
+ }
+
+ uint32_t read = 0;
+ rv = reader(this, closure, mBuffer + mCursor, *_retval, left, &read);
+
+ if (NS_FAILED(rv)) { // If we have 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;
+ }
+
+ 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..93868d3c20
--- /dev/null
+++ b/netwerk/base/nsBufferedStreams.h
@@ -0,0 +1,167 @@
+/* -*- 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"
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsBufferedStream : public nsISeekableStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSITELLABLESTREAM
+
+ nsBufferedStream();
+
+ 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;
+ char* mBuffer;
+
+ // mBufferStartOffset is the offset relative to the start of mStream.
+ int64_t mBufferStartOffset;
+
+ // mCursor is the read cursor for input streams, or write cursor for
+ // output streams, and is relative to mBufferStartOffset.
+ uint32_t mCursor;
+
+ // mFillPoint is the amount available in the buffer for input streams,
+ // or the high watermark of bytes written into the buffer, and therefore
+ // is relative to mBufferStartOffset.
+ uint32_t mFillPoint;
+
+ nsCOMPtr<nsISupports> mStream; // cast to appropriate subclass
+
+ bool mBufferDisabled;
+ bool mEOF; // True if mStream is at EOF
+ uint8_t mGetBufferCount;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+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();
+
+ static nsresult Create(nsISupports* aOuter, 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;
+
+ template <typename M>
+ void SerializeInternal(mozilla::ipc::InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors,
+ bool aDelayedStart, uint32_t aMaxSize,
+ uint32_t* aSizeUsed, M* aManager);
+
+ NS_IMETHOD Fill() override;
+ NS_IMETHOD Flush() override { return NS_OK; } // no-op for input streams
+
+ mozilla::Mutex mMutex;
+
+ // This value is protected by mutex.
+ nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback;
+
+ // This value is protected by mutex.
+ nsCOMPtr<nsIInputStreamLengthCallback> mAsyncInputStreamLengthCallback;
+
+ bool mIsIPCSerializable;
+ bool mIsAsyncInputStream;
+ bool mIsCloneableInputStream;
+ bool mIsInputStreamLength;
+ bool mIsAsyncInputStreamLength;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsBufferedOutputStream : public nsBufferedStream,
+ public nsISafeOutputStream,
+ public nsIBufferedOutputStream,
+ public nsIStreamBufferAccess {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSISAFEOUTPUTSTREAM
+ NS_DECL_NSIBUFFEREDOUTPUTSTREAM
+ NS_DECL_NSISTREAMBUFFERACCESS
+
+ nsBufferedOutputStream() : nsBufferedStream() {}
+
+ static nsresult Create(nsISupports* aOuter, 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..529171a347
--- /dev/null
+++ b/netwerk/base/nsDNSPrefetch.cpp
@@ -0,0 +1,162 @@
+/* -*- 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 "nsIDNSListener.h"
+#include "nsIDNSService.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsICancelable.h"
+#include "nsIURI.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Preferences.h"
+
+static nsIDNSService* sDNSService = nullptr;
+
+nsresult nsDNSPrefetch::Initialize(nsIDNSService* aDNSService) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_IF_RELEASE(sDNSService);
+ sDNSService = aDNSService;
+ NS_IF_ADDREF(sDNSService);
+ return NS_OK;
+}
+
+nsresult nsDNSPrefetch::Shutdown() {
+ NS_IF_RELEASE(sDNSService);
+ return NS_OK;
+}
+
+nsDNSPrefetch::nsDNSPrefetch(nsIURI* aURI,
+ mozilla::OriginAttributes& aOriginAttributes,
+ nsIRequest::TRRMode aTRRMode,
+ nsIDNSListener* aListener, bool storeTiming)
+ : mOriginAttributes(aOriginAttributes),
+ mStoreTiming(storeTiming),
+ mTRRMode(aTRRMode),
+ mListener(do_GetWeakReference(aListener)) {
+ aURI->GetAsciiHost(mHostname);
+}
+
+nsDNSPrefetch::nsDNSPrefetch(nsIURI* aURI,
+ mozilla::OriginAttributes& aOriginAttributes,
+ nsIRequest::TRRMode aTRRMode)
+ : mOriginAttributes(aOriginAttributes),
+ mStoreTiming(false),
+ mTRRMode(aTRRMode),
+ mListener(nullptr) {
+ aURI->GetAsciiHost(mHostname);
+}
+
+nsresult nsDNSPrefetch::Prefetch(uint32_t flags) {
+ if (mHostname.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;
+
+ if (!sDNSService) return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsICancelable> tmpOutstanding;
+
+ if (mStoreTiming) mStartTimestamp = mozilla::TimeStamp::Now();
+ // If AsyncResolve fails, for example because prefetching is disabled,
+ // then our timing will be useless. However, in such a case,
+ // mEndTimestamp will be a null timestamp and callers should check
+ // TimingsValid() before using the timing.
+ nsCOMPtr<nsIEventTarget> target = mozilla::GetCurrentEventTarget();
+
+ 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(bool refreshDNS) {
+ return Prefetch(nsIDNSService::RESOLVE_PRIORITY_LOW |
+ (refreshDNS ? nsIDNSService::RESOLVE_BYPASS_CACHE : 0));
+}
+
+nsresult nsDNSPrefetch::PrefetchMedium(bool refreshDNS) {
+ return Prefetch(nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+ (refreshDNS ? nsIDNSService::RESOLVE_BYPASS_CACHE : 0));
+}
+
+nsresult nsDNSPrefetch::PrefetchHigh(bool refreshDNS) {
+ return Prefetch(refreshDNS ? nsIDNSService::RESOLVE_BYPASS_CACHE : 0);
+}
+
+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::GetCurrentEventTarget();
+ uint32_t 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));
+ return sDNSService->AsyncResolveNative(
+ mHostname, nsIDNSService::RESOLVE_TYPE_HTTPSSVC, flags, nullptr, 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..03d6305b71
--- /dev/null
+++ b/netwerk/base/nsDNSPrefetch.h
@@ -0,0 +1,68 @@
+/* -*- 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"
+
+class nsIURI;
+class nsIDNSService;
+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(bool refreshDNS = false);
+ nsresult PrefetchMedium(bool refreshDNS = false);
+ nsresult PrefetchLow(bool refreshDNS = false);
+
+ nsresult FetchHTTPSSVC(
+ bool aRefreshDNS, bool aPrefetch,
+ std::function<void(nsIDNSHTTPSSVCRecord*)>&& aCallback);
+
+ private:
+ nsCString mHostname;
+ mozilla::OriginAttributes mOriginAttributes;
+ bool mStoreTiming;
+ nsIRequest::TRRMode mTRRMode;
+ mozilla::TimeStamp mStartTimestamp;
+ mozilla::TimeStamp mEndTimestamp;
+ nsWeakPtr mListener;
+
+ nsresult Prefetch(uint32_t flags);
+};
+
+#endif
diff --git a/netwerk/base/nsDirectoryIndexStream.cpp b/netwerk/base/nsDirectoryIndexStream.cpp
new file mode 100644
index 0000000000..9ae629a6bd
--- /dev/null
+++ b/netwerk/base/nsDirectoryIndexStream.cpp
@@ -0,0 +1,321 @@
+/* -*- 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"
+#ifdef THREADSAFE_I18N
+# include "nsCollationCID.h"
+# include "nsICollation.h"
+#endif
+#include "nsIFile.h"
+#include "nsURLHelper.h"
+#include "nsNativeCharsetUtils.h"
+
+// NOTE: This runs on the _file transport_ thread.
+// The problem is that now that we're actually doing something with the data,
+// we want to do stuff like i18n sorting. However, none of the collation stuff
+// is threadsafe.
+// So THIS CODE IS ASCII ONLY!!!!!!!! This is no worse than the current
+// behaviour, though. See bug 99382.
+// When this is fixed, #define THREADSAFE_I18N to get this code working
+
+//#define THREADSAFE_I18N
+
+using namespace mozilla;
+static LazyLogModule gLog("nsDirectoryIndexStream");
+
+nsDirectoryIndexStream::nsDirectoryIndexStream()
+ : mOffset(0), mStatus(NS_OK), mPos(0) {
+ MOZ_LOG(gLog, LogLevel::Debug, ("nsDirectoryIndexStream[%p]: created", this));
+}
+
+static int compare(nsIFile* aElement1, nsIFile* aElement2, void* aData) {
+ if (!NS_IsNativeUTF8()) {
+ // don't check for errors, because we can't report them anyway
+ nsAutoString name1, name2;
+ aElement1->GetLeafName(name1);
+ aElement2->GetLeafName(name2);
+
+ // Note - we should do the collation to do sorting. Why don't we?
+ // Because that is _slow_. Using TestProtocols to list file:///dev/
+ // goes from 3 seconds to 22. (This may be why nsXULSortService is
+ // so slow as well).
+ // Does this have bad effects? Probably, but since nsXULTree appears
+ // to use the raw RDF literal value as the sort key (which ammounts to an
+ // strcmp), it won't be any worse, I think.
+ // This could be made faster, by creating the keys once,
+ // but CompareString could still be smarter - see bug 99383 - bbaetz
+ // NB - 99393 has been WONTFIXed. So if the I18N code is ever made
+ // threadsafe so that this matters, we'd have to pass through a
+ // struct { nsIFile*, uint8_t* } with the pre-calculated key.
+ return Compare(name1, name2);
+ }
+
+ nsAutoCString name1, name2;
+ aElement1->GetNativeLeafName(name1);
+ aElement2->GetNativeLeafName(name2);
+
+ return Compare(name1, name2);
+}
+
+nsresult nsDirectoryIndexStream::Init(nsIFile* aDir) {
+ nsresult rv;
+ bool isDir;
+ rv = aDir->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) return rv;
+ 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
+ }
+
+#ifdef THREADSAFE_I18N
+ nsCOMPtr<nsICollationFactory> cf =
+ do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsICollation> coll;
+ rv = cf->CreateCollation(getter_AddRefs(coll));
+ if (NS_FAILED(rv)) return rv;
+
+ mArray.Sort(compare, coll);
+#else
+ mArray.Sort(compare, nullptr);
+#endif
+
+ mBuf.AppendLiteral("300: ");
+ nsAutoCString url;
+ rv = net_GetURLSpecFromFile(aDir, url);
+ if (NS_FAILED(rv)) return rv;
+ mBuf.Append(url);
+ mBuf.Append('\n');
+
+ mBuf.AppendLiteral("200: filename content-length last-modified file-type\n");
+
+ return NS_OK;
+}
+
+nsDirectoryIndexStream::~nsDirectoryIndexStream() {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("nsDirectoryIndexStream[%p]: destroyed", this));
+}
+
+nsresult nsDirectoryIndexStream::Create(nsIFile* aDir,
+ nsIInputStream** aResult) {
+ RefPtr<nsDirectoryIndexStream> result = new nsDirectoryIndexStream();
+ if (!result) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = result->Init(aDir);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsDirectoryIndexStream, nsIInputStream)
+
+// The below routines are proxied to the UI thread!
+NS_IMETHODIMP
+nsDirectoryIndexStream::Close() {
+ mStatus = NS_BASE_STREAM_CLOSED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::Available(uint64_t* aLength) {
+ if (NS_FAILED(mStatus)) return mStatus;
+
+ // If there's data in our buffer, use that
+ if (mOffset < (int32_t)mBuf.Length()) {
+ *aLength = mBuf.Length() - mOffset;
+ return NS_OK;
+ }
+
+ // Returning one byte is not ideal, but good enough
+ *aLength = (mPos < mArray.Count()) ? 1 : 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryIndexStream::Read(char* aBuf, uint32_t aCount,
+ uint32_t* aReadCount) {
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ *aReadCount = 0;
+ return NS_OK;
+ }
+ if (NS_FAILED(mStatus)) return mStatus;
+
+ uint32_t nread = 0;
+
+ // If anything is enqueued (or left-over) in mBuf, then feed it to
+ // the reader first.
+ while (mOffset < (int32_t)mBuf.Length() && aCount != 0) {
+ *(aBuf++) = char(mBuf.CharAt(mOffset++));
+ --aCount;
+ ++nread;
+ }
+
+ // Room left?
+ if (aCount > 0) {
+ mOffset = 0;
+ mBuf.Truncate();
+
+ // Okay, now we'll suck stuff off of our iterator into the mBuf...
+ while (uint32_t(mBuf.Length()) < aCount) {
+ bool more = mPos < mArray.Count();
+ if (!more) break;
+
+ // don't addref, for speed - an addref happened when it
+ // was placed in the array, so it's not going to go stale
+ nsIFile* current = mArray.ObjectAt(mPos);
+ ++mPos;
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ 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..8e4721300c
--- /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;
+ nsresult mStatus;
+
+ int32_t mPos; // position within mArray
+ nsCOMArray<nsIFile> mArray; // file objects within the directory
+
+ nsDirectoryIndexStream();
+ /**
+ * aDir will only be used on the calling thread.
+ */
+ nsresult Init(nsIFile* aDir);
+ ~nsDirectoryIndexStream();
+
+ public:
+ /**
+ * aDir will only be used on the calling thread.
+ */
+ static nsresult Create(nsIFile* aDir, nsIInputStream** aStreamResult);
+
+ // nsISupportsInterface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIInputStream interface
+ NS_DECL_NSIINPUTSTREAM
+};
+
+#endif // nsDirectoryIndexStream_h__
diff --git a/netwerk/base/nsDownloader.cpp b/netwerk/base/nsDownloader.cpp
new file mode 100644
index 0000000000..670459e710
--- /dev/null
+++ b/netwerk/base/nsDownloader.cpp
@@ -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/. */
+
+#include "nsDownloader.h"
+#include "nsIInputStream.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, nullptr, 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..1e6879d601
--- /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() : mLocationIsTemp(false) {}
+
+ protected:
+ virtual ~nsDownloader();
+
+ static nsresult ConsumeData(nsIInputStream* in, void* closure,
+ const char* fromRawSegment, uint32_t toOffset,
+ uint32_t count, uint32_t* writeCount);
+
+ nsCOMPtr<nsIDownloadObserver> mObserver;
+ nsCOMPtr<nsIFile> mLocation;
+ nsCOMPtr<nsIOutputStream> mSink;
+ bool mLocationIsTemp;
+};
+
+#endif // nsDownloader_h__
diff --git a/netwerk/base/nsFileStreams.cpp b/netwerk/base/nsFileStreams.cpp
new file mode 100644
index 0000000000..41526c7642
--- /dev/null
+++ b/netwerk/base/nsFileStreams.cpp
@@ -0,0 +1,927 @@
+/* -*- 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) || defined(XP_BEOS)
+# include <unistd.h>
+#elif defined(XP_WIN)
+# include <windows.h>
+# include "nsILocalFileWin.h"
+#else
+// XXX add necessary include file for ftruncate (or equivalent)
+#endif
+
+#include "private/pprio.h"
+#include "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/Unused.h"
+#include "mozilla/FileUtils.h"
+#include "nsNetCID.h"
+#include "nsXULAppAPI.h"
+
+typedef mozilla::ipc::FileDescriptor::PlatformHandleType FileHandleType;
+
+using namespace mozilla::ipc;
+using namespace mozilla::net;
+
+using mozilla::DebugOnly;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileStreamBase
+
+nsFileStreamBase::nsFileStreamBase()
+ : mFD(nullptr),
+ mBehaviorFlags(0),
+ mState(eUnitialized),
+ mErrorValue(NS_ERROR_FAILURE) {}
+
+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) || defined(XP_BEOS)
+ // Some system calls require an EOF offset.
+ int64_t offset;
+ rv = Tell(&offset);
+ if (NS_FAILED(rv)) return rv;
+#endif
+
+#if defined(XP_UNIX) || defined(XP_BEOS)
+ if (ftruncate(PR_FileDesc2NativeHandle(mFD), offset) != 0) {
+ NS_ERROR("ftruncate failed");
+ return NS_ERROR_FAILURE;
+ }
+#elif defined(XP_WIN)
+ if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(mFD))) {
+ NS_ERROR("SetEndOfFile failed");
+ return NS_ERROR_FAILURE;
+ }
+#else
+ // XXX not implemented
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileStreamBase::GetSize(int64_t* _retval) {
+ nsresult rv = DoPendingOpen();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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() {
+ 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::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) {
+ 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(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ 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() {
+ // Get the cache position at the time the file was close. This allows
+ // NS_SEEK_CUR on a closed file that has been opened with
+ // REOPEN_ON_REWIND.
+ if (mBehaviorFlags & REOPEN_ON_REWIND) {
+ // Get actual position. Not one modified by subclasses
+ nsFileStreamBase::Tell(&mCachedPosition);
+ }
+
+ // null out mLineBuffer in case Close() is called again after failing
+ mLineBuffer = nullptr;
+ 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;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if we're at the end of file and need to close
+ if (mBehaviorFlags & CLOSE_ON_EOF && *_retval == 0) {
+ Close();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileInputStream::ReadLine(nsACString& aLine, bool* aResult) {
+ if (!mLineBuffer) {
+ mLineBuffer = 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);
+}
+
+void nsFileInputStream::Serialize(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors,
+ bool aDelayedStart, uint32_t aMaxSize,
+ uint32_t* aSizeUsed,
+ ParentToChildStreamActorManager* aManager) {
+ MOZ_ASSERT(aSizeUsed);
+ *aSizeUsed = 0;
+
+ SerializeInternal(aParams, aFileDescriptors);
+}
+
+void nsFileInputStream::Serialize(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors,
+ bool aDelayedStart, uint32_t aMaxSize,
+ uint32_t* aSizeUsed,
+ ChildToParentStreamActorManager* aManager) {
+ MOZ_ASSERT(aSizeUsed);
+ *aSizeUsed = 0;
+
+ SerializeInternal(aParams, aFileDescriptors);
+}
+
+void nsFileInputStream::SerializeInternal(
+ InputStreamParams& aParams, FileDescriptorArray& aFileDescriptors) {
+ FileInputStreamParams params;
+
+ if (NS_SUCCEEDED(DoPendingOpen())) {
+ MOZ_ASSERT(mFD);
+ FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD));
+ NS_ASSERTION(fd, "This should never be null!");
+
+ DebugOnly dbgFD = aFileDescriptors.AppendElement(fd);
+ NS_ASSERTION(dbgFD->IsValid(), "Sending an invalid file descriptor!");
+
+ params.fileDescriptorIndex() = aFileDescriptors.Length() - 1;
+
+ Close();
+ } else {
+ NS_WARNING(
+ "This file has not been opened (or could not be opened). "
+ "Sending an invalid file descriptor to the other process!");
+
+ params.fileDescriptorIndex() = UINT32_MAX;
+ }
+
+ int32_t behaviorFlags = mBehaviorFlags;
+
+ // The receiving process (or thread) is going to have an open file
+ // descriptor automatically so transferring this flag is meaningless.
+ behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN;
+
+ params.behaviorFlags() = behaviorFlags;
+ params.ioFlags() = mIOFlags;
+
+ aParams = params;
+}
+
+bool nsFileInputStream::Deserialize(
+ const InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors) {
+ NS_ASSERTION(!mFD, "Already have a file descriptor?!");
+ NS_ASSERTION(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();
+
+ uint32_t fileDescriptorIndex = params.fileDescriptorIndex();
+
+ FileDescriptor fd;
+ if (fileDescriptorIndex < aFileDescriptors.Length()) {
+ fd = aFileDescriptors[fileDescriptorIndex];
+ NS_WARNING_ASSERTION(fd.IsValid(), "Received an invalid file descriptor!");
+ } else {
+ NS_WARNING("Received a bad file descriptor index!");
+ }
+
+ if (fd.IsValid()) {
+ auto rawFD = fd.ClonePlatformHandle();
+ PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
+ if (!fileDesc) {
+ NS_WARNING("Failed to import file handle!");
+ return false;
+ }
+ mFD = fileDesc;
+ mState = eOpened;
+ } else {
+ 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(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ NS_ENSURE_NO_AGGREGATION(aOuter);
+
+ 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();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsFileStream
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFileStream, nsFileStreamBase, nsIInputStream,
+ nsIOutputStream, nsIFileStream)
+
+NS_IMETHODIMP
+nsFileStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags) {
+ NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(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 & nsIFileStream::DEFER_OPEN);
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/netwerk/base/nsFileStreams.h b/netwerk/base/nsFileStreams.h
new file mode 100644
index 0000000000..c694d1b366
--- /dev/null
+++ b/netwerk/base/nsFileStreams.h
@@ -0,0 +1,288 @@
+/* -*- 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 "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();
+
+ protected:
+ virtual ~nsFileStreamBase();
+
+ nsresult Close();
+ nsresult Available(uint64_t* _retval);
+ nsresult Read(char* aBuf, uint32_t aCount, uint32_t* _retval);
+ nsresult ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* _retval);
+ nsresult IsNonBlocking(bool* _retval);
+ nsresult Flush();
+ nsresult Write(const char* aBuf, uint32_t aCount, uint32_t* _retval);
+ nsresult WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* _retval);
+ nsresult WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval);
+
+ PRFileDesc* mFD;
+
+ /**
+ * Flags describing our behavior. See the IDL file for possible values.
+ */
+ int32_t mBehaviorFlags;
+
+ 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;
+
+ struct OpenParams {
+ nsCOMPtr<nsIFile> localFile;
+ int32_t ioFlags;
+ int32_t perm;
+ };
+
+ /**
+ * Data we need to do an open.
+ */
+ OpenParams mOpenParams;
+
+ nsresult mErrorValue;
+
+ /**
+ * 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 Read(char* aBuf, uint32_t aCount, uint32_t* _retval) override;
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) override {
+ return nsFileStreamBase::ReadSegments(aWriter, aClosure, aCount, _retval);
+ }
+ NS_IMETHOD IsNonBlocking(bool* _retval) override {
+ return nsFileStreamBase::IsNonBlocking(_retval);
+ }
+
+ // Overrided from nsFileStreamBase
+ NS_IMETHOD Seek(int32_t aWhence, int64_t aOffset) override;
+
+ nsFileInputStream()
+ : mLineBuffer(nullptr), mIOFlags(0), mPerm(0), mCachedPosition(0) {}
+
+ static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+ protected:
+ virtual ~nsFileInputStream() = default;
+
+ void SerializeInternal(mozilla::ipc::InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors);
+
+ 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;
+ /**
+ * The permissions passed to Init() for the file open.
+ */
+ int32_t mPerm;
+
+ /**
+ * Cached position for Tell for automatically reopening streams.
+ */
+ int64_t mCachedPosition;
+
+ protected:
+ /**
+ * Internal, called to open a file. Parameters are the same as their
+ * Init() analogues.
+ */
+ nsresult Open(nsIFile* file, int32_t ioFlags, int32_t perm);
+
+ bool IsCloneable() const;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileOutputStream : public nsFileStreamBase, public nsIFileOutputStream {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILEOUTPUTSTREAM
+ NS_FORWARD_NSIOUTPUTSTREAM(nsFileStreamBase::)
+
+ static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+ 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() : mTargetFileExists(true), mWriteResult(NS_OK) {}
+
+ virtual nsresult DoOpen() override;
+
+ NS_IMETHOD Close() override;
+ NS_IMETHOD Write(const char* buf, uint32_t count, uint32_t* result) override;
+ NS_IMETHOD Init(nsIFile* file, int32_t ioFlags, int32_t perm,
+ int32_t behaviorFlags) override;
+
+ protected:
+ virtual ~nsAtomicFileOutputStream() = default;
+
+ nsCOMPtr<nsIFile> mTargetFile;
+ nsCOMPtr<nsIFile> mTempFile;
+
+ bool mTargetFileExists;
+ nsresult mWriteResult; // Internally set in Write()
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A safe file output stream that overwrites the destination file only
+ * once writing + flushing is complete. This protects against more
+ * classes of software/hardware errors than nsAtomicFileOutputStream,
+ * at the expense of being more costly to the disk, OS and battery.
+ */
+class nsSafeFileOutputStream : public nsAtomicFileOutputStream {
+ public:
+ NS_IMETHOD Finish() override;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsFileStream : public nsFileStreamBase,
+ public nsIInputStream,
+ public nsIOutputStream,
+ public nsIFileStream {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILESTREAM
+ NS_FORWARD_NSIINPUTSTREAM(nsFileStreamBase::)
+
+ // Can't use NS_FORWARD_NSIOUTPUTSTREAM due to overlapping methods
+ // Close() and IsNonBlocking()
+ NS_IMETHOD Flush() override { return nsFileStreamBase::Flush(); }
+ NS_IMETHOD Write(const char* aBuf, uint32_t aCount,
+ uint32_t* _retval) override {
+ return nsFileStreamBase::Write(aBuf, aCount, _retval);
+ }
+ NS_IMETHOD WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* _retval) override {
+ return nsFileStreamBase::WriteFrom(aFromStream, aCount, _retval);
+ }
+ NS_IMETHOD WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) override {
+ return nsFileStreamBase::WriteSegments(aReader, aClosure, aCount, _retval);
+ }
+
+ protected:
+ virtual ~nsFileStream() = default;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#endif // nsFileStreams_h__
diff --git a/netwerk/base/nsIApplicationCache.idl b/netwerk/base/nsIApplicationCache.idl
new file mode 100644
index 0000000000..2f4d789041
--- /dev/null
+++ b/netwerk/base/nsIApplicationCache.idl
@@ -0,0 +1,203 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIArray;
+interface nsIFile;
+interface nsIURI;
+
+/**
+ * Application caches can store a set of namespace entries that affect
+ * loads from the application cache. If a load from the cache fails
+ * to match an exact cache entry, namespaces entries will be searched
+ * for a substring match, and should be applied appropriately.
+ */
+[scriptable, uuid(96e4c264-2065-4ce9-93bb-43734c62c4eb)]
+interface nsIApplicationCacheNamespace : nsISupports
+{
+ /**
+ * Items matching this namespace can be fetched from the network
+ * when loading from this cache. The "data" attribute is unused.
+ */
+ const unsigned long NAMESPACE_BYPASS = 1 << 0;
+
+ /**
+ * Items matching this namespace can be fetched from the network
+ * when loading from this cache. If the load fails, the cache entry
+ * specified by the "data" attribute should be loaded instead.
+ */
+ const unsigned long NAMESPACE_FALLBACK = 1 << 1;
+
+ /**
+ * Items matching this namespace should be cached
+ * opportunistically. Successful toplevel loads of documents
+ * in this namespace should be placed in the application cache.
+ * Namespaces specifying NAMESPACE_OPPORTUNISTIC may also specify
+ * NAMESPACE_FALLBACK to supply a fallback entry.
+ */
+ const unsigned long NAMESPACE_OPPORTUNISTIC = 1 << 2;
+
+ /**
+ * Initialize the namespace.
+ */
+ void init(in unsigned long itemType,
+ in ACString namespaceSpec,
+ in ACString data);
+
+ /**
+ * The namespace type.
+ */
+ readonly attribute unsigned long itemType;
+
+ /**
+ * The prefix of this namespace. This should be the asciiSpec of the
+ * URI prefix.
+ */
+ readonly attribute ACString namespaceSpec;
+
+ /**
+ * Data associated with this namespace, such as a fallback. URI data should
+ * use the asciiSpec of the URI.
+ */
+ readonly attribute ACString data;
+};
+
+/**
+ * Application caches store resources for offline use. Each
+ * application cache has a unique client ID for use with
+ * nsICacheService::openSession() to access the cache's entries.
+ *
+ * Each entry in the application cache can be marked with a set of
+ * types, as discussed in the WHAT-WG offline applications
+ * specification.
+ *
+ * All application caches with the same group ID belong to a cache
+ * group. Each group has one "active" cache that will service future
+ * loads. Inactive caches will be removed from the cache when they are
+ * no longer referenced.
+ */
+[scriptable, uuid(06568DAE-C374-4383-A122-0CC96C7177F2)]
+interface nsIApplicationCache : nsISupports
+{
+ /**
+ * Init this application cache instance to just hold the group ID and
+ * the client ID to work just as a handle to the real cache. Used on
+ * content process to simplify the application cache code.
+ */
+ void initAsHandle(in ACString groupId, in ACString clientId);
+
+ /**
+ * Entries in an application cache can be marked as one or more of
+ * the following types.
+ */
+
+ /* This item is the application manifest. */
+ const unsigned long ITEM_MANIFEST = 1 << 0;
+
+ /* This item was explicitly listed in the application manifest. */
+ const unsigned long ITEM_EXPLICIT = 1 << 1;
+
+ /* This item was navigated in a toplevel browsing context, and
+ * named this cache's group as its manifest. */
+ const unsigned long ITEM_IMPLICIT = 1 << 2;
+
+ /* This item was added by the dynamic scripting API */
+ const unsigned long ITEM_DYNAMIC = 1 << 3;
+
+ /* This item was listed in the application manifest, but named a
+ * different cache group as its manifest. */
+ const unsigned long ITEM_FOREIGN = 1 << 4;
+
+ /* This item was listed as a fallback entry. */
+ const unsigned long ITEM_FALLBACK = 1 << 5;
+
+ /* This item matched an opportunistic cache namespace and was
+ * cached accordingly. */
+ const unsigned long ITEM_OPPORTUNISTIC = 1 << 6;
+
+ /**
+ * URI of the manfiest specifying this application cache.
+ **/
+ readonly attribute nsIURI manifestURI;
+
+ /**
+ * The group ID for this cache group. It is an internally generated string
+ * and cannot be used as manifest URL spec.
+ **/
+ readonly attribute ACString groupID;
+
+ /**
+ * The client ID for this application cache. Clients can open a
+ * session with nsICacheService::createSession() using this client
+ * ID and a storage policy of STORE_OFFLINE to access this cache.
+ */
+ readonly attribute ACString clientID;
+
+ /**
+ * TRUE if the cache is the active cache for this group.
+ */
+ readonly attribute boolean active;
+
+ /**
+ * The disk usage of the application cache, in bytes.
+ */
+ readonly attribute unsigned long usage;
+
+ /**
+ * Makes this cache the active application cache for this group.
+ * Future loads associated with this group will come from this
+ * cache. Other caches from this cache group will be deactivated.
+ */
+ void activate();
+
+ /**
+ * Discard this application cache. Removes all cached resources
+ * for this cache. If this is the active application cache for the
+ * group, the group will be removed.
+ */
+ void discard();
+
+ /**
+ * Adds item types to a given entry.
+ */
+ void markEntry(in ACString key, in unsigned long typeBits);
+
+ /**
+ * Removes types from a given entry. If the resulting entry has
+ * no types left, the entry is removed.
+ */
+ void unmarkEntry(in ACString key, in unsigned long typeBits);
+
+ /**
+ * Gets the types for a given entry.
+ */
+ unsigned long getTypes(in ACString key);
+
+ /**
+ * Returns any entries in the application cache whose type matches
+ * one or more of the bits in typeBits.
+ */
+ Array<ACString> gatherEntries(in uint32_t typeBits);
+
+ /**
+ * Add a set of namespace entries to the application cache.
+ * @param namespaces
+ * An nsIArray of nsIApplicationCacheNamespace entries.
+ */
+ void addNamespaces(in nsIArray namespaces);
+
+ /**
+ * Get the most specific namespace matching a given key.
+ */
+ nsIApplicationCacheNamespace getMatchingNamespace(in ACString key);
+
+ /**
+ * If set, this offline cache is placed in a different directory
+ * than the current application profile.
+ */
+ readonly attribute nsIFile profileDirectory;
+};
diff --git a/netwerk/base/nsIApplicationCacheChannel.idl b/netwerk/base/nsIApplicationCacheChannel.idl
new file mode 100644
index 0000000000..410e2946d8
--- /dev/null
+++ b/netwerk/base/nsIApplicationCacheChannel.idl
@@ -0,0 +1,57 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIApplicationCacheContainer.idl"
+
+/**
+ * Interface implemented by channels that support application caches.
+ */
+[scriptable, uuid(6FA816B1-6D5F-4380-9704-054D0908CFA3)]
+interface nsIApplicationCacheChannel : nsIApplicationCacheContainer
+{
+ /**
+ * TRUE when the resource came from the application cache. This
+ * might be false even there is assigned an application cache
+ * e.g. in case of fallback of load of an entry matching bypass
+ * namespace.
+ */
+ readonly attribute boolean loadedFromApplicationCache;
+
+ /**
+ * When true, the channel will ask its notification callbacks for
+ * an application cache if one is not explicitly provided. Default
+ * value is true.
+ *
+ * NS_ERROR_ALREADY_OPENED will be thrown if set after AsyncOpen()
+ * is called.
+ */
+ attribute boolean inheritApplicationCache;
+
+ /**
+ * When true, the channel will choose an application cache if one
+ * was not explicitly provided and none is available from the
+ * notification callbacks. Default value is false.
+ *
+ * This attribute will not be transferred through a redirect.
+ *
+ * NS_ERROR_ALREADY_OPENED will be thrown if set after AsyncOpen()
+ * is called.
+ */
+ attribute boolean chooseApplicationCache;
+
+ /**
+ * A shortcut method to mark the cache item of this channel as 'foreign'.
+ * See the 'cache selection algorithm' and CACHE_SELECTION_RELOAD
+ * action handling in nsContentSink.
+ */
+ void markOfflineCacheEntryAsForeign();
+
+ /**
+ * Set offline application cache object to instruct the channel
+ * to cache for offline use using this application cache.
+ */
+ attribute nsIApplicationCache applicationCacheForWrite;
+};
diff --git a/netwerk/base/nsIApplicationCacheContainer.idl b/netwerk/base/nsIApplicationCacheContainer.idl
new file mode 100644
index 0000000000..af0b74cbee
--- /dev/null
+++ b/netwerk/base/nsIApplicationCacheContainer.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIApplicationCache;
+
+/**
+ * Interface used by objects that can be associated with an
+ * application cache.
+ */
+[scriptable, uuid(bbb80700-1f7f-4258-aff4-1743cc5a7d23)]
+interface nsIApplicationCacheContainer : nsISupports
+{
+ attribute nsIApplicationCache applicationCache;
+};
diff --git a/netwerk/base/nsIApplicationCacheService.idl b/netwerk/base/nsIApplicationCacheService.idl
new file mode 100644
index 0000000000..4c683e874a
--- /dev/null
+++ b/netwerk/base/nsIApplicationCacheService.idl
@@ -0,0 +1,108 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIApplicationCache;
+interface nsIFile;
+interface nsIURI;
+interface nsILoadContextInfo;
+
+/**
+ * The application cache service manages the set of application cache
+ * groups.
+ */
+[scriptable, uuid(b8b6546c-6cec-4bda-82df-08e006a97b56)]
+interface nsIApplicationCacheService : nsISupports
+{
+ /**
+ * Create group string identifying cache group according the manifest
+ * URL and the given principal.
+ */
+ ACString buildGroupIDForInfo(in nsIURI aManifestURL,
+ in nsILoadContextInfo aLoadContextInfo);
+ ACString buildGroupIDForSuffix(in nsIURI aManifestURL,
+ in ACString aOriginSuffix);
+
+ /**
+ * Create a new, empty application cache for the given cache
+ * group.
+ */
+ nsIApplicationCache createApplicationCache(in ACString group);
+
+ /**
+ * Create a new, empty application cache for the given cache
+ * group residing in a custom directory with a custom quota.
+ *
+ * @param group
+ * URL of the manifest
+ * @param directory
+ * Actually a reference to a profile directory where to
+ * create the OfflineCache sub-dir.
+ * @param quota
+ * Optional override of the default quota.
+ */
+ nsIApplicationCache createCustomApplicationCache(in ACString group,
+ in nsIFile profileDir,
+ in int32_t quota);
+
+ /**
+ * Get an application cache object for the given client ID.
+ */
+ nsIApplicationCache getApplicationCache(in ACString clientID);
+
+ /**
+ * Get the currently active cache object for a cache group.
+ */
+ nsIApplicationCache getActiveCache(in ACString group);
+
+ /**
+ * Deactivate the currently-active cache object for a cache group.
+ */
+ void deactivateGroup(in ACString group);
+
+ /**
+ * Evict offline cache entries, either all of them or those belonging
+ * to the given origin.
+ */
+ void evict(in nsILoadContextInfo aLoadContextInfo);
+
+ /**
+ * Delete caches whom origin attributes matches the given pattern.
+ */
+ void evictMatchingOriginAttributes(in AString aPattern);
+
+ /**
+ * Try to find the best application cache to serve a resource.
+ */
+ nsIApplicationCache chooseApplicationCache(in ACString key,
+ [optional] in nsILoadContextInfo aLoadContextInfo);
+
+ /**
+ * Flags the key as being opportunistically cached.
+ *
+ * This method should also propagate the entry to other
+ * application caches with the same opportunistic namespace, but
+ * this is not currently implemented.
+ *
+ * @param cache
+ * The cache in which the entry is cached now.
+ * @param key
+ * The cache entry key.
+ */
+ void cacheOpportunistically(in nsIApplicationCache cache, in ACString key);
+
+ /**
+ * Get the list of application cache groups.
+ */
+ Array<ACString> getGroups();
+
+ /**
+ * Get the list of application cache groups in the order of
+ * activating time.
+ */
+ Array<ACString> getGroupsTimeOrdered();
+};
diff --git a/netwerk/base/nsIArrayBufferInputStream.idl b/netwerk/base/nsIArrayBufferInputStream.idl
new file mode 100644
index 0000000000..f11135b246
--- /dev/null
+++ b/netwerk/base/nsIArrayBufferInputStream.idl
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+
+/**
+ * nsIArrayBufferInputStream
+ *
+ * Provides scriptable methods for initializing a nsIInputStream
+ * implementation with an ArrayBuffer.
+ */
+[scriptable, uuid(3014dde6-aa1c-41db-87d0-48764a3710f6)]
+interface nsIArrayBufferInputStream : nsIInputStream
+{
+ /**
+ * SetData - assign an ArrayBuffer to the input stream.
+ *
+ * @param buffer - stream data
+ * @param byteOffset - stream data offset
+ * @param byteLen - stream data length
+ */
+ void setData(in jsval buffer, in unsigned long byteOffset, in unsigned long 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..8fd4a03117
--- /dev/null
+++ b/netwerk/base/nsIAuthInformation.idl
@@ -0,0 +1,118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * A object that hold authentication information. The caller of
+ * nsIAuthPrompt2::promptUsernameAndPassword or
+ * nsIAuthPrompt2::promptPasswordAsync provides an object implementing this
+ * interface; the prompt implementation can then read the values here to prefill
+ * the dialog. After the user entered the authentication information, it should
+ * set the attributes of this object to indicate to the caller what was entered
+ * by the user.
+ */
+[scriptable, uuid(0d73639c-2a92-4518-9f92-28f71fea5f20)]
+interface nsIAuthInformation : nsISupports
+{
+ /** @name Flags */
+ /* @{ */
+ /**
+ * This dialog belongs to a network host.
+ */
+ const uint32_t AUTH_HOST = 1;
+
+ /**
+ * This dialog belongs to a proxy.
+ */
+ const uint32_t AUTH_PROXY = 2;
+
+ /**
+ * This dialog needs domain information. The user interface should show a
+ * domain field, prefilled with the domain attribute's value.
+ */
+ const uint32_t NEED_DOMAIN = 4;
+
+ /**
+ * This dialog only asks for password information. Authentication prompts
+ * SHOULD NOT show a username field. Attempts to change the username field
+ * will have no effect. nsIAuthPrompt2 implementations should, however, show
+ * its initial value to the user in some form. For example, a paragraph in
+ * the dialog might say "Please enter your password for user jsmith at
+ * server intranet".
+ *
+ * This flag is mutually exclusive with #NEED_DOMAIN.
+ */
+ const uint32_t ONLY_PASSWORD = 8;
+
+ /**
+ * We have already tried to log in for this channel
+ * (with auth values from a previous promptAuth call),
+ * but it failed, so we now ask the user to provide a new, correct login.
+ *
+ * @see also RFC 2616, Section 10.4.2
+ */
+ const uint32_t PREVIOUS_FAILED = 16;
+
+ /**
+ * A cross-origin sub-resource requests an authentication.
+ * The message presented to users must reflect that.
+ */
+ const uint32_t CROSS_ORIGIN_SUB_RESOURCE = 32;
+ /* @} */
+
+ /**
+ * Flags describing this dialog. A bitwise OR of the flag values
+ * above.
+ *
+ * It is possible that neither #AUTH_HOST nor #AUTH_PROXY are set.
+ *
+ * Auth prompts should ignore flags they don't understand; especially, they
+ * should not throw an exception because of an unsupported flag.
+ */
+ readonly attribute unsigned long flags;
+
+ /**
+ * The server-supplied realm of the authentication as defined in RFC 2617.
+ * Can be the empty string if the protocol does not support realms.
+ * Otherwise, this is a human-readable string like "Secret files".
+ */
+ readonly attribute AString realm;
+
+ /**
+ * The authentication scheme used for this request, if applicable. If the
+ * protocol for this authentication does not support schemes, this will be
+ * the empty string. Otherwise, this will be a string such as "basic" or
+ * "digest". This string will always be in lowercase.
+ */
+ readonly attribute AUTF8String authenticationScheme;
+
+ /**
+ * The initial value should be used to prefill the dialog or be shown
+ * in some other way to the user.
+ * On return, this parameter should contain the username entered by
+ * the user.
+ * This field can only be changed if the #ONLY_PASSWORD flag is not set.
+ */
+ attribute AString username;
+
+ /**
+ * The initial value should be used to prefill the dialog or be shown
+ * in some other way to the user.
+ * The password should not be shown in clear.
+ * On return, this parameter should contain the password entered by
+ * the user.
+ */
+ attribute AString password;
+
+ /**
+ * The initial value should be used to prefill the dialog or be shown
+ * in some other way to the user.
+ * On return, this parameter should contain the domain entered by
+ * the user.
+ * This attribute is only used if flags include #NEED_DOMAIN.
+ */
+ attribute AString domain;
+};
+
diff --git a/netwerk/base/nsIAuthModule.idl b/netwerk/base/nsIAuthModule.idl
new file mode 100644
index 0000000000..0cbfbb1dab
--- /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 string aServiceName,
+ in unsigned long aServiceFlags,
+ in wstring aDomain,
+ in wstring aUsername,
+ in wstring aPassword);
+
+ /**
+ * Called to get the next token in a sequence of authentication steps.
+ *
+ * @param aInToken
+ * A buffer containing the input token (e.g., a challenge from a
+ * server). This may be null.
+ * @param aInTokenLength
+ * The length of the input token.
+ * @param aOutToken
+ * If getNextToken succeeds, then aOutToken will point to a buffer
+ * to be sent in response to the server challenge. The length of
+ * this buffer is given by aOutTokenLength. The buffer at aOutToken
+ * must be recycled with a call to free.
+ * @param aOutTokenLength
+ * If getNextToken succeeds, then aOutTokenLength contains the
+ * length of the buffer (number of bytes) pointed to by aOutToken.
+ */
+ void getNextToken([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+ /**
+ * Once a security context has been established through calls to GetNextToken()
+ * it may be used to protect data exchanged between client and server. Calls
+ * to Wrap() are used to protect items of data to be sent to the server.
+ *
+ * @param aInToken
+ * A buffer containing the data to be sent to the server
+ * @param aInTokenLength
+ * The length of the input token
+ * @param confidential
+ * If set to true, Wrap() will encrypt the data, otherwise data will
+ * just be integrity protected (checksummed)
+ * @param aOutToken
+ * A buffer containing the resulting data to be sent to the server
+ * @param aOutTokenLength
+ * The length of the output token buffer
+ *
+ * Wrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying authentication
+ * mechanism does not support security layers.
+ */
+ void wrap([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ in boolean confidential,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+
+ /**
+ * Unwrap() is used to unpack, decrypt, and verify the checksums on data
+ * returned by a server when security layers are in use.
+ *
+ * @param aInToken
+ * A buffer containing the data received from the server
+ * @param aInTokenLength
+ * The length of the input token
+ * @param aOutToken
+ * A buffer containing the plaintext data from the server
+ * @param aOutTokenLength
+ * The length of the output token buffer
+ *
+ * Unwrap() may return NS_ERROR_NOT_IMPLEMENTED, if the underlying
+ * authentication mechanism does not support security layers.
+ */
+ void unwrap([const] in voidPtr aInToken,
+ in unsigned long aInTokenLength,
+ out voidPtr aOutToken,
+ out unsigned long aOutTokenLength);
+
+%{C++
+ /**
+ * 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..08cadf991c
--- /dev/null
+++ b/netwerk/base/nsIAuthPrompt.idl
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrompt;
+
+[scriptable, uuid(358089f9-ee4b-4711-82fd-bcd07fc62061)]
+interface nsIAuthPrompt : nsISupports
+{
+ const uint32_t SAVE_PASSWORD_NEVER = 0;
+ const uint32_t SAVE_PASSWORD_FOR_SESSION = 1;
+ const uint32_t SAVE_PASSWORD_PERMANENTLY = 2;
+
+ /**
+ * Puts up a text input dialog with OK and Cancel buttons.
+ * Note: prompt uses separate args for the "in" and "out" values of the
+ * input field, whereas the other functions use a single inout arg.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param defaultText The default text to display in the text input box.
+ * @param result The value entered by the user if OK was
+ * selected.
+ * @return true for OK, false for Cancel
+ */
+ boolean prompt(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ in wstring defaultText,
+ out wstring result);
+
+ /**
+ * Puts up a username/password dialog with OK and Cancel buttons.
+ * Puts up a password dialog with OK and Cancel buttons.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param user The username entered in the dialog.
+ * @param pwd The password entered by the user if OK was
+ * selected.
+ * @return true for OK, false for Cancel
+ */
+ boolean promptUsernameAndPassword(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ inout wstring user,
+ inout wstring pwd);
+
+ /**
+ * Puts up a password dialog with OK and Cancel buttons.
+ * @param dialogText The title for the dialog.
+ * @param text The text to display in the dialog.
+ * @param passwordRealm The "realm" the password belongs to: e.g.
+ * ldap://localhost/dc=test. If a username is
+ * specified (http://user@site.com) it will be used
+ * when matching existing logins or saving new ones.
+ * If no username is specified, only password-only
+ * logins will be matched or saved.
+ * Note: if a username is specified, the username
+ * should be escaped.
+ * @param savePassword One of the SAVE_PASSWORD_* options above.
+ * @param pwd The password entered by the user if OK was
+ * selected.
+ * @return true for OK, false for Cancel
+ */
+ boolean promptPassword(in wstring dialogTitle,
+ in wstring text,
+ in wstring passwordRealm,
+ in uint32_t savePassword,
+ inout wstring pwd);
+};
diff --git a/netwerk/base/nsIAuthPrompt2.idl b/netwerk/base/nsIAuthPrompt2.idl
new file mode 100644
index 0000000000..dda26e9362
--- /dev/null
+++ b/netwerk/base/nsIAuthPrompt2.idl
@@ -0,0 +1,103 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAuthPromptCallback;
+interface nsIChannel;
+interface nsICancelable;
+interface nsIAuthInformation;
+
+/**
+ * An interface allowing to prompt for a username and password. This interface
+ * is usually acquired using getInterface on notification callbacks or similar.
+ * It can be used to prompt users for authentication information, either
+ * synchronously or asynchronously.
+ */
+[scriptable, uuid(651395EB-8612-4876-8AC0-A88D4DCE9E1E)]
+interface nsIAuthPrompt2 : nsISupports
+{
+ /** @name Security Levels */
+ /* @{ */
+ /**
+ * The password will be sent unencrypted. No security provided.
+ */
+ const uint32_t LEVEL_NONE = 0;
+ /**
+ * Password will be sent encrypted, but the connection is otherwise
+ * insecure.
+ */
+ const uint32_t LEVEL_PW_ENCRYPTED = 1;
+ /**
+ * The connection, both for password and data, is secure.
+ */
+ const uint32_t LEVEL_SECURE = 2;
+ /* @} */
+
+ /**
+ * Requests a username and a password. Implementations will commonly show a
+ * dialog with a username and password field, depending on flags also a
+ * domain field.
+ *
+ * @param aChannel
+ * The channel that requires authentication.
+ * @param level
+ * One of the level constants from above. See there for descriptions
+ * of the levels.
+ * @param authInfo
+ * Authentication information object. The implementation should fill in
+ * this object with the information entered by the user before
+ * returning.
+ *
+ * @retval true
+ * Authentication can proceed using the values in the authInfo
+ * object.
+ * @retval false
+ * Authentication should be cancelled, usually because the user did
+ * not provide username/password.
+ *
+ * @note Exceptions thrown from this function will be treated like a
+ * return value of false.
+ */
+ boolean promptAuth(in nsIChannel aChannel,
+ in uint32_t level,
+ in nsIAuthInformation authInfo);
+
+ /**
+ * Asynchronously prompt the user for a username and password.
+ * This has largely the same semantics as promptUsernameAndPassword(),
+ * but must return immediately after calling and return the entered
+ * data in a callback.
+ *
+ * If the user closes the dialog using a cancel button or similar,
+ * the callback's nsIAuthPromptCallback::onAuthCancelled method must be
+ * called.
+ * Calling nsICancelable::cancel on the returned object SHOULD close the
+ * dialog and MUST call nsIAuthPromptCallback::onAuthCancelled on the provided
+ * callback.
+ *
+ * This implementation may:
+ *
+ * 1) Coalesce identical prompts. This means prompts that are guaranteed to
+ * want the same auth information from the user. A single prompt will be
+ * shown; then the callbacks for all the coalesced prompts will be notified
+ * with the resulting auth information.
+ * 2) Serialize prompts that are all in the same "context" (this might mean
+ * application-wide, for a given window, or something else depending on
+ * the user interface) so that the user is not deluged with prompts.
+ *
+ * @throw
+ * This method may throw any exception when the prompt fails to queue e.g
+ * because of out-of-memory error. It must not throw when the prompt
+ * could already be potentially shown to the user. In that case information
+ * about the failure has to come through the callback. This way we
+ * prevent multiple dialogs shown to the user because consumer may fall
+ * back to synchronous prompt on synchronous failure of this method.
+ */
+ nsICancelable asyncPromptAuth(in nsIChannel aChannel,
+ in nsIAuthPromptCallback aCallback,
+ in nsISupports aContext,
+ in uint32_t level,
+ in nsIAuthInformation authInfo);
+};
diff --git a/netwerk/base/nsIAuthPromptAdapterFactory.idl b/netwerk/base/nsIAuthPromptAdapterFactory.idl
new file mode 100644
index 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..bf9f2f2ac0
--- /dev/null
+++ b/netwerk/base/nsIAuthPromptCallback.idl
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAuthInformation;
+
+/**
+ * Interface for callback methods for the asynchronous nsIAuthPrompt2 method.
+ * Callers MUST call exactly one method if nsIAuthPrompt2::promptPasswordAsync
+ * returns successfully. They MUST NOT call any method on this interface before
+ * promptPasswordAsync returns.
+ */
+[scriptable, uuid(bdc387d7-2d29-4cac-92f1-dd75d786631d)]
+interface nsIAuthPromptCallback : nsISupports
+{
+ /**
+ * Authentication information is available.
+ *
+ * @param aContext
+ * The context as passed to promptPasswordAsync
+ * @param aAuthInfo
+ * Authentication information. Must be the same object that was passed
+ * to promptPasswordAsync.
+ *
+ * @note Any exceptions thrown from this method should be ignored.
+ */
+ void onAuthAvailable(in nsISupports aContext,
+ in nsIAuthInformation aAuthInfo);
+
+ /**
+ * Notification that the prompt was cancelled.
+ *
+ * @param aContext
+ * The context that was passed to promptPasswordAsync.
+ * @param userCancel
+ * If false, this prompt was cancelled by calling the
+ * the cancel method on the nsICancelable; otherwise,
+ * it was cancelled by the user.
+ */
+ void onAuthCancelled(in nsISupports aContext, in boolean userCancel);
+};
+
diff --git a/netwerk/base/nsIAuthPromptProvider.idl b/netwerk/base/nsIAuthPromptProvider.idl
new file mode 100644
index 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..a9876c1d8f
--- /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, 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, 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..7cf70408fd
--- /dev/null
+++ b/netwerk/base/nsICacheInfoChannel.idl
@@ -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/. */
+
+#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 int32_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;
+
+ /**
+ * 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 boolean 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 method will expose the alt-data inputStream if
+ * aviable.
+ */
+ void getAltDataInputStream(in ACString type,
+ in nsIInputStreamReceiver aReceiver);
+
+ /**
+ * 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);
+};
diff --git a/netwerk/base/nsICachingChannel.idl b/netwerk/base/nsICachingChannel.idl
new file mode 100644
index 0000000000..1828f15307
--- /dev/null
+++ b/netwerk/base/nsICachingChannel.idl
@@ -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/. */
+
+#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;
+
+ /**
+ * The same as above but accessing the offline app cache token if there
+ * is any.
+ *
+ * @throws
+ * NS_ERROR_NOT_AVAILABLE when there is not offline cache token
+ */
+ attribute nsISupports offlineCacheToken;
+
+ /**
+ * Instructs the channel to only store the metadata of the entry, and not
+ * the content. When reading an existing entry, this automatically sets
+ * LOAD_ONLY_IF_MODIFIED flag.
+ * Must be called before asyncOpen().
+ */
+ attribute boolean cacheOnlyMetadata;
+
+ /**
+ * Tells the channel to use the pinning storage.
+ */
+ attribute boolean pin;
+
+ /**
+ * Overrides cache validation for a time specified in seconds.
+ *
+ * @param aSecondsToTheFuture
+ *
+ */
+ void forceCacheEntryValidFor(in unsigned long aSecondsToTheFuture);
+
+ /**************************************************************************
+ * Caching channel specific load flags:
+ */
+
+ /**
+ * This load flag inhibits fetching from the net. An error of
+ * NS_ERROR_DOCUMENT_NOT_CACHED will be sent to the listener's
+ * onStopRequest if network IO is necessary to complete the request.
+ *
+ * This flag can be used to find out whether fetching this URL would
+ * cause validation of the cache entry via the network.
+ *
+ * Combining this flag with LOAD_BYPASS_LOCAL_CACHE will cause all
+ * loads to fail.
+ */
+ const unsigned long LOAD_NO_NETWORK_IO = 1 << 26;
+
+ /**
+ * This load flag causes the offline cache to be checked when fetching
+ * a request. It will be set automatically if the browser is offline.
+ *
+ * This flag will not be transferred through a redirect.
+ */
+ const unsigned long LOAD_CHECK_OFFLINE_CACHE = 1 << 27;
+
+ /**
+ * This load flag causes the local cache to be skipped when fetching a
+ * request. Unlike LOAD_BYPASS_CACHE, it does not force an end-to-end load
+ * (i.e., it does not affect proxy caches).
+ */
+ const unsigned long LOAD_BYPASS_LOCAL_CACHE = 1 << 28;
+
+ /**
+ * This load flag causes the local cache to be skipped if the request
+ * would otherwise block waiting to access the cache.
+ */
+ const unsigned long LOAD_BYPASS_LOCAL_CACHE_IF_BUSY = 1 << 29;
+
+ /**
+ * This load flag inhibits fetching from the net if the data in the cache
+ * has been evicted. An error of NS_ERROR_DOCUMENT_NOT_CACHED will be sent
+ * to the listener's onStopRequest in this case. This flag is set
+ * automatically when the application is offline.
+ */
+ const unsigned long LOAD_ONLY_FROM_CACHE = 1 << 30;
+
+ /**
+ * This load flag controls what happens when a document would be loaded
+ * from the cache to satisfy a call to AsyncOpen. If this attribute is
+ * set to TRUE, then the document will not be loaded from the cache. A
+ * stream listener can check nsICachingChannel::isFromCache to determine
+ * if the AsyncOpen will actually result in data being streamed.
+ *
+ * If this flag has been set, and the request can be satisfied via the
+ * cache, then the OnDataAvailable events will be skipped. The listener
+ * will only see OnStartRequest followed by OnStopRequest.
+ */
+ const unsigned long LOAD_ONLY_IF_MODIFIED = 1 << 31;
+};
diff --git a/netwerk/base/nsICancelable.idl b/netwerk/base/nsICancelable.idl
new file mode 100644
index 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..9b6d104fa6
--- /dev/null
+++ b/netwerk/base/nsICaptivePortalService.idl
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(b5fd5629-d04c-4138-9529-9311f291ecd4)]
+interface nsICaptivePortalServiceCallback : nsISupports
+{
+ /**
+ * Invoke callbacks after captive portal detection finished.
+ */
+ void complete(in bool success, in nsresult error);
+};
+
+/**
+ * Service used for captive portal detection.
+ * The service is only active in the main process. It is also available in the
+ * content process, but only to mirror the captive portal state from the main
+ * process.
+ */
+[scriptable, uuid(bdbe0555-fc3d-4f7b-9205-c309ceb2d641)]
+interface nsICaptivePortalService : nsISupports
+{
+ const long UNKNOWN = 0;
+ const long NOT_CAPTIVE = 1;
+ const long UNLOCKED_PORTAL = 2;
+ const long LOCKED_PORTAL = 3;
+
+ /**
+ * Called from XPCOM to trigger a captive portal recheck.
+ * A network request will only be performed if no other checks are currently
+ * ongoing.
+ * Will not do anything if called in the content process.
+ */
+ void recheckCaptivePortal();
+
+ /**
+ * Returns the state of the captive portal.
+ */
+ readonly attribute long state;
+
+ /**
+ * Returns the time difference between NOW and the last time a request was
+ * completed in milliseconds.
+ */
+ readonly attribute unsigned long long lastChecked;
+};
+
+%{C++
+/**
+ * This observer notification will be emitted when the captive portal state
+ * changes. After receiving it, the ContentParent will send an IPC message
+ * to the ContentChild, which will set the state in the captive portal service
+ * in the child.
+ */
+#define NS_IPC_CAPTIVE_PORTAL_SET_STATE "ipc:network:captive-portal-set-state"
+
+/**
+ * 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"
+
+%}
diff --git a/netwerk/base/nsIChannel.idl b/netwerk/base/nsIChannel.idl
new file mode 100644
index 0000000000..f089c5aecc
--- /dev/null
+++ b/netwerk/base/nsIChannel.idl
@@ -0,0 +1,398 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+#include "nsILoadInfo.idl"
+
+interface nsIURI;
+interface nsIInterfaceRequestor;
+interface nsIInputStream;
+interface nsIStreamListener;
+
+%{C++
+#include "nsCOMPtr.h"
+%}
+
+/**
+ * The nsIChannel interface allows clients to construct "GET" requests for
+ * specific protocols, and manage them in a uniform way. Once a channel is
+ * created (via nsIIOService::newChannel), parameters for that request may
+ * be set by using the channel attributes, or by QI'ing to a subclass of
+ * nsIChannel for protocol-specific parameters. Then, the URI can be fetched
+ * by calling nsIChannel::open or nsIChannel::asyncOpen.
+ *
+ * After a request has been completed, the channel is still valid for accessing
+ * protocol-specific results. For example, QI'ing to nsIHttpChannel allows
+ * response headers to be retrieved for the corresponding http transaction.
+ *
+ * This interface must be used only from the XPCOM main thread.
+ */
+[scriptable, uuid(2c389865-23db-4aa7-9fe5-60cc7b00697e)]
+interface nsIChannel : nsIRequest
+{
+ /**
+ * The original URI used to construct the channel. This is used in
+ * the case of a redirect or URI "resolution" (e.g. resolving a
+ * resource: URI to a file: URI) so that the original pre-redirect
+ * URI can still be obtained. This is never null. Attempts to
+ * set it to null must throw.
+ *
+ * NOTE: this is distinctly different from the http Referer (referring URI),
+ * which is typically the page that contained the original URI (accessible
+ * from nsIHttpChannel).
+ */
+ attribute nsIURI originalURI;
+
+ /**
+ * The URI corresponding to the channel. Its value is immutable.
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * The owner, corresponding to the entity that is responsible for this
+ * channel. Used by the security manager to grant or deny privileges to
+ * mobile code loaded from this channel.
+ *
+ * NOTE: this is a strong reference to the owner, so if the owner is also
+ * holding a strong reference to the channel, care must be taken to
+ * explicitly drop its reference to the channel.
+ */
+ attribute nsISupports owner;
+
+ /**
+ * The notification callbacks for the channel. This is set by clients, who
+ * wish to provide a means to receive progress, status and protocol-specific
+ * notifications. If this value is NULL, the channel implementation may use
+ * the notification callbacks from its load group. The channel may also
+ * query the notification callbacks from its load group if its notification
+ * callbacks do not supply the requested interface.
+ *
+ * Interfaces commonly requested include: nsIProgressEventSink, nsIPrompt,
+ * and nsIAuthPrompt/nsIAuthPrompt2.
+ *
+ * When the channel is done, it must not continue holding references to
+ * this object.
+ *
+ * NOTE: A channel implementation should take care when "caching" an
+ * interface pointer queried from its notification callbacks. If the
+ * notification callbacks are changed, then a cached interface pointer may
+ * become invalid and may therefore need to be re-queried.
+ */
+ attribute nsIInterfaceRequestor notificationCallbacks;
+
+ /**
+ * Transport-level security information (if any) corresponding to the
+ * channel.
+ *
+ * NOTE: In some circumstances TLS information is propagated onto
+ * non-nsIHttpChannel objects to indicate that their contents were likely
+ * delivered over TLS all the same.
+ *
+ * FIXME(bz, bug 1528449) is that still true now that
+ * document.open() doesn't do this?
+ */
+ readonly attribute nsISupports securityInfo;
+
+ /**
+ * The MIME type of the channel's content if available.
+ *
+ * NOTE: the content type can often be wrongly specified (e.g., wrong file
+ * extension, wrong MIME type, wrong document type stored on a server, etc.),
+ * and the caller most likely wants to verify with the actual data.
+ *
+ * Setting contentType before the channel has been opened provides a hint
+ * to the channel as to what the MIME type is. The channel may ignore this
+ * hint in deciding on the actual MIME type that it will report.
+ *
+ * Setting contentType after onStartRequest has been fired or after open()
+ * is called will override the type determined by the channel.
+ *
+ * Setting contentType between the time that asyncOpen() is called and the
+ * time when onStartRequest is fired has undefined behavior at this time.
+ *
+ * The value of the contentType attribute is a lowercase string. A value
+ * assigned to this attribute will be parsed and normalized as follows:
+ * 1- any parameters (delimited with a ';') will be stripped.
+ * 2- if a charset parameter is given, then its value will replace the
+ * the contentCharset attribute of the channel.
+ * 3- the stripped contentType will be lowercased.
+ * Any implementation of nsIChannel must follow these rules.
+ */
+ attribute ACString contentType;
+
+ /**
+ * The character set of the channel's content if available and if applicable.
+ * This attribute only applies to textual data.
+ *
+ * The value of the contentCharset attribute is a mixedcase string.
+ */
+ attribute ACString contentCharset;
+
+ /**
+ * The length of the data associated with the channel if available. A value
+ * of -1 indicates that the content length is unknown. Note that this is a
+ * 64-bit value and obsoletes the "content-length" property used on some
+ * channels.
+ */
+ attribute int64_t contentLength;
+
+ /**
+ * Synchronously open the channel.
+ *
+ * @return blocking input stream to the channel's data.
+ *
+ * NOTE: nsIChannel implementations are not required to implement this
+ * method. Moreover, since this method may block the calling thread, it
+ * should not be called on a thread that processes UI events. Like any
+ * other nsIChannel method it must not be called on any thread other
+ * than the XPCOM main thread.
+ *
+ * NOTE: Implementations should throw NS_ERROR_IN_PROGRESS if the channel
+ * is reopened.
+ */
+ nsIInputStream open();
+
+ /**
+ * 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. The value from Content-Disposition header is only used when
+ * the hinted value is not DISPOSITION_ATTACHMENT.
+ * 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;
+
+ /**
+ * 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..76a1e290c0
--- /dev/null
+++ b/netwerk/base/nsIChannelEventSink.idl
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIAsyncVerifyRedirectCallback;
+
+/**
+ * Implement this interface to receive control over various channel events.
+ * Channels will try to get this interface from a channel's
+ * notificationCallbacks or, if not available there, from the loadGroup's
+ * notificationCallbacks.
+ *
+ * These methods are called before onStartRequest.
+ */
+[scriptable, uuid(0197720d-37ed-4e75-8956-d0d296e4d8a6)]
+interface nsIChannelEventSink : nsISupports
+{
+ /**
+ * This is a temporary redirect. New requests for this resource should
+ * continue to use the URI of the old channel.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_TEMPORARY = 1 << 0;
+
+ /**
+ * This is a permanent redirect. New requests for this resource should use
+ * the URI of the new channel (This might be an HTTP 301 reponse).
+ * If this flag is not set, this is a temporary redirect.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_PERMANENT = 1 << 1;
+
+ /**
+ * This is an internal redirect, i.e. it was not initiated by the remote
+ * server, but is specific to the channel implementation.
+ *
+ * The new URI may be identical to the old one.
+ */
+ const unsigned long REDIRECT_INTERNAL = 1 << 2;
+
+ /**
+ * This is a special-cased redirect coming from hitting HSTS upgrade
+ * redirect from http to https only. In some cases this type of redirect
+ * may be considered as safe despite not being the-same-origin redirect.
+ */
+ const unsigned long REDIRECT_STS_UPGRADE = 1 << 3;
+
+ /**
+ * Called when a redirect occurs. This may happen due to an HTTP 3xx status
+ * code. The purpose of this method is to notify the sink that a redirect
+ * is about to happen, but also to give the sink the right to veto the
+ * redirect by throwing or passing a failure-code in the callback.
+ *
+ * Note that vetoing the redirect simply means that |newChannel| will not
+ * be opened. It is important to understand that |oldChannel| will continue
+ * loading as if it received a HTTP 200, which includes notifying observers
+ * and possibly display or process content attached to the HTTP response.
+ * If the sink wants to prevent this loading it must explicitly deal with
+ * it, e.g. by calling |oldChannel->Cancel()|
+ *
+ * There is a certain freedom in implementing this method:
+ *
+ * If the return-value indicates success, a callback on |callback| is
+ * required. This callback can be done from within asyncOnChannelRedirect
+ * (effectively making the call synchronous) or at some point later
+ * (making the call asynchronous). Repeat: A callback must be done
+ * if this method returns successfully.
+ *
+ * If the return value indicates error (method throws an exception)
+ * the redirect is vetoed and no callback must be done. Repeat: No
+ * callback must be done if this method throws!
+ *
+ * @see nsIAsyncVerifyRedirectCallback::onRedirectVerifyCallback()
+ *
+ * @param oldChannel
+ * The channel that's being redirected.
+ * @param newChannel
+ * The new channel. This channel is not opened yet.
+ * @param flags
+ * Flags indicating the type of redirect. A bitmask consisting
+ * of flags from above.
+ * One of REDIRECT_TEMPORARY and REDIRECT_PERMANENT will always be
+ * set.
+ * @param callback
+ * Object to inform about the async result of this method
+ *
+ * @throw <any> Throwing an exception will cause the redirect to be
+ * cancelled
+ */
+ void asyncOnChannelRedirect(in nsIChannel oldChannel,
+ in nsIChannel newChannel,
+ in unsigned long flags,
+ in nsIAsyncVerifyRedirectCallback callback);
+};
diff --git a/netwerk/base/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..e17edaa8dc
--- /dev/null
+++ b/netwerk/base/nsIClassOfService.idl
@@ -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/. */
+
+#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.
+ */
+
+[scriptable, uuid(1ccb58ec-5e07-4cf9-a30d-ac5490d23b41)]
+interface nsIClassOfService : nsISupports
+{
+ attribute unsigned long classFlags;
+
+ void clearClassFlags(in unsigned long flags);
+ void addClassFlags(in unsigned long flags);
+
+ // 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 nor 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..7108e0be6b
--- /dev/null
+++ b/netwerk/base/nsIClassifiedChannel.idl
@@ -0,0 +1,195 @@
+/* -*- 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,
+
+ /**
+ * 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..d84fd2ed4f
--- /dev/null
+++ b/netwerk/base/nsIDashboardEventNotifier.idl
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[builtinclass, uuid(24fdfcbe-54cb-4997-8392-3c476126ea3b)]
+interface nsIDashboardEventNotifier : nsISupports
+{
+ /* These methods are called to register a websocket event with the dashboard
+ *
+ * A host is identified by the (aHost, aSerial) pair.
+ * aHost: the host's name: example.com
+ * aSerial: a number that uniquely identifies the websocket
+ *
+ * aEncrypted: if the connection is encrypted
+ * aLength: the length of the message in bytes
+ */
+ void addHost(in ACString aHost, in unsigned long aSerial, in boolean aEncrypted);
+ void removeHost(in ACString aHost, in unsigned long aSerial);
+ void newMsgSent(in ACString aHost, in unsigned long aSerial, in unsigned long aLength);
+ void newMsgReceived(in ACString aHost, in unsigned long aSerial, in unsigned long aLength);
+};
diff --git a/netwerk/base/nsIDeprecationWarner.idl b/netwerk/base/nsIDeprecationWarner.idl
new file mode 100644
index 0000000000..72303b8156
--- /dev/null
+++ b/netwerk/base/nsIDeprecationWarner.idl
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Interface for warning about deprecated operations. Consumers should
+ * attach this interface to the channel's notification callbacks/loadgroup.
+ */
+[uuid(665c5124-2c52-41ba-ae72-2393f8e76c25)]
+interface nsIDeprecationWarner : nsISupports
+{
+ /**
+ * Issue a deprecation warning.
+ *
+ * @param aWarning a warning code as declared in nsDeprecatedOperationList.h.
+ * @param aAsError optional boolean flag indicating whether the warning
+ * should be treated as an error.
+ */
+ void issueWarning(in uint32_t aWarning, [optional] in bool aAsError);
+};
diff --git a/netwerk/base/nsIDownloader.idl b/netwerk/base/nsIDownloader.idl
new file mode 100644
index 0000000000..738940b4bb
--- /dev/null
+++ b/netwerk/base/nsIDownloader.idl
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIFile;
+interface nsIDownloadObserver;
+
+/**
+ * nsIDownloader
+ *
+ * A downloader is a special implementation of a nsIStreamListener that will
+ * make the contents of the stream available as a file. This may utilize the
+ * disk cache as an optimization to avoid an extra copy of the data on disk.
+ * The resulting file is valid from the time the downloader completes until
+ * the last reference to the downloader is released.
+ */
+[scriptable, uuid(fafe41a9-a531-4d6d-89bc-588a6522fb4e)]
+interface nsIDownloader : nsIStreamListener
+{
+ /**
+ * Initialize this downloader
+ *
+ * @param observer
+ * the observer to be notified when the download completes.
+ * @param downloadLocation
+ * the location where the stream contents should be written.
+ * if null, the downloader will select a location and the
+ * resulting file will be deleted (or otherwise made invalid)
+ * when the downloader object is destroyed. if an explicit
+ * download location is specified then the resulting file will
+ * not be deleted, and it will be the callers responsibility
+ * to keep track of the file, etc.
+ */
+ void init(in nsIDownloadObserver observer,
+ in nsIFile downloadLocation);
+};
+
+[scriptable, uuid(44b3153e-a54e-4077-a527-b0325e40924e)]
+interface nsIDownloadObserver : nsISupports
+{
+ /**
+ * Called to signal a download that has completed.
+ */
+ void onDownloadComplete(in nsIDownloader downloader,
+ in nsIRequest request,
+ in nsISupports ctxt,
+ in nsresult status,
+ in nsIFile result);
+};
diff --git a/netwerk/base/nsIEncodedChannel.idl b/netwerk/base/nsIEncodedChannel.idl
new file mode 100644
index 0000000000..a7571a0a92
--- /dev/null
+++ b/netwerk/base/nsIEncodedChannel.idl
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIUTF8StringEnumerator;
+interface nsIStreamListener;
+interface nsISupports;
+
+/**
+ * A channel interface which allows special handling of encoded content
+ */
+
+[scriptable, uuid(29c29ce6-8ce4-45e6-8d60-36c8fa3e255b)]
+interface nsIEncodedChannel : nsISupports
+{
+ /**
+ * This attribute holds the MIME types corresponding to the content
+ * encodings on the channel. The enumerator returns nsISupportsCString
+ * objects. The first one corresponds to the outermost encoding on the
+ * channel and then we work our way inward. "identity" is skipped and not
+ * represented on the list. Unknown encodings make the enumeration stop.
+ * If you want the actual Content-Encoding value, use
+ * getResponseHeader("Content-Encoding").
+ *
+ * When there is no Content-Encoding header, this property is null.
+ *
+ * Modifying the Content-Encoding header on the channel will cause
+ * this enumerator to have undefined behavior. Don't do it.
+ *
+ * Also note that contentEncodings only exist during or after OnStartRequest.
+ * Calling contentEncodings before OnStartRequest is an error.
+ */
+ readonly attribute nsIUTF8StringEnumerator contentEncodings;
+
+ /**
+ * This attribute controls whether or not content conversion should be
+ * done per the Content-Encoding response header. applyConversion can only
+ * be set before or during OnStartRequest. Calling this during
+ * OnDataAvailable is an error.
+ *
+ * TRUE by default.
+ */
+ attribute boolean applyConversion;
+
+ /**
+ * This function will start converters if they are available.
+ * aNewNextListener will be nullptr if no converter is available.
+ */
+ void doApplyContentConversions(in nsIStreamListener aNextListener,
+ out nsIStreamListener aNewNextListener,
+ in nsISupports aCtxt);
+};
diff --git a/netwerk/base/nsIExternalProtocolHandler.idl b/netwerk/base/nsIExternalProtocolHandler.idl
new file mode 100644
index 0000000000..6f86dbb05a
--- /dev/null
+++ b/netwerk/base/nsIExternalProtocolHandler.idl
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIProtocolHandler.idl"
+
+[scriptable, uuid(0e61f3b2-34d7-4c79-bfdc-4860bc7341b7)]
+interface nsIExternalProtocolHandler: nsIProtocolHandler
+{
+ /**
+ * This method checks if the external handler exists for a given scheme.
+ *
+ * @param scheme external scheme.
+ * @return TRUE if the external handler exists for the input scheme, FALSE otherwise.
+ */
+ boolean externalAppExistsForScheme(in ACString scheme);
+};
diff --git a/netwerk/base/nsIFileStreams.idl b/netwerk/base/nsIFileStreams.idl
new file mode 100644
index 0000000000..4b704493ea
--- /dev/null
+++ b/netwerk/base/nsIFileStreams.idl
@@ -0,0 +1,238 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+#include "nsIOutputStream.idl"
+
+interface nsIEventTarget;
+interface nsIFile;
+interface nsIFileMetadataCallback;
+
+%{C++
+struct PRFileDesc;
+%}
+
+[ptr] native PRFileDescPtr(PRFileDesc);
+
+/**
+ * An input stream that allows you to read from a file.
+ */
+[scriptable, uuid(e3d56a20-c7ec-11d3-8cda-0060b0fc14a3)]
+interface nsIFileInputStream : nsIInputStream
+{
+ /**
+ * @param file file to read from
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_RDONLY).
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default value (0)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (see enumerations in the class)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * If this is set, the file will close automatically when the end of the
+ * file is reached.
+ */
+ const long CLOSE_ON_EOF = 1<<2;
+
+ /**
+ * If this is set, the file will be reopened whenever we reach the start of
+ * the file, either by doing a Seek(0, NS_SEEK_CUR), or by doing a relative
+ * seek that happen to reach the beginning of the file. If the file is
+ * already open and the seek occurs, it will happen naturally. (The file
+ * will only be reopened if it is closed for some reason.)
+ */
+ const long REOPEN_ON_REWIND = 1<<3;
+
+ /**
+ * If this is set, the file will be opened (i.e., a call to
+ * PR_Open done) only when we do an actual operation on the stream,
+ * or more specifically, when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Available
+ * - Read
+ * - ReadLine
+ *
+ * DEFER_OPEN is useful if we use the stream on a background
+ * thread, so that the opening and possible |stat|ing of the file
+ * happens there as well.
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first read. Also, the file is not locked when Init is called,
+ * so it might be deleted before we try to read from it.
+ */
+ const long DEFER_OPEN = 1<<4;
+
+ /**
+ * This flag has no effect and is totally ignored on any platform except
+ * Windows since this is the default behavior on POSIX systems. On Windows
+ * if this flag is set then the stream is opened in a special mode that
+ * allows the OS to delete the file from disk just like POSIX.
+ */
+ const long SHARE_DELETE = 1<<5;
+};
+
+/**
+ * An output stream that lets you stream to a file.
+ */
+[scriptable, uuid(e734cac9-1295-4e6f-9684-3ac4e1f91063)]
+interface nsIFileOutputStream : nsIOutputStream
+{
+ /**
+ * @param file file to write to
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_WRONLY |
+ * PR_CREATE_FILE | PR_TRUNCATE)
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default permissions (0664)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (currently none supported)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * @param length asks the operating system to allocate storage for
+ * this file of at least |length| bytes long, and
+ * set the file length to the corresponding size.
+ * @throws NS_ERROR_FAILURE if the preallocation fails.
+ * @throws NS_ERROR_NOT_INITIALIZED if the file is not opened.
+ */
+ [noscript] void preallocate(in long long length);
+
+ /**
+ * See the same constant in nsIFileInputStream. The deferred open will
+ * be performed when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Write
+ * - Flush
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first write, and if the file is to be created, then it will not
+ * appear on the disk until the first write.
+ */
+ const long DEFER_OPEN = 1<<0;
+};
+
+/**
+ * A stream that allows you to read from a file or stream to a file.
+ */
+[scriptable, uuid(82cf605a-8393-4550-83ab-43cd5578e006)]
+interface nsIFileStream : nsISupports
+{
+ /**
+ * @param file file to read from or stream to
+ * @param ioFlags file open flags listed in prio.h (see
+ * PR_Open documentation) or -1 to open the
+ * file in default mode (PR_RDWR).
+ * @param perm file mode bits listed in prio.h or -1 to
+ * use the default value (0)
+ * @param behaviorFlags flags specifying various behaviors of the class
+ * (see enumerations in the class)
+ */
+ void init(in nsIFile file, in long ioFlags, in long perm,
+ in long behaviorFlags);
+
+ /**
+ * See the same constant in nsIFileInputStream. The deferred open will
+ * be performed when one of the following is called:
+ * - Seek
+ * - Tell
+ * - SetEOF
+ * - Available
+ * - Read
+ * - Flush
+ * - Write
+ * - GetSize
+ * - GetLastModified
+ *
+ * @note Using this flag results in the file not being opened
+ * during the call to Init. This means that any errors that might
+ * happen when this flag is not set would happen during the
+ * first read or write. The file is not locked when Init is called,
+ * so it might be deleted before we try to read from it and if the
+ * file is to be created, then it will not appear on the disk until
+ * the first write.
+ */
+ const long DEFER_OPEN = 1<<0;
+};
+
+/**
+ * An interface that allows you to get some metadata like file size and
+ * file last modified time. 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, 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, 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..b915fd8327
--- /dev/null
+++ b/netwerk/base/nsIForcePendingChannel.idl
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIForcePending interface exposes a function that enables overwriting of the normal
+ * behavior for the channel's IsPending(), forcing 'true' to be returned.
+ */
+
+[noscript, uuid(2ac3e1ca-049f-44c3-a519-f0681f51e9b1)]
+interface nsIForcePendingChannel : nsISupports
+{
+
+/**
+ * forcePending(true) overrides the normal behavior for the
+ * channel's IsPending(), forcing 'true' to be returned. A call to
+ * forcePending(false) reverts IsPending() back to normal behavior.
+ */
+ void forcePending(in boolean aForcePending);
+};
diff --git a/netwerk/base/nsIFormPOSTActionChannel.idl b/netwerk/base/nsIFormPOSTActionChannel.idl
new file mode 100644
index 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..534f95ad38
--- /dev/null
+++ b/netwerk/base/nsIHttpAuthenticatorCallback.idl
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[builtinclass, uuid(d989cb03-e446-4086-b9e6-46842cb97bd5)]
+interface nsIHttpAuthenticatorCallback : nsISupports
+{
+ /**
+ * Authentication data for a header is available.
+ *
+ * @param aCreds
+ * Credentials which were obtained asynchonously.
+ * @param aFlags
+ * Flags set by asynchronous call.
+ * @param aResult
+ * Result status of credentials generation
+ * @param aSessionState
+ * Modified session state to be passed to caller
+ * @param aContinuationState
+ * Modified continuation state to be passed to caller
+ */
+ void onCredsGenerated(in string aCreds,
+ in unsigned long aFlags,
+ in nsresult aResult,
+ in nsISupports aSessionsState,
+ in nsISupports aContinuationState);
+
+};
+
diff --git a/netwerk/base/nsIHttpPushListener.idl b/netwerk/base/nsIHttpPushListener.idl
new file mode 100644
index 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..e162432fb1
--- /dev/null
+++ b/netwerk/base/nsIIOService.idl
@@ -0,0 +1,300 @@
+/* -*- 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;
+
+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 value of corresponding nsIProtocolHandler::protocolFlags
+ */
+ unsigned long getProtocolFlags(in string aScheme);
+
+ /**
+ * This method constructs a new URI by determining the scheme of the
+ * URI spec, and then delegating the construction of the URI to the
+ * protocol handler for that scheme. QueryInterface can be used on
+ * the resulting URI object to obtain a more specific type of URI.
+ *
+ * @see nsIProtocolHandler::newURI
+ */
+ nsIURI newURI(in AUTF8String aSpec,
+ [optional] in string aOriginCharset,
+ [optional] in nsIURI aBaseURI);
+
+ /**
+ * This method constructs a new URI from a nsIFile.
+ *
+ * @param aFile specifies the file path
+ * @return reference to a new nsIURI object
+ *
+ * Note: in the future, for perf reasons we should allow
+ * callers to specify whether this is a file or directory by
+ * splitting this into newDirURI() and newActualFileURI().
+ */
+ nsIURI newFileURI(in nsIFile aFile);
+
+ /**
+ * 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,
+ 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);
+
+ /**
+ * Returns true if networking is in "offline" mode. When in offline mode,
+ * attempts to access the network will fail (although this does not
+ * necessarily correlate with whether there is actually a network
+ * available -- that's hard to detect without causing the dialer to
+ * come up).
+ *
+ * Changing this fires observer notifications ... see below.
+ */
+ attribute boolean offline;
+
+ /**
+ * Returns false if there are no interfaces for a network request
+ */
+ readonly attribute boolean connectivity;
+
+ /**
+ * Checks if a port number is banned. This involves consulting a list of
+ * unsafe ports, corresponding to network services that may be easily
+ * exploitable. If the given port is considered unsafe, then the protocol
+ * handler (corresponding to aScheme) will be asked whether it wishes to
+ * override the IO service's decision to block the port. This gives the
+ * protocol handler ultimate control over its own security policy while
+ * ensuring reasonable, default protection.
+ *
+ * @see nsIProtocolHandler::allowPort
+ */
+ boolean allowPort(in long aPort, in string aScheme);
+
+ /**
+ * Utility to extract the scheme from a URL string, consistently and
+ * according to spec (see RFC 2396).
+ *
+ * NOTE: Most URL parsing is done via nsIURI, and in fact the scheme
+ * can also be extracted from a URL string via nsIURI. This method
+ * is provided purely as an optimization.
+ *
+ * @param aSpec the URL string to parse
+ * @return URL scheme, lowercase
+ *
+ * @throws NS_ERROR_MALFORMED_URI if URL string is not of the right form.
+ */
+ ACString extractScheme(in AUTF8String urlString);
+
+ /**
+ * Checks if a URI host is a local IPv4 or IPv6 address literal.
+ *
+ * @param nsIURI the URI that contains the hostname to check
+ * @return true if the URI hostname is a local IP address
+ */
+ boolean hostnameIsLocalIPAddress(in nsIURI aURI);
+
+ /**
+ * Checks if a URI host is a shared IPv4 address literal.
+ *
+ * @param nsIURI the URI that contains the hostname to check
+ * @return true if the URI hostname is a shared IP address
+ */
+ boolean hostnameIsSharedIPAddress(in nsIURI aURI);
+
+ /**
+ * While this is set, IOService will monitor an nsINetworkLinkService
+ * (if available) and set its offline status to "true" whenever
+ * isLinkUp is false.
+ *
+ * Applications that want to control changes to the IOService's offline
+ * status should set this to false, watch for network:link-status-changed
+ * broadcasts, and change nsIIOService::offline as they see fit. Note
+ * that this means during application startup, IOService may be offline
+ * if there is no link, until application code runs and can turn off
+ * this management.
+ */
+ attribute boolean manageOfflineStatus;
+
+ /**
+ * Creates a channel for a given URI.
+ *
+ * @param aURI
+ * nsIURI from which to make a channel
+ * @param aProxyURI
+ * nsIURI to use for proxy resolution. Can be null in which
+ * case aURI is used
+ * @param aProxyFlags flags from nsIProtocolProxyService to use
+ * when resolving proxies for this new channel
+ * @param aLoadingNode
+ * @param aLoadingPrincipal
+ * @param aTriggeringPrincipal
+ * @param aSecurityFlags
+ * @param aContentPolicyType
+ * These will be used as values for the nsILoadInfo object on the
+ * created channel. For details, see nsILoadInfo in nsILoadInfo.idl
+ * @return reference to the new nsIChannel object
+ *
+ * Please note, if you provide both a loadingNode and a loadingPrincipal,
+ * then loadingPrincipal must be equal to loadingNode->NodePrincipal().
+ * But less error prone is to just supply a loadingNode.
+ */
+ nsIChannel newChannelFromURIWithProxyFlags(in nsIURI aURI,
+ in nsIURI aProxyURI,
+ in unsigned long aProxyFlags,
+ in Node aLoadingNode,
+ in nsIPrincipal aLoadingPrincipal,
+ in nsIPrincipal aTriggeringPrincipal,
+ in unsigned long aSecurityFlags,
+ in nsContentPolicyType aContentPolicyType);
+
+ /**
+ * Return true if socket process is launched.
+ */
+ readonly attribute boolean socketProcessLaunched;
+};
+
+%{C++
+/**
+ * We send notifications through nsIObserverService with topic
+ * NS_IOSERVICE_GOING_OFFLINE_TOPIC and data NS_IOSERVICE_OFFLINE
+ * when 'offline' has changed from false to true, and we are about
+ * to shut down network services such as DNS. When those
+ * services have been shut down, we send a notification with
+ * topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data
+ * NS_IOSERVICE_OFFLINE.
+ *
+ * When 'offline' changes from true to false, then after
+ * network services have been restarted, we send a notification
+ * with topic NS_IOSERVICE_OFFLINE_STATUS_TOPIC and data
+ * NS_IOSERVICE_ONLINE.
+ */
+#define NS_IOSERVICE_GOING_OFFLINE_TOPIC "network:offline-about-to-go-offline"
+#define NS_IOSERVICE_OFFLINE_STATUS_TOPIC "network:offline-status-changed"
+#define NS_IOSERVICE_OFFLINE "offline"
+#define NS_IOSERVICE_ONLINE "online"
+
+%}
+
+[builtinclass, uuid(6633c0bf-d97a-428f-8ece-cb6a655fb95a)]
+interface nsIIOServiceInternal : nsISupports
+{
+ /**
+ * This is an internal method that should only be called from ContentChild
+ * in order to pass the connectivity state from the chrome process to the
+ * content process. It throws if called outside the content process.
+ */
+ void SetConnectivity(in boolean connectivity);
+
+ /**
+ * An internal method to asynchronously run our notifications that happen
+ * when we wake from sleep
+ */
+ void NotifyWakeup();
+};
diff --git a/netwerk/base/nsIIncrementalDownload.idl b/netwerk/base/nsIIncrementalDownload.idl
new file mode 100644
index 0000000000..3ae363c488
--- /dev/null
+++ b/netwerk/base/nsIIncrementalDownload.idl
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIRequest.idl"
+
+interface nsIURI;
+interface nsIFile;
+interface nsIRequestObserver;
+
+/**
+ * An incremental download object attempts to fetch a file piecemeal over time
+ * in an effort to minimize network bandwidth usage.
+ *
+ * Canceling a background download does not cause the file on disk to be
+ * deleted.
+ */
+[scriptable, uuid(6687823f-56c4-461d-93a1-7f6cb7dfbfba)]
+interface nsIIncrementalDownload : nsIRequest
+{
+ /**
+ * Initialize the incremental download object. If the destination file
+ * already exists, then only the remaining portion of the file will be
+ * fetched.
+ *
+ * NOTE: The downloader will create the destination file if it does not
+ * already exist. It will create the file with the permissions 0600 if
+ * needed. To affect the permissions of the file, consumers of this
+ * interface may create an empty file at the specified destination prior to
+ * starting the incremental download.
+ *
+ * NOTE: Since this class may create a temporary file at the specified
+ * destination, it is advisable for the consumer of this interface to specify
+ * a file name for the destination that would not tempt the user into
+ * double-clicking it. For example, it might be wise to append a file
+ * extension like ".part" to the end of the destination to protect users from
+ * accidentally running "blah.exe" before it is a complete file.
+ *
+ * @param uri
+ * The URI to fetch.
+ * @param destination
+ * The location where the file is to be stored.
+ * @param chunkSize
+ * The size of the chunks to fetch. A non-positive value results in
+ * the default chunk size being used.
+ * @param intervalInSeconds
+ * The amount of time to wait between fetching chunks. Pass a
+ * negative to use the default interval, or 0 to fetch the remaining
+ * part of the file in one chunk.
+ */
+ void init(in nsIURI uri, in nsIFile destination, in long chunkSize,
+ in long intervalInSeconds);
+
+ /**
+ * The URI being fetched.
+ */
+ readonly attribute nsIURI URI;
+
+ /**
+ * The URI being fetched after any redirects have been followed. This
+ * attribute is set just prior to calling OnStartRequest on the observer
+ * passed to the start method.
+ */
+ readonly attribute nsIURI finalURI;
+
+ /**
+ * The file where the download is being written.
+ */
+ readonly attribute nsIFile destination;
+
+ /**
+ * The total number of bytes for the requested file. This attribute is set
+ * just prior to calling OnStartRequest on the observer passed to the start
+ * method.
+ *
+ * This attribute has a value of -1 if the total size is unknown.
+ */
+ readonly attribute long long totalSize;
+
+ /**
+ * The current number of bytes downloaded so far. This attribute is set just
+ * prior to calling OnStartRequest on the observer passed to the start
+ * method.
+ *
+ * This attribute has a value of -1 if the current size is unknown.
+ */
+ readonly attribute long long currentSize;
+
+ /**
+ * Start the incremental download.
+ *
+ * @param observer
+ * An observer to be notified of various events. OnStartRequest is
+ * called when finalURI and totalSize have been determined or when an
+ * error occurs. OnStopRequest is called when the file is completely
+ * downloaded or when an error occurs. If this object implements
+ * nsIProgressEventSink, then its OnProgress method will be called as
+ * data is written to the destination file. If this object implements
+ * nsIInterfaceRequestor, then it will be assigned as the underlying
+ * channel's notification callbacks, which allows it to provide a
+ * nsIAuthPrompt implementation if needed by the channel, for example.
+ * @param ctxt
+ * User defined object forwarded to the observer's methods.
+ */
+ void start(in nsIRequestObserver observer,
+ in nsISupports ctxt);
+};
diff --git a/netwerk/base/nsIIncrementalStreamLoader.idl b/netwerk/base/nsIIncrementalStreamLoader.idl
new file mode 100644
index 0000000000..60aa9cfef5
--- /dev/null
+++ b/netwerk/base/nsIIncrementalStreamLoader.idl
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIRequest;
+interface nsIIncrementalStreamLoader;
+
+[scriptable, uuid(07c3d2cc-5454-4618-9f4f-cd93de9504a4)]
+interface nsIIncrementalStreamLoaderObserver : nsISupports
+{
+ /**
+ * Called when new data has arrived on the stream.
+ *
+ * @param loader the stream loader that loaded the stream.
+ * @param ctxt the context parameter of the underlying channel
+ * @param dataLength the length of the new data received
+ * @param data the contents of the new data received.
+ *
+ * This method will always be called asynchronously by the
+ * nsIIncrementalStreamLoader involved, on the thread that called the
+ * loader's init() method.
+ *
+ * If the observer wants to not accumulate all or portional of the data in
+ * the internal buffer, the consumedLength shall be set to the value of
+ * the dataLength or less. By default the consumedLength value is assumed 0.
+ * The data and dataLength reflect the non-consumed data and will be
+ * accumulated if consumedLength is not set.
+ *
+ * In comparison with onStreamComplete(), the data buffer cannot be
+ * adopted if this method returns NS_SUCCESS_ADOPTED_DATA.
+ */
+ void onIncrementalData(in nsIIncrementalStreamLoader loader,
+ in nsISupports ctxt,
+ in unsigned long dataLength,
+ [const,array,size_is(dataLength)] in octet data,
+ inout unsigned long consumedLength);
+
+ /**
+ * Called when the entire stream has been loaded.
+ *
+ * @param loader the stream loader that loaded the stream.
+ * @param ctxt the context parameter of the underlying channel
+ * @param status the status of the underlying channel
+ * @param resultLength the length of the data loaded
+ * @param result the data
+ *
+ * This method will always be called asynchronously by the
+ * nsIIncrementalStreamLoader involved, on the thread that called the
+ * loader's init() method.
+ *
+ * If the observer wants to take over responsibility for the
+ * data buffer (result), it returns NS_SUCCESS_ADOPTED_DATA
+ * in place of NS_OK as its success code. The loader will then
+ * "forget" about the data and not free() it after
+ * onStreamComplete() returns; observer must call free()
+ * when the data is no longer required.
+ */
+ void onStreamComplete(in nsIIncrementalStreamLoader loader,
+ in nsISupports ctxt,
+ in nsresult status,
+ in unsigned long resultLength,
+ [const,array,size_is(resultLength)] in octet result);
+};
+
+/**
+ * Asynchronously loads a channel into a memory buffer.
+ *
+ * To use this interface, first call init() with a nsIIncrementalStreamLoaderObserver
+ * that will be notified when the data has been loaded. Then call asyncOpen()
+ * on the channel with the nsIIncrementalStreamLoader as the listener. The context
+ * argument in the asyncOpen() call will be passed to the onStreamComplete()
+ * callback.
+ *
+ * XXX define behaviour for sizes >4 GB
+ */
+[scriptable, uuid(a023b060-ba23-431a-b449-2dd63e220554)]
+interface nsIIncrementalStreamLoader : nsIStreamListener
+{
+ /**
+ * Initialize this stream loader, and start loading the data.
+ *
+ * @param aObserver
+ * An observer that will be notified when the data is complete.
+ */
+ void init(in nsIIncrementalStreamLoaderObserver aObserver);
+
+ /**
+ * Gets the number of bytes read so far.
+ */
+ readonly attribute unsigned long numBytesRead;
+
+ /**
+ * Gets the request that loaded this file.
+ * null after the request has finished loading.
+ */
+ readonly attribute nsIRequest request;
+};
diff --git a/netwerk/base/nsIInputStreamChannel.idl b/netwerk/base/nsIInputStreamChannel.idl
new file mode 100644
index 0000000000..9172a2b283
--- /dev/null
+++ b/netwerk/base/nsIInputStreamChannel.idl
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIURI;
+
+/**
+ * nsIInputStreamChannel
+ *
+ * This interface provides methods to initialize an input stream channel.
+ * The input stream channel serves as a data pump for an input stream.
+ */
+[scriptable, uuid(ea730238-4bfd-4015-8489-8f264d05b343)]
+interface nsIInputStreamChannel : nsISupports
+{
+ /**
+ * Sets the URI for this channel. This must be called before the
+ * channel is opened, and it may only be called once.
+ */
+ void setURI(in nsIURI aURI);
+
+ /**
+ * Get/set the content stream
+ *
+ * This stream contains the data that will be pushed to the channel's
+ * stream listener. If the stream is non-blocking and supports the
+ * nsIAsyncInputStream interface, then the stream will be read directly.
+ * Otherwise, the stream will be read on a background thread.
+ *
+ * This attribute must be set before the channel is opened, and it may
+ * only be set once.
+ *
+ * @throws NS_ERROR_IN_PROGRESS if the setter is called after the channel
+ * has been opened.
+ */
+ attribute nsIInputStream contentStream;
+
+ /**
+ * Get/set the srcdoc data string. When the input stream channel is
+ * created to load a srcdoc iframe, this is set to hold the value of the
+ * srcdoc attribute.
+ *
+ * This should be the same value used to create contentStream, but this is
+ * not checked.
+ *
+ * Changing the value of this attribute will not otherwise affect the
+ * functionality of the channel or input stream.
+ */
+ attribute AString srcdocData;
+
+ /**
+ * Returns true if srcdocData has been set within the channel.
+ */
+ readonly attribute boolean isSrcdocChannel;
+
+ /**
+ * The base URI to be used for the channel. Used when the base URI cannot
+ * be inferred by other means, for example when this is a srcdoc channel.
+ */
+ attribute nsIURI baseURI;
+};
diff --git a/netwerk/base/nsIInputStreamPump.idl b/netwerk/base/nsIInputStreamPump.idl
new file mode 100644
index 0000000000..9d9cc22678
--- /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 nsIEventTarget;
+interface nsIInputStream;
+interface nsIStreamListener;
+
+/**
+ * nsIInputStreamPump
+ *
+ * This interface provides a means to configure and use a input stream pump
+ * instance. The input stream pump will asynchronously read from an input
+ * stream, and push data to an nsIStreamListener instance. It utilizes the
+ * current thread's nsIEventTarget in order to make reading from the stream
+ * asynchronous. A different thread can be used if the pump also implements
+ * nsIThreadRetargetableRequest.
+ *
+ * If the given stream supports nsIAsyncInputStream, then the stream pump will
+ * call the stream's AsyncWait method to drive the stream listener. Otherwise,
+ * the stream will be read on a background thread utilizing the stream
+ * transport service. More details are provided below.
+ */
+[scriptable, uuid(400F5468-97E7-4d2b-9C65-A82AECC7AE82)]
+interface nsIInputStreamPump : nsIRequest
+{
+ /**
+ * Initialize the input stream pump.
+ *
+ * @param aStream
+ * contains the data to be read. if the input stream is non-blocking,
+ * then it will be QI'd to nsIAsyncInputStream. if the QI succeeds
+ * then the stream will be read directly. otherwise, it will be read
+ * on a background thread using the stream transport service.
+ * @param 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 nsIEventTarget 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/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..e31fbcc6f0
--- /dev/null
+++ b/netwerk/base/nsILoadGroupChild.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsILoadGroup;
+
+/**
+ * nsILoadGroupChild provides a hierarchy of load groups so that the
+ * root load group can be used to conceptually tie a series of loading
+ * operations into a logical whole while still leaving them separate
+ * for the purposes of cancellation and status events.
+ */
+
+[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..f975d5a3c8
--- /dev/null
+++ b/netwerk/base/nsILoadInfo.idl
@@ -0,0 +1,1351 @@
+/* -*- 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"
+
+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 "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>);
+[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();
+
+ /**
+ * 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;
+
+ /**
+ * 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 the request will get upgraded, and the HTTPS-Only
+ * StreamListener got registered.
+ */
+ const unsigned long HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED = (1 << 1);
+
+ /**
+ * 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_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 indicates that the request should not be logged to the
+ * console.
+ */
+ const unsigned long HTTPS_ONLY_DO_NOT_LOG_TO_CONSOLE = (1 << 5);
+
+ /**
+ * 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;
+
+ /**
+ * 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 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;
+
+ /**
+ * True if the loading document has the storage permission. This value would
+ * be set during opening the channel.
+ */
+ [infallible] attribute boolean hasStoragePermission;
+
+ /**
+ * 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.
+ * This is important to know to handle the CSP directive navigate-to.
+ */
+ [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, CORS checks will be skipped.
+ */
+ [infallible] attribute boolean bypassCORSChecks;
+
+ /**
+ * 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;
+
+ /**
+ * 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 entry, the nsIRedirectHistoryEntry before the channel
+ * got redirected.
+ * @param aIsInternalRedirect should be true if the channel is going
+ * through an internal redirect, otherwise false.
+ */
+ void appendRedirectHistoryEntry(in nsIRedirectHistoryEntry entry,
+ 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;
+
+ /**
+ * Returns the null principal of the resulting resource if the SANDBOXED_ORIGIN
+ * flag is set. Otherwise returns null. This is used by
+ * GetChannelResultPrincipal() to ensure that the same null principal object
+ * is returned every time.
+ */
+ [notxpcom, nostdcall] readonly attribute nsIPrincipal sandboxedLoadingPrincipal;
+
+ /**
+ * Return the top-level principal, which is the principal of the top-level
+ * window.
+ */
+ [notxpcom, nostdcall] readonly attribute nsIPrincipal topLevelPrincipal;
+
+ /**
+ * Return the top-level storage area principal, which is the principal of
+ * the top-level window if it's not a 3rd party context, non tracking
+ * resource.
+ */
+ [notxpcom, nostdcall] readonly attribute nsIPrincipal topLevelStorageAreaPrincipal;
+
+ /**
+ * 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;
+
+ /**
+ * 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;
+
+ /**
+ * 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_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;
+ const uint32_t BLOCKING_REASON_CONTENT_POLICY_WEBEXT = 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;
+
+ /**
+ * 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,
+ };
+
+ /**
+ * 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;
+};
diff --git a/netwerk/base/nsIMIMEInputStream.idl b/netwerk/base/nsIMIMEInputStream.idl
new file mode 100644
index 0000000000..f3411beb1a
--- /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, 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..128d9c91ae
--- /dev/null
+++ b/netwerk/base/nsIMultiPartChannel.idl
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+
+/**
+ * An interface to access the the base channel
+ * associated with a MultiPartChannel.
+ */
+
+[scriptable, uuid(4fefb490-5567-11e5-a837-0800200c9a66)]
+interface nsIMultiPartChannel : nsISupports
+{
+ /**
+ * readonly attribute to access the underlying channel
+ */
+ readonly attribute nsIChannel baseChannel;
+
+ /**
+ * Attribute guaranteed to be different for different parts of
+ * the same multipart document.
+ */
+ readonly attribute uint32_t partID;
+
+ /**
+ * Set to true when onStopRequest is received from the base channel.
+ * The listener can check this from its onStopRequest to determine
+ * whether more data can be expected.
+ */
+ readonly attribute boolean isLastPart;
+};
+
+/**
+ * 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..b8daa30a58
--- /dev/null
+++ b/netwerk/base/nsINetUtil.idl
@@ -0,0 +1,215 @@
+/* -*- 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;
+
+ /**
+ * escape a string with %00-style escaping
+ */
+ ACString escapeString(in ACString aString, in unsigned long aEscapeType);
+
+ /** %XX-escape URL scheme */
+ const unsigned long ESCAPE_URL_SCHEME = 1;
+
+ /** %XX-escape username in the URL */
+ const unsigned long ESCAPE_URL_USERNAME = 1 << 1;
+
+ /** %XX-escape password in the URL */
+ const unsigned long ESCAPE_URL_PASSWORD = 1 << 2;
+
+ /** %XX-escape URL host */
+ const unsigned long ESCAPE_URL_HOST = 1 << 3;
+
+ /** %XX-escape URL directory */
+ const unsigned long ESCAPE_URL_DIRECTORY = 1 << 4;
+
+ /** %XX-escape file basename in the URL */
+ const unsigned long ESCAPE_URL_FILE_BASENAME = 1 << 5;
+
+ /** %XX-escape file extension in the URL */
+ const unsigned long ESCAPE_URL_FILE_EXTENSION = 1 << 6;
+
+ /** %XX-escape URL parameters */
+ const unsigned long ESCAPE_URL_PARAM = 1 << 7;
+
+ /** %XX-escape URL query */
+ const unsigned long ESCAPE_URL_QUERY = 1 << 8;
+
+ /** %XX-escape URL ref */
+ const unsigned long ESCAPE_URL_REF = 1 << 9;
+
+ /** %XX-escape URL path - same as escaping directory, basename and extension */
+ const unsigned long ESCAPE_URL_FILEPATH =
+ ESCAPE_URL_DIRECTORY | ESCAPE_URL_FILE_BASENAME | ESCAPE_URL_FILE_EXTENSION;
+
+ /** %XX-escape scheme, username, password, host, path, params, query and ref */
+ const unsigned long ESCAPE_URL_MINIMAL =
+ ESCAPE_URL_SCHEME | ESCAPE_URL_USERNAME | ESCAPE_URL_PASSWORD |
+ ESCAPE_URL_HOST | ESCAPE_URL_FILEPATH | ESCAPE_URL_PARAM |
+ ESCAPE_URL_QUERY | ESCAPE_URL_REF;
+
+ /** Force %XX-escaping of already escaped sequences */
+ const unsigned long ESCAPE_URL_FORCED = 1 << 10;
+
+ /** Skip non-ascii octets, %XX-escape all others */
+ const unsigned long ESCAPE_URL_ONLY_ASCII = 1 << 11;
+
+ /**
+ * Skip graphic octets (0x20-0x7E) when escaping
+ * Skips all ASCII octets (0x00-0x7F) when unescaping
+ */
+ const unsigned long ESCAPE_URL_ONLY_NONASCII = 1 << 12;
+
+ /** Force %XX-escape of colon */
+ const unsigned long ESCAPE_URL_COLON = 1 << 14;
+
+ /** Skip C0 and DEL from unescaping */
+ const unsigned long ESCAPE_URL_SKIP_CONTROL = 1 << 15;
+
+ /**
+ * %XX-Escape invalid chars in a URL segment.
+ *
+ * @param aStr the URL to be escaped
+ * @param aFlags the URL segment type flags
+ *
+ * @return the escaped string (the string itself if escaping did not happen)
+ *
+ */
+ ACString escapeURL(in ACString aStr, in unsigned long aFlags);
+
+ /**
+ * Expands URL escape sequences
+ *
+ * @param aStr the URL to be unescaped
+ * @param aFlags only ESCAPE_URL_ONLY_NONASCII and ESCAPE_URL_SKIP_CONTROL
+ * are recognized. If |aFlags| is 0 all escape sequences are
+ * unescaped
+ * @return unescaped string
+ */
+ ACString unescapeString(in AUTF8String aStr, in unsigned long aFlags);
+
+ /**
+ * Extract the charset parameter location and value from a content-type
+ * header.
+ *
+ * @param aTypeHeader the header string to parse
+ * @param [out] aCharset the charset parameter specified in the
+ * header, if any.
+ * @param [out] aCharsetStart index of the start of the charset parameter
+ * (the ';' separating it from what came before) in aTypeHeader.
+ * If this function returns false, this argument will still be
+ * set, to the index of the location where a new charset should
+ * be inserted.
+ * @param [out] aCharsetEnd index of the end of the charset parameter (the
+ * ';' separating it from what comes after, or the end
+ * of the string) in aTypeHeader. If this function returns
+ * false, this argument will still be set, to the index of the
+ * location where a new charset should be inserted.
+ *
+ * @return whether a charset parameter was found. This can be false even in
+ * cases when parseContentType would claim to have a charset, if the type
+ * that won out does not have a charset parameter specified.
+ */
+ boolean extractCharsetFromContentType(in AUTF8String aTypeHeader,
+ out AUTF8String aCharset,
+ out long aCharsetStart,
+ out long aCharsetEnd);
+
+ /**
+ * 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..64a4a71b03
--- /dev/null
+++ b/netwerk/base/nsINetworkInterceptController.idl
@@ -0,0 +1,264 @@
+/* -*- 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.
+ */
+ void resetInterception();
+
+ /**
+ * Set the status and reason for the forthcoming synthesized response.
+ * Multiple calls overwrite existing values.
+ */
+ void synthesizeStatus(in uint16_t status, in ACString reason);
+
+ /**
+ * Attach a header name/value pair to the forthcoming synthesized response.
+ * Overwrites any existing header value.
+ */
+ void synthesizeHeader(in ACString name, in ACString value);
+
+ /**
+ * Instruct a channel that has been intercepted that a response 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;
+
+ /**
+ * Save the timestamps of various service worker interception phases.
+ */
+ [noscript]
+ void SetLaunchServiceWorkerStart(in TimeStamp aTimeStamp);
+
+ // A hack to get sw launch start time for telemetry.
+ [noscript]
+ void GetLaunchServiceWorkerStart(out TimeStamp aTimeStamp);
+
+ [noscript]
+ void SetLaunchServiceWorkerEnd(in TimeStamp aTimeStamp);
+
+ // A hack to get sw launch end time for telemetry.
+ [noscript]
+ void GetLaunchServiceWorkerEnd(out TimeStamp aTimeStamp);
+
+ [noscript]
+ void SetDispatchFetchEventStart(in TimeStamp aTimeStamp);
+
+ [noscript]
+ void SetDispatchFetchEventEnd(in TimeStamp aTimeStamp);
+
+ [noscript]
+ void SetHandleFetchEventStart(in TimeStamp aTimeStamp);
+
+ [noscript]
+ void SetHandleFetchEventEnd(in TimeStamp aTimeStamp);
+
+ // Depending on the outcome we measure the time difference between
+ // |FinishResponseStart| and either |FinishSynthesizedResponseEnd| or
+ // |ChannelResetEnd|.
+ [noscript]
+ void SetFinishResponseStart(in TimeStamp aTimeStamp);
+
+ [noscript]
+ void SetFinishSynthesizedResponseEnd(in TimeStamp aTimeStamp);
+
+ [noscript]
+ void SetChannelResetEnd(in TimeStamp aTimeStamp);
+
+ [noscript]
+ void SaveTimeStamps();
+
+%{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: {
+ 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;
+ }
+ }
+ }
+ }
+%}
+
+ /**
+ * 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..eb5f541932
--- /dev/null
+++ b/netwerk/base/nsINetworkLinkService.idl
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Network link status monitoring service.
+ */
+[scriptable, uuid(103e5293-77b3-4b70-af59-6e9e4a1f994a)]
+interface nsINetworkLinkService : nsISupports
+{
+ /* Link type constants */
+ const unsigned long LINK_TYPE_UNKNOWN = 0;
+ const unsigned long LINK_TYPE_ETHERNET = 1;
+ const unsigned long LINK_TYPE_USB = 2;
+ const unsigned long LINK_TYPE_WIFI = 3;
+ const unsigned long LINK_TYPE_WIMAX = 4;
+ const unsigned long LINK_TYPE_2G = 5;
+ const unsigned long LINK_TYPE_3G = 6;
+ const unsigned long LINK_TYPE_4G = 7;
+
+ /**
+ * This is set to true when the system is believed to have a usable
+ * network connection.
+ *
+ * The link is only up when network connections can be established. For
+ * example, the link is down during DHCP configuration (unless there
+ * is another usable interface already configured).
+ *
+ * If the link status is not currently known, we generally assume that
+ * it is up.
+ */
+ readonly attribute boolean isLinkUp;
+
+ /**
+ * This is set to true when we believe that isLinkUp is accurate.
+ */
+ readonly attribute boolean linkStatusKnown;
+
+ /**
+ * The type of network connection.
+ */
+ readonly attribute unsigned long linkType;
+
+ /**
+ * 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;
+
+ 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 '2G' mobile connection (e.g. GSM, GPRS, EDGE) */
+#define NS_NETWORK_LINK_TYPE_2G "2g"
+
+/** A '3G' mobile connection (e.g. UMTS, CDMA) */
+#define NS_NETWORK_LINK_TYPE_3G "3g"
+
+/** A '4G' mobile connection (e.g. LTE, UMB) */
+#define NS_NETWORK_LINK_TYPE_4G "4g"
+%}
diff --git a/netwerk/base/nsINetworkPredictor.idl b/netwerk/base/nsINetworkPredictor.idl
new file mode 100644
index 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..1476671f00
--- /dev/null
+++ b/netwerk/base/nsIOService.cpp
@@ -0,0 +1,2039 @@
+/* -*- 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 "nsErrorService.h"
+#include "netCore.h"
+#include "nsIObserverService.h"
+#include "nsXPCOM.h"
+#include "nsIProxiedProtocolHandler.h"
+#include "nsIProxyInfo.h"
+#include "nsEscape.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "nsCRT.h"
+#include "nsSimpleNestedURI.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 "nsIWidget.h"
+#include "nsThreadUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/NeckoParent.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ContentParent.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 "nsExceptionHandler.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "nsNSSComponent.h"
+#include "ssl.h"
+
+#ifdef MOZ_WIDGET_GTK
+# include "nsGIOProtocolHandler.h"
+#endif
+
+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 MAX_RECURSION_COUNT 50
+
+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
+ 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;
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsIOService::nsIOService()
+ : mOffline(true),
+ mOfflineForProfileChange(false),
+ mManageLinkStatus(false),
+ mConnectivity(true),
+ mSettingOffline(false),
+ mSetOfflineValue(false),
+ mSocketProcessLaunchComplete(false),
+ mShutdown(false),
+ mHttpHandlerAlreadyShutingDown(false),
+ mNetworkLinkServiceInitialized(false),
+ mChannelEventSinks(NS_CHANNEL_EVENT_SINK_CATEGORY),
+ mMutex("nsIOService::mMutex"),
+ mTotalRequests(0),
+ mCacheWon(0),
+ mNetWon(0),
+ mLastOfflineStateChange(PR_IntervalNow()),
+ mLastConnectivityChange(PR_IntervalNow()),
+ mLastNetworkLinkChange(PR_IntervalNow()),
+ mNetTearingDownStarted(0),
+ mSocketProcess(nullptr) {}
+
+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,
+ nullptr,
+};
+
+static const char* gCallbackPrefsForSocketProcess[] = {
+ WEBRTC_PREF_PREFIX,
+ NETWORK_DNS_PREF,
+ "network.ssl_tokens_cache_enabled",
+ "network.send_ODA_to_content_directly",
+ "network.trr.",
+ "doh-rollout.",
+ "network.dns.disableIPv6",
+ "network.dns.skipTRR-when-parental-control-enabled",
+ "network.offline-mirrors-connectivity",
+ 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",
+ "security.cert_pinning.enforcement_level",
+ "security.pki.name_matching_mode",
+ nullptr,
+};
+
+nsresult nsIOService::Init() {
+ // XXX hack until xpidl supports error info directly (bug 13423)
+ nsCOMPtr<nsIErrorService> errorService = nsErrorService::GetOrCreate();
+ MOZ_ALWAYS_TRUE(errorService);
+ errorService->RegisterErrorStringBundle(NS_ERROR_MODULE_NETWORK,
+ NECKO_MSGS_URL);
+
+ SSLTokensCache::Init();
+
+ InitializeCaptivePortalService();
+
+ // setup our bad port list stuff
+ for (int i = 0; gBadPortList[i]; i++)
+ mRestrictedPortList.AppendElement(gBadPortList[i]);
+
+ // Further modifications to the port list come from prefs
+ Preferences::RegisterPrefixCallbacks(nsIOService::PrefsChanged,
+ gCallbackPrefs, this);
+ PrefsChanged();
+
+ mSocketProcessTopicBlackList.PutEntry(
+ nsLiteralCString(NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID));
+ mSocketProcessTopicBlackList.PutEntry(
+ nsLiteralCString(NS_XPCOM_SHUTDOWN_OBSERVER_ID));
+ mSocketProcessTopicBlackList.PutEntry("xpcom-shutdown-threads"_ns);
+ mSocketProcessTopicBlackList.PutEntry("profile-do-change"_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;
+ }
+
+ if (!UseSocketProcess()) {
+ return NS_OK;
+ }
+
+ nsAutoCString topic(aTopic);
+ if (mSocketProcessTopicBlackList.Contains(topic)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Avoid registering duplicate topics.
+ if (mObserverTopicForSocketProcess.Contains(topic)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mObserverTopicForSocketProcess.PutEntry(topic);
+
+ // This happens when AddObserver() is called by nsIOService::Init(). We don't
+ // want to add nsIOService again.
+ if (SameCOMIdentity(aObserver, static_cast<nsIObserver*>(this))) {
+ return NS_OK;
+ }
+
+ 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") ||
+ pref.EqualsLiteral("security.cert_pinning.enforcement_level") ||
+ pref.EqualsLiteral("security.pki.name_matching_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 (!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);
+ }
+};
+
+nsresult nsIOService::LaunchSocketProcess() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ return NS_OK;
+ }
+
+ if (mShutdown) {
+ 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 (!Preferences::GetBool("network.process.enabled", true)) {
+ 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 (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, Nothing(), Nothing());
+ Preferences::GetPreference(&pref);
+ 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 = true;
+
+ if (mShutdown || !SocketProcessReady()) {
+ 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();
+}
+
+RefPtr<MemoryReportingProcess> nsIOService::GetSocketProcessMemoryReporter() {
+ // Check the prefs here again, since we don't want to create
+ // SocketProcessMemoryReporter for some tests.
+ if (!Preferences::GetBool("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;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(host.BeginReading(), &prAddr) != PR_SUCCESS) {
+ // The redirect wasn't to an IP literal, so there's probably no need
+ // to trigger the captive portal detection right now. It can wait.
+ return NS_OK;
+ }
+
+ NetAddr netAddr(&prAddr);
+ if (netAddr.IsIPAddrLocal()) {
+ // Redirects to local IP addresses are probably captive portals
+ 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;
+}
+
+nsresult nsIOService::CacheProtocolHandler(const char* scheme,
+ nsIProtocolHandler* handler) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (unsigned int i = 0; i < NS_N(gScheme); i++) {
+ if (!nsCRT::strcasecmp(scheme, gScheme[i])) {
+ nsresult rv;
+ NS_ASSERTION(!mWeakHandler[i], "Protocol handler already cached");
+ // Make sure the handler supports weak references.
+ nsCOMPtr<nsISupportsWeakReference> factoryPtr =
+ do_QueryInterface(handler, &rv);
+ if (!factoryPtr) {
+ // Don't cache handlers that don't support weak reference as
+ // there is real danger of a circular reference.
+#ifdef DEBUG_dp
+ printf(
+ "DEBUG: %s protcol handler doesn't support weak ref. Not cached.\n",
+ scheme);
+#endif /* DEBUG_dp */
+ return NS_ERROR_FAILURE;
+ }
+ mWeakHandler[i] = do_GetWeakReference(handler);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsIOService::GetCachedProtocolHandler(const char* scheme,
+ nsIProtocolHandler** result,
+ uint32_t start, uint32_t end) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ uint32_t len = end - start - 1;
+ for (unsigned int i = 0; i < NS_N(gScheme); i++) {
+ if (!mWeakHandler[i]) continue;
+
+ // handle unterminated strings
+ // start is inclusive, end is exclusive, len = end - start - 1
+ if (end ? (!nsCRT::strncasecmp(scheme + start, gScheme[i], len) &&
+ gScheme[i][len] == '\0')
+ : (!nsCRT::strcasecmp(scheme, gScheme[i]))) {
+ return CallQueryReferent(mWeakHandler[i].get(), result);
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+static bool UsesExternalProtocolHandler(const char* aScheme) {
+ if ("file"_ns.Equals(aScheme) || "chrome"_ns.Equals(aScheme) ||
+ "resource"_ns.Equals(aScheme)) {
+ // Don't allow file:, chrome: or resource: URIs to be handled with
+ // nsExternalProtocolHandler, since internally we rely on being able to
+ // use and read from these URIs.
+ return false;
+ }
+
+ // When ftp protocol is disabled, return true if external protocol handler was
+ // not explicitly disabled by the prererence.
+ if ("ftp"_ns.Equals(aScheme) &&
+ !Preferences::GetBool("network.ftp.enabled", true) &&
+ Preferences::GetBool("network.protocol-handler.external.ftp", true)) {
+ return true;
+ }
+
+ for (const auto& forcedExternalScheme : gForcedExternalSchemes) {
+ if (!nsCRT::strcasecmp(forcedExternalScheme, aScheme)) {
+ return true;
+ }
+ }
+
+ nsAutoCString pref("network.protocol-handler.external.");
+ pref += aScheme;
+
+ return Preferences::GetBool(pref.get(), false);
+}
+
+NS_IMETHODIMP
+nsIOService::GetProtocolHandler(const char* scheme,
+ nsIProtocolHandler** result) {
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(scheme);
+ // XXX we may want to speed this up by introducing our own protocol
+ // scheme -> protocol handler mapping, avoiding the string manipulation
+ // and service manager stuff
+
+ rv = GetCachedProtocolHandler(scheme, result);
+ if (NS_SUCCEEDED(rv)) return rv;
+
+ if (scheme[0] != '\0' && !UsesExternalProtocolHandler(scheme)) {
+ nsAutoCString contractID(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX);
+ contractID += scheme;
+ ToLowerCase(contractID);
+
+ rv = CallGetService(contractID.get(), result);
+ if (NS_SUCCEEDED(rv)) {
+ CacheProtocolHandler(scheme, *result);
+ return rv;
+ }
+
+#ifdef MOZ_WIDGET_GTK
+ // check to see whether GVFS can handle this URI scheme. otherwise, we
+ // failover to using the default protocol handler.
+
+ RefPtr<nsGIOProtocolHandler> gioHandler =
+ nsGIOProtocolHandler::GetSingleton();
+ if (gioHandler->IsSupportedProtocol(nsCString(scheme))) {
+ gioHandler.forget(result);
+ return NS_OK;
+ }
+#endif
+ }
+
+ // Okay we don't have a protocol handler to handle this url type, so use
+ // the default protocol handler. This will cause urls to get dispatched
+ // out to the OS ('cause we can't do anything with them) when we try to
+ // read from a channel created by the default protocol handler.
+
+ rv = CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "default", result);
+ if (NS_FAILED(rv)) return NS_ERROR_UNKNOWN_PROTOCOL;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIOService::ExtractScheme(const nsACString& inURI, nsACString& scheme) {
+ return net_ExtractURLScheme(inURI, scheme);
+}
+
+NS_IMETHODIMP
+nsIOService::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;
+
+ PRNetAddr addr;
+ PRStatus result = PR_StringToNetAddr(host.get(), &addr);
+ if (result == PR_SUCCESS) {
+ NetAddr netAddr(&addr);
+ if (netAddr.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;
+
+ PRNetAddr addr;
+ PRStatus result = PR_StringToNetAddr(host.get(), &addr);
+ if (result == PR_SUCCESS) {
+ NetAddr netAddr(&addr);
+ if (netAddr.IsIPAddrShared()) {
+ *aResult = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::GetProtocolFlags(const char* scheme, uint32_t* flags) {
+ nsCOMPtr<nsIProtocolHandler> handler;
+ nsresult rv = GetProtocolHandler(scheme, getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ // We can't call DoGetProtocolFlags here because we don't have a URI. This
+ // API is used by (and only used by) extensions, which is why it's still
+ // around. Calling this on a scheme with dynamic flags will throw.
+ rv = handler->GetProtocolFlags(flags);
+#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 rv;
+}
+
+class AutoIncrement {
+ public:
+ explicit AutoIncrement(uint32_t* var) : mVar(var) { ++*var; }
+ ~AutoIncrement() { --*mVar; }
+
+ private:
+ uint32_t* mVar;
+};
+
+nsresult nsIOService::NewURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** result) {
+ 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;
+
+ nsAutoCString userPass;
+ uri->GetUserPass(userPass);
+ if (!userPass.IsEmpty()) {
+ 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,
+ nsIChannel** aResult) {
+ return NewChannelFromURIWithProxyFlagsInternal(
+ aURI,
+ nullptr, // aProxyURI
+ 0, // aProxyFlags
+ aLoadingNode, aLoadingPrincipal, aTriggeringPrincipal, aLoadingClientInfo,
+ aController, aSecurityFlags, aContentPolicyType, aSandboxFlags, 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,
+ nsIChannel** result) {
+ nsCOMPtr<nsILoadInfo> loadInfo = new LoadInfo(
+ aLoadingPrincipal, aTriggeringPrincipal, aLoadingNode, aSecurityFlags,
+ aContentPolicyType, aLoadingClientInfo, aController, aSandboxFlags);
+ 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;
+
+ uint32_t protoFlags;
+ rv = handler->DoGetProtocolFlags(aURI, &protoFlags);
+ 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,
+ 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);
+}
+
+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) {
+ 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()) {
+ 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) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ nsTArray<int32_t> restrictedPortList;
+ {
+ MutexAutoLock lock(mMutex);
+ 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();
+ }
+ }
+ }
+}
+
+void nsIOService::ParsePortList(const char* pref, bool remove) {
+ nsAutoCString portList;
+ nsTArray<int32_t> restrictedPortList;
+ {
+ MutexAutoLock lock(mMutex);
+ 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);
+ }
+ }
+ }
+ }
+
+ MutexAutoLock lock(mMutex);
+ 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 (!strcmp(topic, kProfileChangeNetTeardownTopic)) {
+ if (!mHttpHandlerAlreadyShutingDown) {
+ mNetTearingDownStarted = PR_IntervalNow();
+ }
+ mHttpHandlerAlreadyShutingDown = false;
+ if (!mOffline) {
+ mOfflineForProfileChange = true;
+ SetOffline(true);
+ }
+ } else if (!strcmp(topic, kProfileChangeNetRestoreTopic)) {
+ if (mOfflineForProfileChange) {
+ mOfflineForProfileChange = false;
+ SetOffline(false);
+ }
+ } else if (!strcmp(topic, kProfileDoChange)) {
+ if (data && 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;
+
+ SetOffline(true);
+
+ if (mCaptivePortalService) {
+ static_cast<CaptivePortalService*>(mCaptivePortalService.get())->Stop();
+ mCaptivePortalService = nullptr;
+ }
+
+ SSLTokensCache::Shutdown();
+
+ DestroySocketProcess();
+
+ if (IsSocketProcessChild()) {
+ Preferences::UnregisterCallbacks(nsIOService::OnTLSPrefChange,
+ gCallbackSecurityPrefs, this);
+ NSSShutdownForSocketProcess();
+ }
+ } 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);
+ }
+
+ if (UseSocketProcess() &&
+ mObserverTopicForSocketProcess.Contains(nsDependentCString(topic))) {
+ nsCString topicStr(topic);
+ nsString dataStr(data);
+ auto sendObserver = [topicStr, dataStr]() {
+ Unused << gIOService->mSocketProcess->GetActor()->SendNotifyObserver(
+ topicStr, dataStr);
+ };
+ CallOrWaitForSocketProcess(sendObserver);
+ }
+
+ return NS_OK;
+}
+
+// nsINetUtil interface
+NS_IMETHODIMP
+nsIOService::ParseRequestContentType(const nsACString& aTypeHeader,
+ nsACString& aCharset, bool* aHadCharset,
+ nsACString& aContentType) {
+ net_ParseRequestContentType(aTypeHeader, aContentType, aCharset, aHadCharset);
+ return NS_OK;
+}
+
+// nsINetUtil interface
+NS_IMETHODIMP
+nsIOService::ParseResponseContentType(const nsACString& aTypeHeader,
+ nsACString& aCharset, bool* aHadCharset,
+ nsACString& aContentType) {
+ net_ParseContentType(aTypeHeader, aContentType, aCharset, aHadCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::ProtocolHasFlags(nsIURI* uri, uint32_t flags, bool* result) {
+ NS_ENSURE_ARG(uri);
+
+ *result = false;
+ nsAutoCString scheme;
+ nsresult rv = uri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Grab the protocol flags from the URI.
+ uint32_t protocolFlags;
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = handler->DoGetProtocolFlags(uri, &protocolFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *result = (protocolFlags & flags) == flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOService::URIChainHasFlags(nsIURI* uri, uint32_t flags, bool* result) {
+ nsresult rv = ProtocolHasFlags(uri, flags, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*result) {
+ return rv;
+ }
+
+ // Dig deeper into the chain. Note that this is not a do/while loop to
+ // avoid the extra addref/release on |uri| in the common (non-nested) case.
+ nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(uri);
+ while (nestedURI) {
+ nsCOMPtr<nsIURI> innerURI;
+ rv = nestedURI->GetInnerURI(getter_AddRefs(innerURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ProtocolHasFlags(innerURI, flags, result);
+
+ if (*result) {
+ return rv;
+ }
+
+ nestedURI = do_QueryInterface(innerURI);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIOService::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)
+ : mCallbacks(aCallbacks), mIOService(aIOService) {}
+
+ private:
+ RefPtr<nsIInterfaceRequestor> mCallbacks;
+ RefPtr<nsIOService> mIOService;
+};
+
+NS_IMPL_ISUPPORTS(IOServiceProxyCallback, nsIProtocolProxyCallback)
+
+NS_IMETHODIMP
+IOServiceProxyCallback::OnProxyAvailable(nsICancelable* request,
+ nsIChannel* channel, nsIProxyInfo* pi,
+ nsresult status) {
+ // Checking proxy status for speculative connect
+ nsAutoCString type;
+ if (NS_SUCCEEDED(status) && pi && NS_SUCCEEDED(pi->GetType(type)) &&
+ !type.EqualsLiteral("direct")) {
+ // proxies dont do speculative connect
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = channel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ nsAutoCString scheme;
+ rv = uri->GetScheme(scheme);
+ if (NS_FAILED(rv)) return NS_OK;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = mIOService->GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return NS_OK;
+
+ nsCOMPtr<nsISpeculativeConnect> speculativeHandler =
+ do_QueryInterface(handler);
+ if (!speculativeHandler) return NS_OK;
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal();
+
+ nsLoadFlags loadFlags = 0;
+ channel->GetLoadFlags(&loadFlags);
+ if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
+ speculativeHandler->SpeculativeAnonymousConnect(uri, principal, mCallbacks);
+ } else {
+ speculativeHandler->SpeculativeConnect(uri, principal, mCallbacks);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsIOService::SpeculativeConnectInternal(
+ nsIURI* aURI, nsIPrincipal* aPrincipal, 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, 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, "We expect passing a principal here.");
+
+ if (!aPrincipal) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // 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);
+ 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) {
+ return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, false);
+}
+
+NS_IMETHODIMP
+nsIOService::SpeculativeAnonymousConnect(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ nsIInterfaceRequestor* aCallbacks) {
+ return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, true);
+}
+
+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;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsIOService.h b/netwerk/base/nsIOService.h
new file mode 100644
index 0000000000..5d5c3a66e3
--- /dev/null
+++ b/netwerk/base/nsIOService.h
@@ -0,0 +1,274 @@
+/* -*- 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 "nsDataHashtable.h"
+#include "nsWeakReference.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+#include "prtime.h"
+#include "nsICaptivePortalService.h"
+#include "nsIObserverService.h"
+#include "nsWeakReference.h"
+
+#define NS_N(x) (sizeof(x) / sizeof(*x))
+
+// We don't want to expose this observer topic.
+// Intended internal use only for remoting offline/inline events.
+// See Bug 552829
+#define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
+#define NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC "ipc:network:set-connectivity"
+
+static const char gScheme[][sizeof("moz-safe-about")] = {
+ "chrome", "file", "http", "https",
+ "jar", "data", "about", "moz-safe-about",
+ "resource", "moz-extension", "page-icon", "blob"};
+
+static const char gForcedExternalSchemes[][sizeof("moz-nullprincipal")] = {
+ "place", "fake-favicon-uri", "favicon", "moz-nullprincipal"};
+
+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();
+
+ static void OnTLSPrefChange(const char* aPref, void* aSelf);
+
+ nsresult LaunchSocketProcess();
+
+ private:
+ // These shouldn't be called directly:
+ // - construct using GetInstance
+ // - destroy using Release
+ nsIOService();
+ ~nsIOService();
+ nsresult SetConnectivityInternal(bool aConnectivity);
+
+ nsresult OnNetworkLinkEvent(const char* data);
+
+ nsresult GetCachedProtocolHandler(const char* scheme,
+ nsIProtocolHandler** hdlrResult,
+ uint32_t start = 0, uint32_t end = 0);
+ nsresult CacheProtocolHandler(const char* scheme, nsIProtocolHandler* hdlr);
+
+ nsresult InitializeCaptivePortalService();
+ nsresult RecheckCaptivePortalIfLocalRedirect(nsIChannel* newChan);
+
+ // Prefs wrangling
+ 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, nsIChannel** result);
+
+ nsresult NewChannelFromURIWithProxyFlagsInternal(nsIURI* aURI,
+ nsIURI* aProxyURI,
+ uint32_t aProxyFlags,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result);
+
+ nsresult SpeculativeConnectInternal(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ nsIInterfaceRequestor* aCallbacks,
+ bool aAnonymous);
+
+ void DestroySocketProcess();
+
+ private:
+ mozilla::Atomic<bool, mozilla::Relaxed> mOffline;
+ mozilla::Atomic<bool, mozilla::Relaxed> mOfflineForProfileChange;
+ bool mManageLinkStatus;
+ mozilla::Atomic<bool, mozilla::Relaxed> mConnectivity;
+
+ // Used to handle SetOffline() reentrancy. See the comment in
+ // SetOffline() for more details.
+ bool mSettingOffline;
+ bool mSetOfflineValue;
+
+ bool mSocketProcessLaunchComplete;
+
+ mozilla::Atomic<bool, mozilla::Relaxed> mShutdown;
+ mozilla::Atomic<bool, mozilla::Relaxed> mHttpHandlerAlreadyShutingDown;
+
+ nsCOMPtr<nsPISocketTransportService> mSocketTransportService;
+ nsCOMPtr<nsICaptivePortalService> mCaptivePortalService;
+ nsCOMPtr<nsINetworkLinkService> mNetworkLinkService;
+ bool mNetworkLinkServiceInitialized;
+
+ // Cached protocol handlers, only accessed on the main thread
+ nsWeakPtr mWeakHandler[NS_N(gScheme)];
+
+ // cached categories
+ nsCategoryCache<nsIChannelEventSink> mChannelEventSinks;
+
+ Mutex mMutex;
+ nsTArray<int32_t> mRestrictedPortList;
+
+ uint32_t mTotalRequests;
+ uint32_t mCacheWon;
+ uint32_t mNetWon;
+
+ // 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;
+
+ SocketProcessHost* mSocketProcess;
+
+ // 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.
+ nsTHashtable<nsCStringHashKey> mObserverTopicForSocketProcess;
+ // Some noticications (e.g., NS_XPCOM_SHUTDOWN_OBSERVER_ID) are triggered in
+ // socket process, so we should not send the notifications again.
+ nsTHashtable<nsCStringHashKey> mSocketProcessTopicBlackList;
+
+ 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..137d1907bd
--- /dev/null
+++ b/netwerk/base/nsIParentChannel.idl
@@ -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/. */
+
+#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 notify the HttpChannelChild that flash plugin state has changed.
+ */
+ [noscript] void notifyFlashPluginStateChanged(in nsIHttpChannel_FlashPluginState aState);
+
+ /**
+ * 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..b2fe8c83d2
--- /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;
+
+[builtinclass, 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 ParentChannelListener::OnRedirectResult and kept as
+ * mActiveChannel and mRedirectChannel in that class.
+ */
+ void completeRedirect(in boolean succeeded);
+};
diff --git a/netwerk/base/nsIPermission.idl b/netwerk/base/nsIPermission.idl
new file mode 100644
index 0000000000..1f6deb22f3
--- /dev/null
+++ b/netwerk/base/nsIPermission.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 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);
+
+ /**
+ * Similar to matches() but the principal's URI should be just an origin
+ * (no path, no queryString, etc).
+ */
+ [noscript] boolean matchesPrincipalForPermission(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..14ae70021f
--- /dev/null
+++ b/netwerk/base/nsIPermissionManager.idl
@@ -0,0 +1,223 @@
+/* -*- 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 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);
+
+ /**
+ * 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);
+
+ /**
+ * See testExactPermission() above.
+ * 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,
+ * @see testExactPermission. If false, subdomains will
+ * also be searched, @see testPermission.
+ * @returns The matching permission object, or null if no matching object
+ * was found. No matching object is equivalent to UNKNOWN_ACTION.
+ * @note Clients in general should prefer the test* methods unless they
+ * need to know the specific stored details.
+ * @note This method will always return null for the system principal.
+ */
+ nsIPermission getPermissionObject(in nsIPrincipal principal,
+ in 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);
+
+ /**
+ * Broadcasts permissions for the given principal to all content processes.
+ *
+ * DO NOT USE THIS METHOD if you can avoid it. It was added in bug XXX to
+ * handle the current temporary implementation of ServiceWorker debugging. It
+ * will be removed when service worker debugging is fixed.
+ *
+ * @param aPrincipal The principal to broadcast permissions for.
+ */
+ void broadcastPermissionsForPrincipalToAllContentProcesses(in nsIPrincipal aPrincipal);
+};
+
+%{ 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..a1166d151d
--- /dev/null
+++ b/netwerk/base/nsIPrompt.idl
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This is the prompt interface which can be used without knowlege of a
+ * parent window. The parentage is hidden by the GetInterface though
+ * which it is gotten. This interface is identical to nsIPromptService
+ * but without the parent nsIDOMWindow parameter. See nsIPromptService
+ * for all documentation.
+ *
+ * Accesskeys can be attached to buttons and checkboxes by inserting
+ * an & before the accesskey character. For a real &, use && instead.
+ */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(a63f70c0-148b-11d3-9333-00104ba0fd40)]
+interface nsIPrompt : nsISupports
+{
+ void alert(in wstring dialogTitle,
+ in wstring text);
+
+ void alertCheck(in wstring dialogTitle,
+ in wstring text,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean confirm(in wstring dialogTitle,
+ in wstring text);
+
+ boolean confirmCheck(in wstring dialogTitle,
+ in wstring text,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ const unsigned long BUTTON_POS_0 = 1;
+ const unsigned long BUTTON_POS_1 = 1 << 8;
+ const unsigned long BUTTON_POS_2 = 1 << 16;
+
+ const unsigned long BUTTON_TITLE_OK = 1;
+ const unsigned long BUTTON_TITLE_CANCEL = 2;
+ const unsigned long BUTTON_TITLE_YES = 3;
+ const unsigned long BUTTON_TITLE_NO = 4;
+ const unsigned long BUTTON_TITLE_SAVE = 5;
+ const unsigned long BUTTON_TITLE_DONT_SAVE = 6;
+ const unsigned long BUTTON_TITLE_REVERT = 7;
+
+ const unsigned long BUTTON_TITLE_IS_STRING = 127;
+
+ const unsigned long BUTTON_POS_0_DEFAULT = 0 << 24;
+ const unsigned long BUTTON_POS_1_DEFAULT = 1 << 24;
+ const unsigned long BUTTON_POS_2_DEFAULT = 2 << 24;
+
+ /* used for security dialogs, buttons are initially disabled */
+ const unsigned long BUTTON_DELAY_ENABLE = 1 << 26;
+
+ const unsigned long STD_OK_CANCEL_BUTTONS = (BUTTON_TITLE_OK * BUTTON_POS_0) +
+ (BUTTON_TITLE_CANCEL * BUTTON_POS_1);
+ const unsigned long STD_YES_NO_BUTTONS = (BUTTON_TITLE_YES * BUTTON_POS_0) +
+ (BUTTON_TITLE_NO * BUTTON_POS_1);
+
+
+ // 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;
+
+ int32_t confirmEx(in wstring dialogTitle,
+ in wstring text,
+ in unsigned long buttonFlags,
+ in wstring button0Title,
+ in wstring button1Title,
+ in wstring button2Title,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean prompt(in wstring dialogTitle,
+ in wstring text,
+ inout wstring value,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean promptPassword(in wstring dialogTitle,
+ in wstring text,
+ inout wstring password,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean promptUsernameAndPassword(in wstring dialogTitle,
+ in wstring text,
+ inout wstring username,
+ inout wstring password,
+ in wstring checkMsg,
+ inout boolean checkValue);
+
+ boolean select(in wstring dialogTitle,
+ in wstring text,
+ in Array<AString> selectList,
+ out long outSelection);
+};
diff --git a/netwerk/base/nsIProtocolHandler.idl b/netwerk/base/nsIProtocolHandler.idl
new file mode 100644
index 0000000000..a323603238
--- /dev/null
+++ b/netwerk/base/nsIProtocolHandler.idl
@@ -0,0 +1,312 @@
+/* -*- 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.
+ */
+ unsigned long getFlagsForURI(in nsIURI aURI);
+};
+
+/**
+ * nsIProtocolHandler
+ */
+[scriptable, uuid(a87210e6-7c8c-41f7-864d-df809015193e)]
+interface nsIProtocolHandler : nsISupports
+{
+ /**
+ * The scheme of this protocol (e.g., "file").
+ */
+ readonly attribute ACString scheme;
+
+ /**
+ * The default port is the port that this protocol normally uses.
+ * If a port does not make sense for the protocol (e.g., "about:")
+ * then -1 will be returned.
+ */
+ readonly attribute long defaultPort;
+
+ /**
+ * Returns the protocol specific flags (see flag definitions below).
+ */
+ readonly attribute unsigned long protocolFlags;
+
+%{C++
+ // Helper method to get the protocol flags in the right way.
+ nsresult DoGetProtocolFlags(nsIURI* aURI, uint32_t* aFlags)
+ {
+ nsCOMPtr<nsIProtocolHandlerWithDynamicFlags> dh = do_QueryInterface(this);
+ nsresult rv = dh ? dh->GetFlagsForURI(aURI, aFlags) : GetProtocolFlags(aFlags);
+ if (NS_SUCCEEDED(rv)) {
+#if !IS_ORIGIN_IS_FULL_SPEC_DEFINED
+ MOZ_RELEASE_ASSERT(!(*aFlags & nsIProtocolHandler::ORIGIN_IS_FULL_SPEC),
+ "ORIGIN_IS_FULL_SPEC is unsupported but used");
+#endif
+ }
+ return rv;
+ }
+%}
+
+ /**
+ * 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: Implementation must ignore any flags they do not understand.
+ */
+
+ /**
+ * standard full URI with authority component and concept of relative
+ * URIs (http, ftp, ...)
+ */
+ const unsigned long URI_STD = 0;
+
+ /**
+ * no concept of relative URIs (about, javascript, finger, ...)
+ */
+ const unsigned long URI_NORELATIVE = (1<<0);
+
+ /**
+ * no authority component (file, ...)
+ */
+ const unsigned long URI_NOAUTH = (1<<1);
+
+ /**
+ * This protocol handler can be proxied via a proxy (socks or http)
+ * (e.g., irc, smtp, http, etc.). If the protocol supports transparent
+ * proxying, the handler should implement nsIProxiedProtocolHandler.
+ *
+ * If it supports only HTTP proxying, then it need not support
+ * nsIProxiedProtocolHandler, but should instead set the ALLOWS_PROXY_HTTP
+ * flag (see below).
+ *
+ * @see nsIProxiedProtocolHandler
+ */
+ const unsigned long ALLOWS_PROXY = (1<<2);
+
+ /**
+ * This protocol handler can be proxied using a http proxy (e.g., http,
+ * ftp, etc.). nsIIOService::newChannelFromURI will feed URIs from this
+ * protocol handler to the HTTP protocol handler instead. This flag is
+ * ignored if ALLOWS_PROXY is not set.
+ */
+ const unsigned long ALLOWS_PROXY_HTTP = (1<<3);
+
+ /**
+ * The URIs for this protocol have no inherent security context, so
+ * documents loaded via this protocol should inherit the security context
+ * from the document that loads them.
+ */
+ const unsigned long URI_INHERITS_SECURITY_CONTEXT = (1<<4);
+
+ /**
+ * "Automatic" loads that would replace the document (e.g. <meta> refresh,
+ * certain types of XLinks, possibly other loads that the application
+ * decides are not user triggered) are not allowed if the originating (NOT
+ * the target) URI has this protocol flag. Note that the decision as to
+ * what constitutes an "automatic" load is made externally, by the caller
+ * of nsIScriptSecurityManager::CheckLoadURI. See documentation for that
+ * method for more information.
+ *
+ * A typical protocol that might want to set this flag is a protocol that
+ * shows highly untrusted content in a viewing area that the user expects
+ * to have a lot of control over, such as an e-mail reader.
+ */
+ const unsigned long URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT = (1<<5);
+
+ /**
+ * +-------------------------------------------------------------------+
+ * | |
+ * | ALL PROTOCOL HANDLERS MUST SET ONE OF THE FOLLOWING FIVE FLAGS. |
+ * | |
+ * +-------------------------------------------------------------------+
+ *
+ * * 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.
+ */
+ 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);
+};
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..c07592d38a
--- /dev/null
+++ b/netwerk/base/nsIProtocolProxyService.idl
@@ -0,0 +1,296 @@
+/* -*- 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;
+
+/**
+ * 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 ACString aUsername, in ACString 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);
+
+ /**
+ * 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..593fc3740f
--- /dev/null
+++ b/netwerk/base/nsIProxyInfo.idl
@@ -0,0 +1,100 @@
+/* -*- 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.
+ */
+ 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;
+
+ /**
+ * 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..29ee25bbe1
--- /dev/null
+++ b/netwerk/base/nsIRandomGenerator.idl
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Interface used to generate random data.
+ *
+ * @threadsafe
+ */
+[scriptable, uuid(2362d97a-747a-4576-8863-697667309209)]
+interface nsIRandomGenerator : nsISupports {
+ /**
+ * Generates the specified amount of random bytes.
+ *
+ * @param aLength
+ * The length of the data to generate.
+ * @param aBuffer
+ * A buffer that contains random bytes of size aLength.
+ */
+ void generateRandomBytes(in unsigned long aLength,
+ [retval, array, size_is(aLength)] out octet aBuffer);
+};
diff --git a/netwerk/base/nsIRedirectChannelRegistrar.idl b/netwerk/base/nsIRedirectChannelRegistrar.idl
new file mode 100644
index 0000000000..02c8abdaaf
--- /dev/null
+++ b/netwerk/base/nsIRedirectChannelRegistrar.idl
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIParentChannel;
+
+/**
+ * Used on the chrome process as a service to join channel implementation
+ * and parent IPC protocol side under a unique id. Provides this way a generic
+ * communication while redirecting to various protocols.
+ *
+ * See also nsIChildChannel and nsIParentChannel.
+ */
+
+[scriptable, uuid (efa36ea2-5b07-46fc-9534-a5acb8b77b72)]
+interface nsIRedirectChannelRegistrar : nsISupports
+{
+ /**
+ * Register the redirect target channel. 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 ParentChannelListener::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 ParentChannelListener::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..1afb3e9ecc
--- /dev/null
+++ b/netwerk/base/nsIRedirectResultListener.idl
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(85cd2640-e91e-41ac-bdca-1dbf10dc131e)]
+interface nsIRedirectResultListener : nsISupports
+{
+ /**
+ * When an HTTP redirect has been processed (either successfully or not)
+ * nsIHttpChannel will call this function if its callbacks implement this
+ * interface.
+ *
+ * @param proceeding
+ * Indicated whether the redirect will be proceeding, or not (i.e.
+ * has been canceled, or failed).
+ */
+ void onRedirectResult(in boolean proceeding);
+};
diff --git a/netwerk/base/nsIRequest.idl b/netwerk/base/nsIRequest.idl
new file mode 100644
index 0000000000..6473d02d00
--- /dev/null
+++ b/netwerk/base/nsIRequest.idl
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsILoadGroup;
+
+typedef unsigned long nsLoadFlags;
+
+/**
+ * nsIRequest
+ */
+[scriptable, uuid(ef6bfbd2-fd46-48d8-96b7-9f8f0fd387fe)]
+interface nsIRequest : nsISupports
+{
+ /**
+ * The name of the request. Often this is the URI of the request.
+ */
+ readonly attribute AUTF8String name;
+
+ /**
+ * Indicates whether the request is pending. nsIRequest::isPending is
+ * true when there is an outstanding asynchronous event that will make
+ * the request no longer be pending. Requests do not necessarily start
+ * out pending; in some cases, requests have to be explicitly initiated
+ * (e.g. nsIChannel implementations are only pending once asyncOpen
+ * returns successfully).
+ *
+ * Requests can become pending multiple times during their lifetime.
+ *
+ * @return TRUE if the request has yet to reach completion.
+ * @return FALSE if the request has reached completion (e.g., after
+ * OnStopRequest has fired).
+ * @note Suspended requests are still considered pending.
+ */
+ boolean isPending();
+
+ /**
+ * The error status associated with the request.
+ */
+ readonly attribute nsresult status;
+
+ /**
+ * Cancels the current request. This will close any open input or
+ * output streams and terminate any async requests. Users should
+ * normally pass NS_BINDING_ABORTED, although other errors may also
+ * be passed. The error passed in will become the value of the
+ * status attribute.
+ *
+ * Implementations must not send any notifications (e.g. via
+ * nsIRequestObserver) synchronously from this function. Similarly,
+ * removal from the load group (if any) must also happen asynchronously.
+ *
+ * Requests that use nsIStreamListener must not call onDataAvailable
+ * anymore after cancel has been called.
+ *
+ * @param aStatus the reason for canceling this request.
+ *
+ * NOTE: most nsIRequest implementations expect aStatus to be a
+ * failure code; however, some implementations may allow aStatus to
+ * be a success code such as NS_OK. In general, aStatus should be
+ * a failure code.
+ */
+ void cancel(in nsresult aStatus);
+
+ /**
+ * Suspends the current request. This may have the effect of closing
+ * any underlying transport (in order to free up resources), although
+ * any open streams remain logically opened and will continue delivering
+ * data when the transport is resumed.
+ *
+ * Calling cancel() on a suspended request must not send any
+ * notifications (such as onstopRequest) until the request is resumed.
+ *
+ * NOTE: some implementations are unable to immediately suspend, and
+ * may continue to deliver events already posted to an event queue. In
+ * general, callers should be capable of handling events even after
+ * suspending a request.
+ */
+ void suspend();
+
+ /**
+ * Resumes the current request. This may have the effect of re-opening
+ * any underlying transport and will resume the delivery of data to
+ * any open streams.
+ */
+ void resume();
+
+ /**
+ * The load group of this request. While pending, the request is a
+ * member of the load group. It is the responsibility of the request
+ * to implement this policy.
+ */
+ attribute nsILoadGroup loadGroup;
+
+ /**
+ * The load flags of this request. Bits 0-15 are reserved.
+ *
+ * When added to a load group, this request's load flags are merged with
+ * the load flags of the load group.
+ */
+ attribute nsLoadFlags loadFlags;
+
+ /**
+ * Mask defining the bits reserved for nsIRequest LoadFlags
+ */
+ const unsigned long LOAD_REQUESTMASK = 0xFFFF;
+
+ /**************************************************************************
+ * Listed below are the various load flags which may be or'd together.
+ */
+
+ /**
+ * No special load flags:
+ */
+ const unsigned long LOAD_NORMAL = 0;
+
+ /**
+ * Do not deliver status notifications to the nsIProgressEventSink and
+ * do not block the loadgroup from completing (should this load belong to one).
+ * Note: Progress notifications will still be delivered.
+ */
+ const unsigned long LOAD_BACKGROUND = 1 << 0;
+
+ /**
+ * 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 : 8 {
+ 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_DISABLED_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);
+
+ /**************************************************************************
+ * 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;
+};
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..772de50d32
--- /dev/null
+++ b/netwerk/base/nsIRequestObserver.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 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.
+ */
+ 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.
+ */
+ 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..61e38602ff
--- /dev/null
+++ b/netwerk/base/nsIServerSocket.idl
@@ -0,0 +1,262 @@
+/* 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);
+
+ /**
+ * 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/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..aadbdb4c76
--- /dev/null
+++ b/netwerk/base/nsISocketTransport.idl
@@ -0,0 +1,349 @@
+/* -*- 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"
+
+interface nsIInterfaceRequestor;
+interface nsINetAddr;
+
+%{ C++
+#include "mozilla/BasePrincipal.h"
+namespace mozilla {
+namespace net {
+union NetAddr;
+class TCPFastOpen;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+native OriginAttributes(mozilla::OriginAttributes);
+[ref] native const_OriginAttributesRef(const mozilla::OriginAttributes);
+[ptr] native TCPFastOpenPtr(mozilla::net::TCPFastOpen);
+
+/**
+ * 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();
+
+ /**
+ * Security info object returned from the secure socket provider. This
+ * object supports nsISSLSocketControl, nsITransportSecurityInfo, and
+ * possibly other interfaces.
+ *
+ * This attribute is only available once the socket is connected.
+ */
+ readonly attribute nsISupports securityInfo;
+
+ /**
+ * Security notification callbacks passed to the secure socket provider
+ * via nsISSLSocketControl at socket creation time.
+ *
+ * NOTE: this attribute cannot be changed once a stream has been opened.
+ */
+ attribute nsIInterfaceRequestor securityCallbacks;
+
+ /**
+ * Test if this socket transport is (still) connected.
+ */
+ boolean isAlive();
+
+ /**
+ * Socket timeouts in seconds. To specify no timeout, pass UINT32_MAX
+ * as aValue to setTimeout. The implementation may truncate timeout values
+ * to a smaller range of values (e.g., 0 to 0xFFFF).
+ */
+ unsigned long getTimeout(in unsigned long aType);
+ void setTimeout(in unsigned long aType, in unsigned long aValue);
+
+ /**
+ * 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 = 0x804b0003;
+ const unsigned long STATUS_RESOLVED = 0x804b000b;
+ const unsigned long STATUS_CONNECTING_TO = 0x804b0007;
+ const unsigned long STATUS_CONNECTED_TO = 0x804b0004;
+ const unsigned long STATUS_SENDING_TO = 0x804b0005;
+ const unsigned long STATUS_WAITING_FOR = 0x804b000a;
+ const unsigned long STATUS_RECEIVING_FROM = 0x804b0006;
+ const unsigned long STATUS_TLS_HANDSHAKE_STARTING = 0x804b000c;
+ const unsigned long STATUS_TLS_HANDSHAKE_ENDED = 0x804b000d;
+
+ /**
+ * 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);
+
+ /**
+ * 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);
+
+ [noscript] void setFastOpenCallback(in TCPFastOpenPtr aFastOpen);
+
+ readonly attribute nsresult firstRetryError;
+
+ /**
+ * 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();
+
+ /**
+ * 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);
+};
diff --git a/netwerk/base/nsISocketTransportService.idl b/netwerk/base/nsISocketTransportService.idl
new file mode 100644
index 0000000000..4de6655880
--- /dev/null
+++ b/netwerk/base/nsISocketTransportService.idl
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsISocketTransport;
+interface nsIProxyInfo;
+interface nsIRunnable;
+
+%{C++
+class nsASocketHandler;
+struct PRFileDesc;
+%}
+
+[ptr] native PRFileDescPtr(PRFileDesc);
+[ptr] native nsASocketHandlerPtr(nsASocketHandler);
+
+[scriptable, 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).
+ *
+ * @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);
+
+ /**
+ * 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);
+};
diff --git a/netwerk/base/nsISpeculativeConnect.idl b/netwerk/base/nsISpeculativeConnect.idl
new file mode 100644
index 0000000000..fd53520ef3
--- /dev/null
+++ b/netwerk/base/nsISpeculativeConnect.idl
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIPrincipal;
+interface nsIURI;
+interface nsIInterfaceRequestor;
+
+[scriptable, uuid(d74a17ac-5b8a-4824-a309-b1f04a3c4aed)]
+interface nsISpeculativeConnect : nsISupports
+{
+ /**
+ * Called as a hint to indicate a new transaction for the URI is likely coming
+ * soon. The implementer may use this information to start a TCP
+ * and/or SSL level handshake for that resource immediately so that it is
+ * ready and/or progressed when the transaction is actually submitted.
+ *
+ * No obligation is taken on by the implementer, nor is the submitter obligated
+ * to actually open the new channel.
+ *
+ * @param aURI the URI of the hinted transaction
+ * @param aPrincipal the principal that will be used for opening the
+ * channel of the hinted transaction.
+ * @param aCallbacks any security callbacks for use with SSL for interfaces.
+ * May be null.
+ *
+ */
+ void speculativeConnect(in nsIURI aURI,
+ in nsIPrincipal aPrincipal,
+ in nsIInterfaceRequestor aCallbacks);
+
+ void speculativeAnonymousConnect(in nsIURI aURI,
+ in nsIPrincipal aPrincipal,
+ in nsIInterfaceRequestor aCallbacks);
+};
+
+/**
+ * This is used to override the default values for various values (documented
+ * inline) to determine whether or not to actually make a speculative
+ * connection.
+ */
+[builtinclass, uuid(1040ebe3-6ed1-45a6-8587-995e082518d7)]
+interface nsISpeculativeConnectionOverrider : nsISupports
+{
+ /**
+ * Used to determine the maximum number of unused speculative connections
+ * we will have open for a host at any one time
+ */
+ [infallible] readonly attribute unsigned long parallelSpeculativeConnectLimit;
+
+ /**
+ * Used to determine if we will ignore the existence of any currently idle
+ * connections when we decide whether or not to make a speculative
+ * connection.
+ */
+ [infallible] readonly attribute boolean ignoreIdle;
+
+ /*
+ * Used by the Predictor to gather telemetry data on speculative connection
+ * usage.
+ */
+ [infallible] readonly attribute boolean isFromPredictor;
+
+ /**
+ * by default speculative connections are not made to RFC 1918 addresses
+ */
+ [infallible] readonly attribute boolean allow1918;
+};
diff --git a/netwerk/base/nsIStandardURL.idl b/netwerk/base/nsIStandardURL.idl
new file mode 100644
index 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..d51bdf3c46
--- /dev/null
+++ b/netwerk/base/nsIStreamListenerTee.idl
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIOutputStream;
+interface nsIRequestObserver;
+interface nsIEventTarget;
+
+/**
+ * As data "flows" into a stream listener tee, it is copied to the output stream
+ * and then forwarded to the real listener.
+ */
+[scriptable, uuid(62b27fc1-6e8c-4225-8ad0-b9d44252973a)]
+interface nsIStreamListenerTee : nsIStreamListener
+{
+ /**
+ * Initalize the tee.
+ *
+ * @param listener
+ * the original listener the tee will propagate onStartRequest,
+ * onDataAvailable and onStopRequest notifications to, exceptions from
+ * the listener will be propagated back to the channel
+ * @param sink
+ * the stream the data coming from the channel will be written to,
+ * should be blocking
+ * @param requestObserver
+ * optional parameter, listener that gets only onStartRequest and
+ * onStopRequest notifications; exceptions threw within this optional
+ * observer are also propagated to the channel, but exceptions from
+ * the original listener (listener parameter) are privileged
+ */
+ void init(in nsIStreamListener listener,
+ in nsIOutputStream sink,
+ [optional] in nsIRequestObserver requestObserver);
+
+ /**
+ * Initalize the tee like above, but with the extra parameter to make it
+ * possible to copy the output asynchronously
+ * @param anEventTarget
+ * if set, this event-target is used to copy data to the output stream,
+ * giving an asynchronous tee
+ */
+ void initAsync(in nsIStreamListener listener,
+ in nsIEventTarget eventTarget,
+ in nsIOutputStream sink,
+ [optional] in nsIRequestObserver requestObserver);
+
+};
diff --git a/netwerk/base/nsIStreamLoader.idl b/netwerk/base/nsIStreamLoader.idl
new file mode 100644
index 0000000000..274a07e9d0
--- /dev/null
+++ b/netwerk/base/nsIStreamLoader.idl
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIStreamListener.idl"
+
+interface nsIRequest;
+interface nsIStreamLoader;
+
+[scriptable, uuid(359F7990-D4E9-11d3-A1A5-0050041CAF44)]
+interface nsIStreamLoaderObserver : nsISupports
+{
+ /**
+ * Called when the entire stream has been loaded.
+ *
+ * @param loader the stream loader that loaded the stream.
+ * @param ctxt the context parameter of the underlying channel
+ * @param status the status of the underlying channel
+ * @param resultLength the length of the data loaded
+ * @param result the data
+ *
+ * This method will always be called asynchronously by the
+ * nsIStreamLoader involved, on the thread that called the
+ * loader's init() method.
+ *
+ * If the observer wants to take over responsibility for the
+ * data buffer (result), it returns NS_SUCCESS_ADOPTED_DATA
+ * in place of NS_OK as its success code. The loader will then
+ * "forget" about the data and not free() it after
+ * onStreamComplete() returns; observer must call free()
+ * when the data is no longer required.
+ */
+ void onStreamComplete(in nsIStreamLoader loader,
+ in nsISupports ctxt,
+ in nsresult status,
+ in unsigned long resultLength,
+ [const,array,size_is(resultLength)] in octet result);
+};
+
+/**
+ * Asynchronously loads a channel into a memory buffer.
+ *
+ * To use this interface, first call init() with a nsIStreamLoaderObserver
+ * that will be notified when the data has been loaded. Then call asyncOpen()
+ * on the channel with the nsIStreamLoader as the listener. The context
+ * argument in the asyncOpen() call will be passed to the onStreamComplete()
+ * callback.
+ *
+ * XXX define behaviour for sizes >4 GB
+ */
+[scriptable, uuid(323bcff1-7513-4e1f-a541-1c9213c2ed1b)]
+interface nsIStreamLoader : nsIStreamListener
+{
+ /**
+ * Initialize this stream loader, and start loading the data.
+ *
+ * @param aStreamObserver
+ * An observer that will be notified when the data is complete.
+ * @param aRequestObserver
+ * An optional observer that will be notified when the request
+ * has started or stopped.
+ */
+ void init(in nsIStreamLoaderObserver aStreamObserver,
+ [optional] in nsIRequestObserver aRequestObserver);
+
+ /**
+ * Gets the number of bytes read so far.
+ */
+ readonly attribute unsigned long numBytesRead;
+
+ /**
+ * Gets the request that loaded this file.
+ * null after the request has finished loading.
+ */
+ readonly attribute nsIRequest request;
+};
diff --git a/netwerk/base/nsIStreamTransportService.idl b/netwerk/base/nsIStreamTransportService.idl
new file mode 100644
index 0000000000..e3ac6e6b18
--- /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);
+};
+
+[builtinclass, 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..ac5a6444ba
--- /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 nsIEventTarget;
+
+/**
+ * nsIThreadRetargetableRequest
+ *
+ * Should be implemented by requests that support retargeting delivery of
+ * data off the main thread.
+ */
+[uuid(27b84c48-5a73-4ba4-a8a4-8b5e649a145e)]
+interface nsIThreadRetargetableRequest : nsISupports
+{
+ /**
+ * Called to retarget delivery of OnDataAvailable to another thread. Should
+ * only be called before AsyncOpen for nsIWebsocketChannels, or during
+ * OnStartRequest for nsIChannels.
+ * Note: For nsIChannels, OnStartRequest and OnStopRequest will still be
+ * delivered on the main thread.
+ *
+ * @param aNewTarget New event target, e.g. thread or threadpool.
+ *
+ * Note: no return value is given. If the retargeting cannot be handled,
+ * normal delivery to the main thread will continue. As such, listeners
+ * should be ready to deal with OnDataAvailable on either the main thread or
+ * the new target thread.
+ */
+ void retargetDeliveryTo(in nsIEventTarget aNewTarget);
+
+ /**
+ * 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 nsIEventTarget deliveryTarget;
+};
diff --git a/netwerk/base/nsIThreadRetargetableStreamListener.idl b/netwerk/base/nsIThreadRetargetableStreamListener.idl
new file mode 100644
index 0000000000..428807a156
--- /dev/null
+++ b/netwerk/base/nsIThreadRetargetableStreamListener.idl
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsIThreadRetargetableStreamListener
+ *
+ * To be used by classes which implement nsIStreamListener and whose
+ * OnDataAvailable callback may be retargeted for delivery off the main thread.
+ */
+[uuid(fb2304b8-f82f-4433-af68-d874a2ebbdc1)]
+interface nsIThreadRetargetableStreamListener : nsISupports
+{
+ /**
+ * Checks this listener and any next listeners it may have to verify that
+ * they can receive OnDataAvailable off the main thread. It is the
+ * responsibility of the implementing class to decide on the criteria to
+ * determine if retargeted delivery of these methods is possible, but it must
+ * check any and all nsIStreamListener objects that might be called in the
+ * listener chain.
+ *
+ * An exception should be thrown if a listener in the chain does not
+ * support retargeted delivery, i.e. if the next listener does not implement
+ * nsIThreadRetargetableStreamListener, or a call to its checkListenerChain()
+ * fails.
+ */
+ void checkListenerChain();
+};
+
diff --git a/netwerk/base/nsIThrottledInputChannel.idl b/netwerk/base/nsIThrottledInputChannel.idl
new file mode 100644
index 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..48efff5355
--- /dev/null
+++ b/netwerk/base/nsITimedChannel.idl
@@ -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/. */
+
+#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;
+
+ // 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;
+
+ // 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..c9aaa94083
--- /dev/null
+++ b/netwerk/base/nsITransport.idl
@@ -0,0 +1,163 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsITransportEventSink;
+interface nsIEventTarget;
+
+/**
+ * nsITransport
+ *
+ * This interface provides a common way of accessing i/o streams connected
+ * to some resource. This interface does not in any way specify the resource.
+ * It provides methods to open blocking or non-blocking, buffered or unbuffered
+ * streams to the resource. The name "transport" is meant to connote the
+ * inherent data transfer implied by this interface (i.e., data is being
+ * transfered in some fashion via the streams exposed by this interface).
+ *
+ * A transport can have an event sink associated with it. The event sink
+ * receives transport-specific events as the transfer is occuring. For a
+ * socket transport, these events can include status about the connection.
+ * See nsISocketTransport for more info about socket transport specifics.
+ */
+[scriptable, uuid(2a8c6334-a5e6-4ec3-9865-1256541446fb)]
+interface nsITransport : nsISupports
+{
+ /**
+ * Open flags.
+ */
+ const unsigned long OPEN_BLOCKING = 1<<0;
+ const unsigned long OPEN_UNBUFFERED = 1<<1;
+
+ /**
+ * Open an input stream on this transport.
+ *
+ * Flags have the following meaning:
+ *
+ * OPEN_BLOCKING
+ * If specified, then the resulting stream will have blocking stream
+ * semantics. This means that if the stream has no data and is not
+ * closed, then reading from it will block the calling thread until
+ * at least one byte is available or until the stream is closed.
+ * If this flag is NOT specified, then the stream has non-blocking
+ * stream semantics. This means that if the stream has no data and is
+ * not closed, then reading from it returns NS_BASE_STREAM_WOULD_BLOCK.
+ * In addition, in non-blocking mode, the stream is guaranteed to
+ * support nsIAsyncInputStream. This interface allows the consumer of
+ * the stream to be notified when the stream can again be read.
+ *
+ * OPEN_UNBUFFERED
+ * If specified, the resulting stream may not support ReadSegments.
+ * ReadSegments is only gauranteed to be implemented when this flag is
+ * NOT specified.
+ *
+ * @param aFlags
+ * optional transport specific flags.
+ * @param aSegmentSize
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * size of each buffer segment (pass 0 to use default value).
+ * @param aSegmentCount
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * maximum number of buffer segments (pass 0 to use default value).
+ */
+ nsIInputStream openInputStream(in unsigned long aFlags,
+ in unsigned long aSegmentSize,
+ in unsigned long aSegmentCount);
+
+ /**
+ * Open an output stream on this transport.
+ *
+ * Flags have the following meaning:
+ *
+ * OPEN_BLOCKING
+ * If specified, then the resulting stream will have blocking stream
+ * semantics. This means that if the stream is full and is not closed,
+ * then writing to it will block the calling thread until ALL of the
+ * data can be written or until the stream is closed. If this flag is
+ * NOT specified, then the stream has non-blocking stream semantics.
+ * This means that if the stream is full and is not closed, then writing
+ * to it returns NS_BASE_STREAM_WOULD_BLOCK. In addition, in non-
+ * blocking mode, the stream is guaranteed to support
+ * nsIAsyncOutputStream. This interface allows the consumer of the
+ * stream to be notified when the stream can again accept more data.
+ *
+ * OPEN_UNBUFFERED
+ * If specified, the resulting stream may not support WriteSegments and
+ * WriteFrom. WriteSegments and WriteFrom are only guaranteed to be
+ * implemented when this flag is NOT specified.
+ *
+ * @param aFlags
+ * optional transport specific flags.
+ * @param aSegmentSize
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * size of each buffer segment (pass 0 to use default value).
+ * @param aSegmentCount
+ * if OPEN_UNBUFFERED is not set, then this parameter specifies the
+ * maximum number of buffer segments (pass 0 to use default value).
+ */
+ nsIOutputStream openOutputStream(in unsigned long aFlags,
+ in unsigned long aSegmentSize,
+ in unsigned long aSegmentCount);
+
+ /**
+ * Close the transport and any open streams.
+ *
+ * @param aReason
+ * the reason for closing the stream.
+ */
+ void close(in nsresult aReason);
+
+ /**
+ * Set the transport event sink.
+ *
+ * @param aSink
+ * receives transport layer notifications
+ * @param aEventTarget
+ * indicates the event target to which the notifications should
+ * be delivered. if NULL, then the notifications may occur on
+ * any thread.
+ */
+ void setEventSink(in nsITransportEventSink aSink,
+ in nsIEventTarget aEventTarget);
+
+ /**
+ * Generic nsITransportEventSink status codes. nsITransport
+ * implementations may override these status codes with their own more
+ * specific status codes (e.g., see nsISocketTransport).
+ *
+ * In C++, these constants have a type of uint32_t, so C++ callers must use
+ * the NS_NET_STATUS_* constants defined below, which have a type of
+ * nsresult.
+ */
+ const unsigned long STATUS_READING = 0x804b0008;
+ const unsigned long STATUS_WRITING = 0x804b0009;
+};
+
+[scriptable, uuid(EDA4F520-67F7-484b-A691-8C3226A5B0A6)]
+interface nsITransportEventSink : nsISupports
+{
+ /**
+ * Transport status notification.
+ *
+ * @param aTransport
+ * the transport sending this status notification.
+ * @param aStatus
+ * the transport status (resolvable to a string using
+ * nsIErrorService). See nsISocketTransport for socket specific
+ * status codes and more comments.
+ * @param aProgress
+ * the amount of data either read or written depending on the value
+ * of the status code. this value is relative to aProgressMax.
+ * @param aProgressMax
+ * the maximum amount of data that will be read or written. if
+ * unknown, -1 will be passed.
+ */
+ void onTransportStatus(in nsITransport aTransport,
+ in nsresult aStatus,
+ in long long aProgress,
+ in long long aProgressMax);
+};
diff --git a/netwerk/base/nsIUDPSocket.idl b/netwerk/base/nsIUDPSocket.idl
new file mode 100644
index 0000000000..c8217dc5d3
--- /dev/null
+++ b/netwerk/base/nsIUDPSocket.idl
@@ -0,0 +1,347 @@
+/* vim:set ts=4 sw=4 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsINetAddr;
+interface nsIUDPSocketListener;
+interface nsIUDPMessage;
+interface nsISocketTransport;
+interface nsIOutputStream;
+interface nsIInputStream;
+interface nsIPrincipal;
+
+%{ C++
+#include "nsTArrayForwardDeclare.h"
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+[ref] native Uint8TArrayRef(FallibleTArray<uint8_t>);
+
+/**
+ * nsIUDPSocket
+ *
+ * An interface to a UDP socket that can accept incoming connections.
+ */
+[scriptable, uuid(d423bf4e-4499-40cf-bc03-153e2bf206d1)]
+interface nsIUDPSocket : nsISupports
+{
+ /**
+ * init
+ *
+ * This method initializes a UDP socket.
+ *
+ * @param aPort
+ * The port of the UDP socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param aLoopbackOnly
+ * If true, the UDP socket will only respond to connections on the
+ * local loopback interface. Otherwise, it will accept connections
+ * from any interface. To specify a particular network interface,
+ * use initWithAddress.
+ * @param aPrincipal
+ * The principal connected to this socket.
+ * @param aAddressReuse
+ * If true, the socket is allowed to be bound to an address that is
+ * already in use. Default is true.
+ */
+ [optional_argc] void init(in long aPort,
+ in boolean aLoopbackOnly,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ [optional_argc] void init2(in AUTF8String aAddr,
+ in long aPort,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ /**
+ * initWithAddress
+ *
+ * This method initializes a UDP socket, and binds it to a particular
+ * local address (and hence a particular local network interface).
+ *
+ * @param aAddr
+ * The address to which this UDP socket should be bound.
+ * @param aPrincipal
+ * The principal connected to this socket.
+ * @param aAddressReuse
+ * If true, the socket is allowed to be bound to an address that is
+ * already in use. Default is true.
+ */
+ [noscript, optional_argc] void initWithAddress([const] in NetAddrPtr aAddr,
+ in nsIPrincipal aPrincipal,
+ [optional] in boolean aAddressReuse);
+
+ /**
+ * close
+ *
+ * This method closes a UDP socket. This does not affect already
+ * connected client sockets (i.e., the nsISocketTransport instances
+ * created from this UDP socket). This will cause the onStopListening
+ * event to asynchronously fire with a status of NS_BINDING_ABORTED.
+ */
+ void close();
+
+ /**
+ * asyncListen
+ *
+ * This method puts the UDP socket in the listening state. It will
+ * asynchronously listen for and accept client connections. The listener
+ * will be notified once for each client connection that is accepted. The
+ * listener's onSocketAccepted method will be called on the same thread
+ * that called asyncListen (the calling thread must have a nsIEventTarget).
+ *
+ * The listener will be passed a reference to an already connected socket
+ * transport (nsISocketTransport). See below for more details.
+ *
+ * @param aListener
+ * The listener to be notified when client connections are accepted.
+ */
+ void asyncListen(in nsIUDPSocketListener aListener);
+
+ /**
+ * connect
+ *
+ * This method connects the UDP socket to a remote UDP address.
+ *
+ * @param aRemoteAddr
+ * The remote address to connect to
+ */
+ void connect([const] in NetAddrPtr aAddr);
+
+ /**
+ * Returns the local address of this UDP socket
+ */
+ readonly attribute nsINetAddr localAddr;
+
+ /**
+ * Returns the port of this UDP socket.
+ */
+ readonly attribute long port;
+
+ /**
+ * Returns the address to which this UDP socket is bound. Since a
+ * UDP socket may be bound to multiple network devices, this address
+ * may not necessarily be specific to a single network device. In the
+ * case of an IP socket, the IP address field would be zerod out to
+ * indicate a UDP socket bound to all network devices. Therefore,
+ * this method cannot be used to determine the IP address of the local
+ * system. See nsIDNSService::myHostName if this is what you need.
+ */
+ [noscript] NetAddr getAddress();
+
+ /**
+ * send
+ *
+ * Send out the datagram to specified remote host and port.
+ * DNS lookup will be triggered.
+ *
+ * @param host The remote host name.
+ * @param port The remote port.
+ * @param data The buffer containing the data to be written.
+ * @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);
+
+ /**
+ * 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,
+ in Array<uint8_t> data);
+
+ /**
+ * sendBinaryStream
+ *
+ * Send out the datagram to specified remote address and port.
+ *
+ * @param host The remote host name.
+ * @param port The remote port.
+ * @param stream The input stream to be sent. This must be a buffered stream implementation.
+ */
+ void sendBinaryStream(in AUTF8String host, in unsigned short port,
+ in nsIInputStream stream);
+
+ /**
+ * sendBinaryStreamWithAddress
+ *
+ * Send out the datagram to specified remote address and port.
+ *
+ * @param addr The remote host address.
+ * @param stream The input stream to be sent. This must be a buffered stream implementation.
+ */
+ void sendBinaryStreamWithAddress([const] in NetAddrPtr addr,
+ in nsIInputStream stream);
+
+ /**
+ * joinMulticast
+ *
+ * Join the multicast group specified by |addr|. You are then able to
+ * receive future datagrams addressed to the group.
+ *
+ * @param addr
+ * The multicast group address.
+ * @param iface
+ * The local address of the interface on which to join the group. If
+ * this is not specified, the OS may join the group on all interfaces
+ * or only the primary interface.
+ */
+ void joinMulticast(in AUTF8String addr, [optional] in AUTF8String iface);
+ [noscript] void joinMulticastAddr([const] in NetAddr addr,
+ [const, optional] in NetAddrPtr iface);
+
+ /**
+ * leaveMulticast
+ *
+ * Leave the multicast group specified by |addr|. You will no longer
+ * receive future datagrams addressed to the group.
+ *
+ * @param addr
+ * The multicast group address.
+ * @param iface
+ * The local address of the interface on which to leave the group.
+ * If this is not specified, the OS may leave the group on all
+ * interfaces or only the primary interface.
+ */
+ void leaveMulticast(in AUTF8String addr, [optional] in AUTF8String iface);
+ [noscript] void leaveMulticastAddr([const] in NetAddr addr,
+ [const, optional] in NetAddrPtr iface);
+
+ /**
+ * multicastLoopback
+ *
+ * Whether multicast datagrams sent via this socket should be looped back to
+ * this host (assuming this host has joined the relevant group). Defaults
+ * to true.
+ * Note: This is currently write-only.
+ */
+ attribute boolean multicastLoopback;
+
+ /**
+ * multicastInterface
+ *
+ * The interface that should be used for sending future multicast datagrams.
+ * Note: This is currently write-only.
+ */
+ attribute AUTF8String multicastInterface;
+
+ /**
+ * multicastInterfaceAddr
+ *
+ * The interface that should be used for sending future multicast datagrams.
+ * Note: This is currently write-only.
+ */
+ [noscript] attribute NetAddr multicastInterfaceAddr;
+
+ /**
+ * recvBufferSize
+ *
+ * The size of the receive buffer. Default depends on the OS.
+ */
+ [noscript] attribute long recvBufferSize;
+
+ /**
+ * sendBufferSize
+ *
+ * The size of the send buffer. Default depends on the OS.
+ */
+ [noscript] attribute long sendBufferSize;
+};
+
+/**
+ * nsIUDPSocketListener
+ *
+ * This interface is notified whenever a UDP socket accepts a new connection.
+ * The transport is in the connected state, and read/write streams can be opened
+ * using the normal nsITransport API. The address of the client can be found by
+ * calling the nsISocketTransport::GetAddress method or by inspecting
+ * nsISocketTransport::GetHost, which returns a string representation of the
+ * client's IP address (NOTE: this may be an IPv4 or IPv6 string literal).
+ */
+[scriptable, uuid(2E4B5DD3-7358-4281-B81F-10C62EF39CB5)]
+interface nsIUDPSocketListener : nsISupports
+{
+ /**
+ * onPacketReceived
+ *
+ * This method is called when a client sends an UDP packet.
+ *
+ * @param aSocket
+ * The UDP socket.
+ * @param aMessage
+ * The message.
+ */
+ void onPacketReceived(in nsIUDPSocket aSocket,
+ in nsIUDPMessage aMessage);
+
+ /**
+ * onStopListening
+ *
+ * This method is called when the listening socket stops for some reason.
+ * The UDP socket is effectively dead after this notification.
+ *
+ * @param aSocket
+ * The UDP socket.
+ * @param aStatus
+ * The reason why the UDP socket stopped listening. If the
+ * UDP socket was manually closed, then this value will be
+ * NS_BINDING_ABORTED.
+ */
+ void onStopListening(in nsIUDPSocket aSocket, in nsresult aStatus);
+};
+
+/**
+ * nsIUDPMessage
+ *
+ * This interface is used to encapsulate an incomming UDP message
+ */
+[scriptable, 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();
+};
diff --git a/netwerk/base/nsIURI.idl b/netwerk/base/nsIURI.idl
new file mode 100644
index 0000000000..df1a98e873
--- /dev/null
+++ b/netwerk/base/nsIURI.idl
@@ -0,0 +1,322 @@
+/* -*- 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;
+
+ /************************************************************************
+ * 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;
+
+ /**
+ * 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..c3afa21075
--- /dev/null
+++ b/netwerk/base/nsIURIMutator.idl
@@ -0,0 +1,557 @@
+/* -*- 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 <functional>
+
+#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
+// passed to NS_MutatorMethod.
+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 helper returns a std::function that will be applied on the
+// nsIURIMutator. The type of `Interface` will be deduced from the method type.
+// aMethod will be called on the target object if it successfully QIs to
+// `Interface`, and the arguments will be passed to the call.
+template <typename Method, typename... Args>
+const std::function<nsresult(nsIURIMutator*)>
+NS_MutatorMethod(Method aMethod, Args ...aArgs)
+{
+ // Capture arguments by value, otherwise we crash.
+ return [=](nsIURIMutator* aMutator) {
+ typedef typename nsMethodTypeTraits<Method>::class_type Interface;
+ nsresult rv;
+ nsCOMPtr<Interface> target = do_QueryInterface(aMutator, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = (target->*aMethod)(aArgs...);
+ if (NS_FAILED(rv)) return rv;
+ return NS_OK;
+ };
+}
+
+// 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(NS_MutatorMethod(&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.
+ */
+ NS_MutateURI& Apply(const std::function<nsresult(nsIURIMutator*)>& aFunction)
+ {
+ if (NS_FAILED(mStatus)) {
+ return *this;
+ }
+ mStatus = aFunction(mMutator);
+ 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..abbfb1b781
--- /dev/null
+++ b/netwerk/base/nsIUploadChannel2.idl
@@ -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 "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;
+
+ /**
+ * Ensure the upload stream, if any, is cloneable. This may involve
+ * async copying, so a callback runnable must be provided. It will
+ * invoked on the current thread when the upload stream is ready
+ * for cloning. If the stream is already cloneable, then the callback
+ * will be invoked synchronously.
+ */
+ [noscript]
+ void ensureUploadStreamIsCloneable(in nsIRunnable aCallback);
+
+ /**
+ * Clones the upload stream. May return failure if the upload stream
+ * is not cloneable. If this is not acceptable, use the
+ * ensureUploadStreamIsCloneable() method first.
+ * 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..c9fb767f94
--- /dev/null
+++ b/netwerk/base/nsIncrementalDownload.cpp
@@ -0,0 +1,832 @@
+/* -*- 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/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 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();
+
+ 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;
+ int32_t mChunkSize;
+ int32_t mInterval;
+ int64_t mTotalSize;
+ int64_t mCurrentSize;
+ uint32_t mLoadFlags;
+ int32_t mNonPartialCount;
+ nsresult mStatus;
+ bool mIsPending;
+ bool mDidOnStartRequest;
+ PRTime mLastProgressUpdate;
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+ nsCOMPtr<nsIChannel> mNewRedirectChannel;
+ nsCString mPartialValidator;
+ bool mCacheBust;
+};
+
+nsIncrementalDownload::nsIncrementalDownload()
+ : mChunkLen(0),
+ mChunkSize(DEFAULT_CHUNK_SIZE),
+ mInterval(DEFAULT_INTERVAL),
+ mTotalSize(-1),
+ mCurrentSize(-1),
+ mLoadFlags(LOAD_NORMAL),
+ mNonPartialCount(0),
+ mStatus(NS_OK),
+ mIsPending(false),
+ mDidOnStartRequest(false),
+ mLastProgressUpdate(0),
+ mRedirectCallback(nullptr),
+ mNewRedirectChannel(nullptr),
+ mCacheBust(false) {}
+
+nsresult nsIncrementalDownload::FlushChunk() {
+ NS_ASSERTION(mTotalSize != int64_t(-1), "total size should be known");
+
+ if (mChunkLen == 0) return NS_OK;
+
+ nsresult rv = AppendToFile(mDest, mChunk.get(), mChunkLen);
+ if (NS_FAILED(rv)) return rv;
+
+ mCurrentSize += int64_t(mChunkLen);
+ mChunkLen = 0;
+
+ return NS_OK;
+}
+
+void nsIncrementalDownload::UpdateProgress() {
+ mLastProgressUpdate = PR_Now();
+
+ if (mProgressSink)
+ mProgressSink->OnProgress(this, 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) {
+ return NS_NewTimerWithObserver(getter_AddRefs(mTimer), this, interval * 1000,
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+nsresult nsIncrementalDownload::ProcessTimeout() {
+ NS_ASSERTION(!mChannel, "how can we have a channel?");
+
+ // Handle existing error conditions
+ if (NS_FAILED(mStatus)) {
+ CallOnStopRequest();
+ return NS_OK;
+ }
+
+ // Fetch next chunk
+
+ nsCOMPtr<nsIChannel> channel;
+ nsresult rv = NS_NewChannel(
+ getter_AddRefs(channel), mFinalURI, nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_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 ||
+ rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+ mCurrentSize = 0;
+ return NS_OK;
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ mCurrentSize = size;
+ return NS_OK;
+}
+
+// nsISupports
+
+NS_IMPL_ISUPPORTS(nsIncrementalDownload, nsIIncrementalDownload, nsIRequest,
+ nsIStreamListener, nsIRequestObserver, nsIObserver,
+ nsIInterfaceRequestor, nsIChannelEventSink,
+ nsISupportsWeakReference, nsIAsyncVerifyRedirectCallback)
+
+// nsIRequest
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetName(nsACString& name) {
+ NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
+
+ return mURI->GetSpec(name);
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::IsPending(bool* isPending) {
+ *isPending = mIsPending;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetStatus(nsresult* status) {
+ *status = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Cancel(nsresult status) {
+ NS_ENSURE_ARG(NS_FAILED(status));
+
+ // Ignore this cancelation if we're already canceled.
+ if (NS_FAILED(mStatus)) return NS_OK;
+
+ mStatus = status;
+
+ // Nothing more to do if callbacks aren't pending.
+ if (!mIsPending) return NS_OK;
+
+ if (mChannel) {
+ mChannel->Cancel(mStatus);
+ NS_ASSERTION(!mTimer, "what is this timer object doing here?");
+ } else {
+ // dispatch a timer callback event to drive invoking our listener's
+ // OnStopRequest.
+ if (mTimer) mTimer->Cancel();
+ StartTimer(0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsIncrementalDownload::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetLoadFlags(nsLoadFlags* loadFlags) {
+ *loadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::SetLoadFlags(nsLoadFlags loadFlags) {
+ mLoadFlags = loadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIncrementalDownload::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();
+ } else if (strcmp(topic, NS_TIMER_CALLBACK_TOPIC) == 0) {
+ mTimer = nullptr;
+ nsresult rv = ProcessTimeout();
+ if (NS_FAILED(rv)) Cancel(rv);
+ }
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+
+NS_IMETHODIMP
+nsIncrementalDownload::GetInterface(const nsIID& iid, void** result) {
+ if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
+ NS_ADDREF_THIS();
+ *result = static_cast<nsIChannelEventSink*>(this);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(mObserver);
+ if (ir) return ir->GetInterface(iid, result);
+
+ return NS_ERROR_NO_INTERFACE;
+}
+
+nsresult nsIncrementalDownload::ClearRequestHeader(nsIHttpChannel* channel) {
+ NS_ENSURE_ARG(channel);
+
+ // We don't support encodings -- they make the Content-Length not equal
+ // to the actual size of the data.
+ return channel->SetRequestHeader("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(nsISupports* outer, const nsIID& iid,
+ void** result) {
+ if (outer) return NS_ERROR_NO_AGGREGATION;
+
+ 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..00fb7aeb03
--- /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 "GeckoProfiler.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(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ if (aOuter) return NS_ERROR_NO_AGGREGATION;
+
+ 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) {
+ NS_IF_ADDREF(*aRequest = mRequest);
+ 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; }
diff --git a/netwerk/base/nsIncrementalStreamLoader.h b/netwerk/base/nsIncrementalStreamLoader.h
new file mode 100644
index 0000000000..3b19765e53
--- /dev/null
+++ b/netwerk/base/nsIncrementalStreamLoader.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIncrementalStreamLoader_h__
+#define nsIncrementalStreamLoader_h__
+
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIIncrementalStreamLoader.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Vector.h"
+
+class nsIRequest;
+
+class nsIncrementalStreamLoader final
+ : public nsIIncrementalStreamLoader,
+ public nsIThreadRetargetableStreamListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINCREMENTALSTREAMLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsIncrementalStreamLoader();
+
+ static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+ protected:
+ ~nsIncrementalStreamLoader() = 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..53e003948b
--- /dev/null
+++ b/netwerk/base/nsInputStreamChannel.cpp
@@ -0,0 +1,102 @@
+/* -*- 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) {
+ NS_IF_ADDREF(*stream = mContentStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetContentStream(nsIInputStream* stream) {
+ NS_ENSURE_TRUE(!mContentStream, NS_ERROR_ALREADY_INITIALIZED);
+ mContentStream = stream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetSrcdocData(nsAString& aSrcdocData) {
+ aSrcdocData = mSrcdocData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetSrcdocData(const nsAString& aSrcdocData) {
+ mSrcdocData = aSrcdocData;
+ mIsSrcdocChannel = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetIsSrcdocChannel(bool* aIsSrcdocChannel) {
+ *aIsSrcdocChannel = mIsSrcdocChannel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::GetBaseURI(nsIURI** aBaseURI) {
+ *aBaseURI = mBaseURI;
+ NS_IF_ADDREF(*aBaseURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamChannel::SetBaseURI(nsIURI* aBaseURI) {
+ mBaseURI = aBaseURI;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsInputStreamChannel.h b/netwerk/base/nsInputStreamChannel.h
new file mode 100644
index 0000000000..f00df9536f
--- /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() : mIsSrcdocChannel(false) {}
+
+ 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;
+};
+
+} // 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..80b5daa1ba
--- /dev/null
+++ b/netwerk/base/nsInputStreamPump.cpp
@@ -0,0 +1,739 @@
+/* -*- 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/SlicedInputStream.h"
+#include "GeckoProfiler.h"
+#include "nsIStreamListener.h"
+#include "nsILoadGroup.h"
+#include "nsNetCID.h"
+#include "nsStreamUtils.h"
+#include <algorithm>
+
+static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
+
+//
+// MOZ_LOG=nsStreamPump:5
+//
+static mozilla::LazyLogModule gStreamPumpLog("nsStreamPump");
+#undef LOG
+#define LOG(args) MOZ_LOG(gStreamPumpLog, mozilla::LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump methods
+//-----------------------------------------------------------------------------
+
+nsInputStreamPump::nsInputStreamPump()
+ : mState(STATE_IDLE),
+ mStreamOffset(0),
+ mStreamLength(0),
+ mSegSize(0),
+ mSegCount(0),
+ mStatus(NS_OK),
+ mSuspendCount(0),
+ mLoadFlags(LOAD_NORMAL),
+ mIsPending(false),
+ mProcessingCallbacks(false),
+ mWaitingForInputStreamReady(false),
+ mCloseWhenDone(false),
+ mRetargeting(false),
+ mAsyncStreamIsBuffered(false),
+ mOffMainThread(!NS_IsMainThread()),
+ mMutex("nsInputStreamPump") {}
+
+nsresult nsInputStreamPump::Create(nsInputStreamPump** result,
+ nsIInputStream* stream, uint32_t segsize,
+ uint32_t segcount, bool closeWhenDone,
+ nsIEventTarget* 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, 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<nsIEventTarget> mainThread =
+ mLabeledMainThreadTarget ? mLabeledMainThreadTarget
+ : do_AddRef(GetMainThreadEventTarget());
+ 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 wil be on mTargetThread.
+ mRetargeting = false;
+ mWaitingForInputStreamReady = true;
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsInputStreamPump::nsISupports
+//-----------------------------------------------------------------------------
+
+// although this class can only be accessed from one thread at a time, we do
+// allow its ownership to move from thread to thread, assuming the consumer
+// understands the limitations of this.
+NS_IMPL_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);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::GetStatus(nsresult* status) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ *status = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::Cancel(nsresult status) {
+#if DEBUG
+ if (mOffMainThread) {
+ MOZ_ASSERT_IF(mTargetThread, mTargetThread->IsOnCurrentThread());
+ } else {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+#endif
+
+ RecursiveMutexAutoLock lock(mMutex);
+
+ 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) {
+ mAsyncStream->CloseWithStatus(status);
+ if (mSuspendCount == 0) EnsureWaiting();
+ // Otherwise, EnsureWaiting will be called by Resume().
+ // Note that while suspended, OnInputStreamReady will
+ // not do anything, and also note that calling asyncWait
+ // on a closed stream works and will dispatch an event immediately.
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamPump::Suspend() {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ LOG(("nsInputStreamPump::Suspend [this=%p]\n", this));
+ NS_ENSURE_TRUE(mState != STATE_IDLE, 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, 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_IDLE, 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);
+
+ NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
+ 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,
+ nsIEventTarget* mainThreadTarget) {
+ 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);
+
+ 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 = GetCurrentEventTarget();
+ }
+ 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) {
+ 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;
+ }
+
+ {
+ // 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 = mListener->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));
+
+ {
+ // 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 = mListener->OnDataAvailable(this, mAsyncStream, mStreamOffset,
+ odaAvail);
+ }
+
+ // 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.
+ nsresult rv = mLabeledMainThreadTarget->Dispatch(
+ NewRunnableMethod("nsInputStreamPump::CallOnStateStop", this,
+ &nsInputStreamPump::CallOnStateStop));
+ NS_ENSURE_SUCCESS(rv, STATE_IDLE);
+ return STATE_IDLE;
+ }
+
+ 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_IDLE;
+ }
+
+ if (NS_FAILED(mStatus))
+ mAsyncStream->CloseWithStatus(mStatus);
+ else if (mCloseWhenDone)
+ mAsyncStream->Close();
+
+ mAsyncStream = nullptr;
+ mTargetThread = nullptr;
+ mIsPending = false;
+ {
+ // 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);
+ mListener->OnStopRequest(this, mStatus);
+ }
+ mListener = nullptr;
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ return STATE_IDLE;
+}
+
+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(nsIEventTarget* 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(nsIEventTarget** aNewTarget) {
+ RecursiveMutexAutoLock lock(mMutex);
+
+ nsCOMPtr<nsIEventTarget> 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..a9f8525088
--- /dev/null
+++ b/netwerk/base/nsInputStreamPump.h
@@ -0,0 +1,110 @@
+/* -*- 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"
+
+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:
+ typedef mozilla::RecursiveMutexAutoLock RecursiveMutexAutoLock;
+ typedef mozilla::RecursiveMutexAutoUnlock 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,
+ nsIEventTarget* mainThreadTarget = nullptr);
+
+ typedef void (*PeekSegmentFun)(void* closure, const uint8_t* buf,
+ uint32_t bufLen);
+ /**
+ * Peek into the first chunk of data that's in the stream. Note that this
+ * method will not call the callback when there is no data in the stream.
+ * The callback will be called at most once.
+ *
+ * The data from the stream will not be consumed, i.e. the pump's listener
+ * can still read all the data.
+ *
+ * Do not call before asyncRead. Do not call after onStopRequest.
+ */
+ nsresult PeekStream(PeekSegmentFun callback, void* closure);
+
+ /**
+ * Dispatched (to the main thread) by OnStateStop if it's called off main
+ * thread. Updates mState based on return value of OnStateStop.
+ */
+ nsresult CallOnStateStop();
+
+ protected:
+ enum { STATE_IDLE, STATE_START, STATE_TRANSFER, STATE_STOP };
+
+ nsresult EnsureWaiting();
+ uint32_t OnStateStart();
+ uint32_t OnStateTransfer();
+ uint32_t OnStateStop();
+ nsresult CreateBufferedStreamIfNeeded();
+
+ uint32_t mState;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+ nsCOMPtr<nsIEventTarget> mLabeledMainThreadTarget;
+ nsCOMPtr<nsIInputStream> mStream;
+ nsCOMPtr<nsIAsyncInputStream> mAsyncStream;
+ uint64_t mStreamOffset;
+ uint64_t mStreamLength;
+ uint32_t mSegSize;
+ uint32_t mSegCount;
+ nsresult mStatus;
+ uint32_t mSuspendCount;
+ uint32_t mLoadFlags;
+ bool mIsPending;
+ // True while in OnInputStreamReady, calling OnStateStart, OnStateTransfer
+ // and OnStateStop. Used to prevent calls to AsyncWait during callbacks.
+ bool mProcessingCallbacks;
+ // True if waiting on the "input stream ready" callback.
+ bool mWaitingForInputStreamReady;
+ bool mCloseWhenDone;
+ bool mRetargeting;
+ bool mAsyncStreamIsBuffered;
+ // Indicate whether nsInputStreamPump is used completely off main thread.
+ // If true, OnStateStop() is executed off main thread.
+ bool mOffMainThread;
+ // Protects state/member var accesses across multiple threads.
+ mozilla::RecursiveMutex mMutex;
+};
+
+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..ea8acb73cb
--- /dev/null
+++ b/netwerk/base/nsLoadGroup.cpp
@@ -0,0 +1,1079 @@
+/* -*- 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/StoragePrincipalHelper.h"
+#include "mozilla/Unused.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()
+ : mForegroundCount(0),
+ mLoadFlags(LOAD_NORMAL),
+ mDefaultLoadFlags(0),
+ mPriority(PRIORITY_NORMAL),
+ mRequests(&sRequestHashOps, sizeof(RequestMapEntry)),
+ mStatus(NS_OK),
+ mIsCanceling(false),
+ mDefaultLoadIsTimed(false),
+ mBrowsingContextDiscarded(false),
+ mExternalRequestContext(false),
+ mTimedRequests(0),
+ mCachedRequests(0) {
+ LOG(("LOADGROUP [%p]: Created.\n", this));
+}
+
+nsLoadGroup::~nsLoadGroup() {
+ DebugOnly<nsresult> rv = Cancel(NS_BINDING_ABORTED);
+ 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) ? true : false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::GetStatus(nsresult* status) {
+ if (NS_SUCCEEDED(mStatus) && mDefaultLoadRequest)
+ return mDefaultLoadRequest->GetStatus(status);
+
+ *status = mStatus;
+ return NS_OK;
+}
+
+static bool AppendRequestsToArray(PLDHashTable* aTable,
+ nsTArray<nsIRequest*>* aArray) {
+ for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
+ auto e = static_cast<RequestMapEntry*>(iter.Get());
+ nsIRequest* request = e->mKey;
+ NS_ASSERTION(request, "What? Null key in PLDHashTable entry?");
+
+ // 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::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->Cancel(status);
+
+ // 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);
+
+ if (!(flags & nsIRequest::LOAD_BACKGROUND)) {
+ // Update the count of foreground URIs..
+ mForegroundCount += 1;
+
+ //
+ // Fire the OnStartRequest notification out to the observer...
+ //
+ // If the notification fails then DO NOT add the request to
+ // the load group.
+ //
+ nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
+ if (observer) {
+ LOG(
+ ("LOADGROUP [%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;
+
+ mForegroundCount -= 1;
+ }
+ }
+
+ // Ensure that we're part of our loadgroup while pending
+ if (mForegroundCount == 1 && mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLoadGroup::RemoveRequest(nsIRequest* request, nsISupports* ctxt,
+ nsresult aStatus) {
+ // 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;
+
+ if (!(flags & nsIRequest::LOAD_BACKGROUND)) {
+ NS_ASSERTION(mForegroundCount > 0, "ForegroundCount messed up");
+ mForegroundCount -= 1;
+
+ // Fire the OnStopRequest out to the observer...
+ nsCOMPtr<nsIRequestObserver> observer = do_QueryReferent(mObserver);
+ if (observer) {
+ LOG(
+ ("LOADGROUP [%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 (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) {
+ mObserver = do_GetWeakReference(aObserver);
+ return NS_OK;
+}
+
+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); \
+ }
+
+ 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);
+ }
+ }
+ }
+
+#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..c419b742c6
--- /dev/null
+++ b/netwerk/base/nsLoadGroup.h
@@ -0,0 +1,107 @@
+/* -*- 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);
+
+ 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;
+ uint32_t mLoadFlags;
+ uint32_t mDefaultLoadFlags;
+ int32_t mPriority;
+
+ 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;
+ bool mIsCanceling;
+ bool mDefaultLoadIsTimed;
+ bool mBrowsingContextDiscarded;
+ bool mExternalRequestContext;
+
+ /* Telemetry */
+ mozilla::TimeStamp mDefaultRequestCreationTime;
+ uint32_t mTimedRequests;
+ uint32_t mCachedRequests;
+};
+
+} // 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..13b6ad0ac1
--- /dev/null
+++ b/netwerk/base/nsMIMEInputStream.cpp
@@ -0,0 +1,536 @@
+/* -*- 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();
+
+ 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:
+ void InitStreams();
+
+ template <typename M>
+ void SerializeInternal(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors,
+ bool aDelayedStart, uint32_t aMaxSize,
+ uint32_t* aSizeUsed, M* aManager);
+
+ struct MOZ_STACK_CLASS ReadSegmentsState {
+ nsCOMPtr<nsIInputStream> mThisStream;
+ nsWriteSegmentFun mWriter;
+ void* mClosure;
+ };
+ static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment, uint32_t aToOffset,
+ uint32_t aCount, uint32_t* aWriteCount);
+
+ bool IsSeekableInputStream() const;
+ bool IsAsyncInputStream() const;
+ bool IsInputStreamLength() const;
+ bool IsAsyncInputStreamLength() const;
+ bool IsCloneableInputStream() const;
+
+ nsTArray<HeaderEntry> mHeaders;
+
+ nsCOMPtr<nsIInputStream> mStream;
+ bool mStartedReading;
+
+ mozilla::Mutex mMutex;
+
+ // This is protected by mutex.
+ nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback;
+
+ // 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)
+
+nsMIMEInputStream::nsMIMEInputStream()
+ : mStartedReading(false), mMutex("nsMIMEInputStream::mMutex") {}
+
+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);
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aStream);
+ if (!seekable) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mStream = aStream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::GetData(nsIInputStream** aStream) {
+ NS_ENSURE_ARG_POINTER(aStream);
+ *aStream = mStream;
+ NS_IF_ADDREF(*aStream);
+ return NS_OK;
+}
+
+// set up the internal streams
+void nsMIMEInputStream::InitStreams() {
+ NS_ASSERTION(!mStartedReading,
+ "Don't call initStreams twice without rewinding");
+
+ mStartedReading = true;
+}
+
+#define INITSTREAMS \
+ if (!mStartedReading) { \
+ NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED); \
+ InitStreams(); \
+ }
+
+// 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::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;
+ {
+ MutexAutoLock lock(mMutex);
+ if (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;
+
+ {
+ 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(nsISupports* outer, REFNSIID iid,
+ void** result) {
+ *result = nullptr;
+
+ if (outer) return NS_ERROR_NO_AGGREGATION;
+
+ RefPtr<nsMIMEInputStream> inst = new nsMIMEInputStream();
+ if (!inst) return NS_ERROR_OUT_OF_MEMORY;
+
+ return inst->QueryInterface(iid, result);
+}
+
+void nsMIMEInputStream::Serialize(
+ InputStreamParams& aParams, FileDescriptorArray& aFileDescriptors,
+ bool aDelayedStart, uint32_t aMaxSize, uint32_t* aSizeUsed,
+ mozilla::ipc::ParentToChildStreamActorManager* aManager) {
+ SerializeInternal(aParams, aFileDescriptors, aDelayedStart, aMaxSize,
+ aSizeUsed, aManager);
+}
+
+void nsMIMEInputStream::Serialize(
+ InputStreamParams& aParams, FileDescriptorArray& aFileDescriptors,
+ bool aDelayedStart, uint32_t aMaxSize, uint32_t* aSizeUsed,
+ mozilla::ipc::ChildToParentStreamActorManager* aManager) {
+ SerializeInternal(aParams, aFileDescriptors, aDelayedStart, aMaxSize,
+ aSizeUsed, aManager);
+}
+
+template <typename M>
+void nsMIMEInputStream::SerializeInternal(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors,
+ bool aDelayedStart, uint32_t aMaxSize,
+ uint32_t* aSizeUsed, M* aManager) {
+ 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,
+ aFileDescriptors, aDelayedStart,
+ aMaxSize, aSizeUsed, aManager);
+ } 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,
+ aDelayedStart, aManager);
+ }
+
+ NS_ASSERTION(wrappedParams.type() != InputStreamParams::T__None,
+ "Wrapped stream failed to serialize!");
+
+ params.optionalStream().emplace(wrappedParams);
+ aParams = params;
+}
+
+bool nsMIMEInputStream::Deserialize(
+ const InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors) {
+ if (aParams.type() != InputStreamParams::TMIMEInputStreamParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const MIMEInputStreamParams& params = aParams.get_MIMEInputStreamParams();
+ const Maybe<InputStreamParams>& wrappedParams = params.optionalStream();
+
+ mHeaders = params.headers().Clone();
+ mStartedReading = params.startedReading();
+
+ if (wrappedParams.isSome()) {
+ nsCOMPtr<nsIInputStream> stream;
+ stream = InputStreamHelper::DeserializeInputStream(wrappedParams.ref(),
+ aFileDescriptors);
+ if (!stream) {
+ NS_WARNING("Failed to deserialize wrapped stream!");
+ return false;
+ }
+
+ mStream = stream;
+ }
+
+ 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;
+ {
+ MutexAutoLock lock(mMutex);
+ mAsyncInputStreamLengthCallback = aCallback;
+ }
+
+ return stream->AsyncLengthWait(callback, aEventTarget);
+}
+
+NS_IMETHODIMP
+nsMIMEInputStream::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);
+}
+
+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 =
+ 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..fde9995304
--- /dev/null
+++ b/netwerk/base/nsMIMEInputStream.h
@@ -0,0 +1,25 @@
+/* -*- 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_
+
+#define NS_MIMEINPUTSTREAM_CONTRACTID "@mozilla.org/network/mime-input-stream;1"
+#define NS_MIMEINPUTSTREAM_CID \
+ { /* 58a1c31c-1dd2-11b2-a3f6-d36949d48268 */ \
+ 0x58a1c31c, 0x1dd2, 0x11b2, { \
+ 0xa3, 0xf6, 0xd3, 0x69, 0x49, 0xd4, 0x82, 0x68 \
+ } \
+ }
+
+extern nsresult nsMIMEInputStreamConstructor(nsISupports* outer, REFNSIID iid,
+ void** result);
+
+#endif // _nsMIMEInputStream_h_
diff --git a/netwerk/base/nsMediaFragmentURIParser.cpp b/netwerk/base/nsMediaFragmentURIParser.cpp
new file mode 100644
index 0000000000..9252c24263
--- /dev/null
+++ b/netwerk/base/nsMediaFragmentURIParser.cpp
@@ -0,0 +1,353 @@
+/* -*- 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;
+ } else if (!gotTemporal && fragments[i].first.EqualsLiteral("t")) {
+ nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
+ gotTemporal = ParseNPT(nsDependentSubstring(value, 0));
+ } else if (!gotSpatial && fragments[i].first.EqualsLiteral("xywh")) {
+ nsAutoString value = NS_ConvertUTF8toUTF16(fragments[i].second);
+ gotSpatial = ParseXYWH(nsDependentSubstring(value, 0));
+ }
+ }
+}
+
+} // 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..1e50e5cebf
--- /dev/null
+++ b/netwerk/base/nsNetUtil.cpp
@@ -0,0 +1,3268 @@
+/* -*- 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 "nsNetUtil.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/LoadContext.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Monitor.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 "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 "nsIOfflineCacheUpdate.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 "plstr.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>
+
+#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::services::GetIOService();
+ 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(nullptr, 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)) {
+ 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 loadingOrigin;
+ MOZ_ALWAYS_SUCCEEDS(aLoadingPrincipal->GetOrigin(loadingOrigin));
+
+ nsAutoCString clientOrigin;
+ MOZ_ALWAYS_SUCCEEDS(clientPrincipal->GetOrigin(clientOrigin));
+
+ MOZ_DIAGNOSTIC_ASSERT(loadingOrigin == clientOrigin);
+ }
+#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 */) {
+ return NS_NewChannelInternal(
+ outChannel, aUri,
+ nullptr, // aLoadingNode,
+ aLoadingPrincipal,
+ nullptr, // aTriggeringPrincipal
+ Maybe<ClientInfo>(), Maybe<ServiceWorkerDescriptor>(), aSecurityFlags,
+ aContentPolicyType, aCookieJarSettings, aPerformanceStorage, aLoadGroup,
+ aCallbacks, aLoadFlags, aIoService, aSandboxFlags);
+}
+
+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 */) {
+ 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);
+}
+
+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 */) {
+ 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, 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 */) {
+ 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);
+}
+
+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, false, nullptr) == 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 has a lot of XPCOM
+ // overhead involved. We optimize the protocols that matter for Web pages
+ // (HTTP and HTTPS) by hardcoding their default ports here.
+ 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;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ioService->GetProtocolHandler(scheme, getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return -1;
+ int32_t port;
+ rv = handler->GetDefaultPort(&port);
+ return NS_SUCCEEDED(rv) ? port : -1;
+}
+
+/**
+ * This function is a helper function to apply the ToAscii conversion
+ * to a string
+ */
+bool NS_StringToACE(const nsACString& idn, nsACString& result) {
+ nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ if (!idnSrv) return false;
+ nsresult rv = idnSrv->ConvertUTF8toACE(idn, result);
+ if (NS_FAILED(rv)) return false;
+
+ return true;
+}
+
+int32_t NS_GetRealPort(nsIURI* aURI) {
+ int32_t port;
+ nsresult rv = aURI->GetPort(&port);
+ if (NS_FAILED(rv)) return -1;
+
+ if (port != -1) return port; // explicitly specified
+
+ // Otherwise, we have to get the default port from the protocol handler
+
+ // Need the scheme first
+ nsAutoCString scheme;
+ rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return -1;
+
+ return NS_GetDefaultPort(scheme.get());
+}
+
+nsresult NS_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 */,
+ nsIEventTarget* 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 = nsSyncStreamListener::Create();
+ if (listener) {
+ nsresult rv = listener->GetInputStream(stream);
+ if (NS_SUCCEEDED(rv)) {
+ listener.forget(result);
+ }
+ return rv;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult NS_ImplementChannelOpen(nsIChannel* channel, nsIInputStream** result) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewSyncStreamListener(getter_AddRefs(listener),
+ getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_MaybeOpenChannelUsingAsyncOpen(channel, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t n;
+ // block until the initial response is received or an error occurs.
+ rv = stream->Available(&n);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *result = nullptr;
+ stream.swap(*result);
+
+ return NS_OK;
+}
+
+nsresult NS_NewRequestObserverProxy(nsIRequestObserver** result,
+ nsIRequestObserver* observer,
+ nsISupports* context) {
+ 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;
+
+ nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(channel));
+ if (props) {
+ // We have to check for a property on a property bag because the
+ // referrer may be empty for security reasons (for example, when loading
+ // an http page with an https referrer).
+ nsresult rv = props->GetPropertyAsInterface(
+ u"docshell.internalReferrer"_ns, NS_GET_IID(nsIURI),
+ reinterpret_cast<void**>(referrer));
+ if (NS_SUCCEEDED(rv)) {
+ 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::services::GetIOService();
+ 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_NewLocalFileStream(nsIFileStream** result, nsIFile* file,
+ int32_t ioFlags /* = -1 */,
+ int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsCOMPtr<nsIFileStream> stream = new nsFileStream();
+ nsresult rv = stream->Init(file, ioFlags, perm, behaviorFlags);
+ if (NS_SUCCEEDED(rv)) {
+ stream.forget(result);
+ }
+ return rv;
+}
+
+mozilla::Result<nsCOMPtr<nsIFileStream>, nsresult> NS_NewLocalFileStream(
+ nsIFile* file, int32_t ioFlags /* = -1 */, int32_t perm /* = -1 */,
+ int32_t behaviorFlags /* = 0 */) {
+ nsCOMPtr<nsIFileStream> stream;
+ const nsresult rv = NS_NewLocalFileStream(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(
+ nullptr, 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 = new TaskQueue(target.forget());
+ }
+
+ 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;
+
+ 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) {
+ nsCOMPtr<nsIURI> base(aBaseURI);
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(NS_MutatorMethod(&nsIStandardURLMutator::Init,
+ nsIStandardURL::URLTYPE_AUTHORITY, aDefaultPort,
+ nsCString(aSpec), aCharset, base, nullptr))
+ .Finalize(aURI);
+}
+
+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;
+ }
+
+ 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("ssh")) {
+ return NewStandardURI(aSpec, aCharset, aBaseURI, 22, aURI);
+ }
+
+ if (scheme.EqualsLiteral("file")) {
+ nsAutoCString buf(aSpec);
+#if defined(XP_WIN)
+ buf.Truncate();
+ if (!net_NormalizeFileURL(aSpec, buf)) {
+ buf = aSpec;
+ }
+#endif
+
+ nsCOMPtr<nsIURI> base(aBaseURI);
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(NS_MutatorMethod(&nsIFileURLMutator::MarkFileURL))
+ .Apply(NS_MutatorMethod(&nsIStandardURLMutator::Init,
+ nsIStandardURL::URLTYPE_NO_AUTHORITY, -1, buf,
+ aCharset, base, 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("moz-anno") ||
+ scheme.EqualsLiteral("moz-fonttable")) {
+ 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")) {
+ nsCOMPtr<nsIURI> base(aBaseURI);
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(NS_MutatorMethod(&nsIStandardURLMutator::Init,
+ nsIStandardURL::URLTYPE_AUTHORITY, 0,
+ nsCString(aSpec), aCharset, base, 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")) {
+ nsCOMPtr<nsIURI> base(aBaseURI);
+ return NS_MutateURI(new nsJARURI::Mutator())
+ .Apply(NS_MutatorMethod(&nsIJARURIMutator::SetSpecBaseCharset,
+ nsCString(aSpec), base, 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")) {
+ nsCOMPtr<nsIURI> base(aBaseURI);
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(NS_MutatorMethod(&nsIStandardURLMutator::Init,
+ nsIStandardURL::URLTYPE_STANDARD, -1,
+ nsCString(aSpec), aCharset, base, nullptr))
+ .Finalize(aURI);
+ }
+#endif
+
+ if (scheme.EqualsLiteral("android")) {
+ nsCOMPtr<nsIURI> base(aBaseURI);
+ return NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .Apply(NS_MutatorMethod(&nsIStandardURLMutator::Init,
+ nsIStandardURL::URLTYPE_STANDARD, -1,
+ nsCString(aSpec), aCharset, base, 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("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();
+}
+
+bool NS_ShouldCheckAppCache(nsIPrincipal* aPrincipal) {
+ uint32_t privateBrowsingId = 0;
+ nsresult rv = aPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
+ if (NS_SUCCEEDED(rv) && (privateBrowsingId > 0)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIOfflineCacheUpdateService> offlineService =
+ do_GetService("@mozilla.org/offlinecacheupdate-service;1");
+ if (!offlineService) {
+ return false;
+ }
+
+ bool allowed;
+ rv = offlineService->OfflineAppAllowed(aPrincipal, &allowed);
+ return NS_SUCCEEDED(rv) && allowed;
+}
+
+void NS_WrapAuthPrompt(nsIAuthPrompt* aAuthPrompt,
+ nsIAuthPrompt2** aAuthPrompt2) {
+ nsCOMPtr<nsIAuthPromptAdapterFactory> factory =
+ do_GetService(NS_AUTHPROMPT_ADAPTER_FACTORY_CONTRACTID);
+ if (!factory) return;
+
+ NS_WARNING("Using deprecated nsIAuthPrompt");
+ factory->CreateAdapter(aAuthPrompt, aAuthPrompt2);
+}
+
+void NS_QueryAuthPrompt2(nsIInterfaceRequestor* aCallbacks,
+ nsIAuthPrompt2** aAuthPrompt) {
+ CallGetInterface(aCallbacks, aAuthPrompt);
+ if (*aAuthPrompt) return;
+
+ // Maybe only nsIAuthPrompt is provided and we have to wrap it.
+ nsCOMPtr<nsIAuthPrompt> prompt(do_GetInterface(aCallbacks));
+ if (!prompt) return;
+
+ NS_WrapAuthPrompt(prompt, aAuthPrompt);
+}
+
+void NS_QueryAuthPrompt2(nsIChannel* aChannel, nsIAuthPrompt2** aAuthPrompt) {
+ *aAuthPrompt = nullptr;
+
+ // We want to use any auth prompt we can find on the channel's callbacks,
+ // and if that fails use the loadgroup's prompt (if any)
+ // Therefore, we can't just use NS_QueryNotificationCallbacks, because
+ // that would prefer a loadgroup's nsIAuthPrompt2 over a channel's
+ // nsIAuthPrompt.
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (callbacks) {
+ NS_QueryAuthPrompt2(callbacks, aAuthPrompt);
+ if (*aAuthPrompt) return;
+ }
+
+ nsCOMPtr<nsILoadGroup> group;
+ aChannel->GetLoadGroup(getter_AddRefs(group));
+ if (!group) return;
+
+ group->GetNotificationCallbacks(getter_AddRefs(callbacks));
+ if (!callbacks) return;
+ NS_QueryAuthPrompt2(callbacks, aAuthPrompt);
+}
+
+nsresult NS_NewNotificationCallbacksAggregation(
+ nsIInterfaceRequestor* callbacks, nsILoadGroup* loadGroup,
+ nsIEventTarget* target, nsIInterfaceRequestor** result) {
+ nsCOMPtr<nsIInterfaceRequestor> cbs;
+ if (loadGroup) loadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
+ return NS_NewInterfaceRequestorAggregation(callbacks, cbs, target, result);
+}
+
+nsresult NS_NewNotificationCallbacksAggregation(
+ nsIInterfaceRequestor* callbacks, nsILoadGroup* loadGroup,
+ nsIInterfaceRequestor** result) {
+ return NS_NewNotificationCallbacksAggregation(callbacks, loadGroup, nullptr,
+ result);
+}
+
+nsresult NS_DoImplGetInnermostURI(nsINestedURI* nestedURI, nsIURI** result) {
+ 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;
+ }
+
+ if (!StaticPrefs::privacy_file_unique_origin()) {
+ //
+ // If the file to be loaded is in a subdirectory of the source
+ // (or same-dir if source is not a directory) then it will
+ // inherit its source principal and be scriptable by that source.
+ //
+ bool sourceIsDir;
+ bool allowed = false;
+ nsresult rv = sourceFile->IsDirectory(&sourceIsDir);
+ if (NS_SUCCEEDED(rv) && sourceIsDir) {
+ rv = sourceFile->Contains(targetFile, &allowed);
+ } else {
+ nsCOMPtr<nsIFile> sourceParent;
+ rv = sourceFile->GetParent(getter_AddRefs(sourceParent));
+ if (NS_SUCCEEDED(rv) && sourceParent) {
+ rv = sourceParent->Equals(targetFile, &allowed);
+ if (NS_FAILED(rv) || !allowed) {
+ rv = sourceParent->Contains(targetFile, &allowed);
+ } else {
+ MOZ_ASSERT(aAllowDirectoryTarget,
+ "sourceFile->Parent == targetFile, but targetFile "
+ "should've been disallowed if it is a directory");
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && allowed) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool NS_IsInternalSameURIRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel, uint32_t aFlags) {
+ if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> oldURI, newURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+
+ if (!oldURI || !newURI) {
+ return false;
+ }
+
+ bool res;
+ return NS_SUCCEEDED(oldURI->Equals(newURI, &res)) && res;
+}
+
+bool NS_IsHSTSUpgradeRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel,
+ uint32_t aFlags) {
+ if (!(aFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> oldURI, newURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+
+ if (!oldURI || !newURI) {
+ return false;
+ }
+
+ 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;
+}
+
+nsresult NS_LinkRedirectChannels(uint64_t channelId,
+ nsIParentChannel* parentChannel,
+ nsIChannel** _result) {
+ nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+ RedirectChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+
+ return registrar->LinkChannels(channelId, parentChannel, _result);
+}
+
+nsresult NS_MaybeOpenChannelUsingOpen(nsIChannel* aChannel,
+ nsIInputStream** aStream) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ return aChannel->Open(aStream);
+}
+
+nsresult NS_MaybeOpenChannelUsingAsyncOpen(nsIChannel* aChannel,
+ nsIStreamListener* aListener) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ return aChannel->AsyncOpen(aListener);
+}
+
+nsILoadInfo::CrossOriginEmbedderPolicy
+NS_GetCrossOriginEmbedderPolicyFromHeader(const nsACString& aHeader) {
+ 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;
+ }
+
+ return embedderPolicy.EqualsLiteral("require-corp")
+ ? nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP
+ : 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),
+ 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;
+ }
+
+ return uri->GetSpecOrDefault().EqualsLiteral("about:blank");
+}
+
+nsresult NS_GenerateHostPort(const nsCString& host, int32_t port,
+ nsACString& hostLine) {
+ if (strchr(host.get(), ':')) {
+ // host is an IPv6 address literal and must be encapsulated in []'s
+ hostLine.Assign('[');
+ // scope id is not needed for Host header.
+ int scopeIdPos = host.FindChar('%');
+ if (scopeIdPos == -1)
+ hostLine.Append(host);
+ else if (scopeIdPos > 0)
+ hostLine.Append(Substring(host, 0, scopeIdPos));
+ else
+ return NS_ERROR_MALFORMED_URI;
+ hostLine.Append(']');
+ } else
+ hostLine.Assign(host);
+ if (port != -1) {
+ hostLine.Append(':');
+ hostLine.AppendInt(port);
+ }
+ return NS_OK;
+}
+
+void NS_SniffContent(const char* aSnifferType, nsIRequest* aRequest,
+ const uint8_t* aData, uint32_t aLength,
+ nsACString& aSniffedType) {
+ typedef nsCategoryCache<nsIContentSniffer> ContentSnifferCache;
+ extern ContentSnifferCache* gNetSniffers;
+ extern ContentSnifferCache* gDataSniffers;
+ ContentSnifferCache* cache = nullptr;
+ if (!strcmp(aSnifferType, NS_CONTENT_SNIFFER_CATEGORY)) {
+ if (!gNetSniffers) {
+ gNetSniffers = new ContentSnifferCache(NS_CONTENT_SNIFFER_CATEGORY);
+ }
+ cache = gNetSniffers;
+ } else if (!strcmp(aSnifferType, NS_DATA_SNIFFER_CATEGORY)) {
+ if (!gDataSniffers) {
+ gDataSniffers = new ContentSnifferCache(NS_DATA_SNIFFER_CATEGORY);
+ }
+ cache = gDataSniffers;
+ } else {
+ // Invalid content sniffer type was requested
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ // 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;
+}
+
+nsresult NS_ShouldSecureUpgrade(
+ nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIPrincipal* aChannelResultPrincipal,
+ bool aPrivateBrowsing, bool aAllowSTS,
+ const OriginAttributes& aOriginAttributes, bool& aShouldUpgrade,
+ std::function<void(bool, nsresult)>&& aResultCallback,
+ bool& aWillCallback) {
+ aWillCallback = 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 (!isHttps &&
+ !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aURI)) {
+ if (aLoadInfo) {
+ // Check if the request can get upgraded with the HTTPS-Only mode
+ if (nsHTTPSOnlyUtils::ShouldUpgradeRequest(aURI, aLoadInfo)) {
+ aShouldUpgrade = true;
+ return NS_OK;
+ }
+
+ // If any of the documents up the chain to the root document makes use of
+ // the CSP directive 'upgrade-insecure-requests', then it's time to
+ // fulfill the promise to CSP and mixed content blocking to upgrade the
+ // channel from http to https.
+ if (aLoadInfo->GetUpgradeInsecureRequests() ||
+ 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);
+
+ if (aLoadInfo->GetUpgradeInsecureRequests()) {
+ AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
+ uint32_t innerWindowId = aLoadInfo->GetInnerWindowID();
+ CSP_LogLocalizedStr(
+ "upgradeInsecureRequest", params,
+ u""_ns, // aSourceFile
+ u""_ns, // aScriptSample
+ 0, // aLineNumber
+ 0, // aColumnNumber
+ nsIScriptError::warningFlag, "upgradeInsecureRequest"_ns,
+ innerWindowId,
+ !!aLoadInfo->GetOriginAttributes().mPrivateBrowsingId);
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::CSP);
+ } else {
+ 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);
+
+ uint32_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);
+ }
+
+ aShouldUpgrade = true;
+ return NS_OK;
+ }
+ }
+
+ // enforce Strict-Transport-Security
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
+
+ bool isStsHost = false;
+ uint32_t hstsSource = 0;
+ uint32_t flags =
+ aPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
+
+ auto handleResultFunc = [aAllowSTS](bool aIsStsHost, uint32_t aHstsSource) {
+ if (aIsStsHost) {
+ LOG(("nsHttpChannel::Connect() STS permissions found\n"));
+ if (aAllowSTS) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::STS);
+ switch (aHstsSource) {
+ case nsISiteSecurityService::SOURCE_PRELOAD_LIST:
+ Telemetry::Accumulate(Telemetry::HSTS_UPGRADE_SOURCE, 0);
+ break;
+ case nsISiteSecurityService::SOURCE_ORGANIC_REQUEST:
+ Telemetry::Accumulate(Telemetry::HSTS_UPGRADE_SOURCE, 1);
+ break;
+ case nsISiteSecurityService::SOURCE_UNKNOWN:
+ default:
+ // record this as an organic request
+ Telemetry::Accumulate(Telemetry::HSTS_UPGRADE_SOURCE, 1);
+ break;
+ }
+ return true;
+ }
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::PrefBlockedSTS);
+ } else {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::NoReasonToUpgrade);
+ }
+ return 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<nsIURI> uri = aURI;
+ nsCOMPtr<nsISiteSecurityService> service = sss;
+ nsresult rv = gSocketTransportService->Dispatch(
+ NS_NewRunnableFunction(
+ "net::NS_ShouldSecureUpgrade",
+ [service{std::move(service)}, uri{std::move(uri)}, flags(flags),
+ originAttributes(aOriginAttributes),
+ handleResultFunc{std::move(handleResultFunc)},
+ resultCallback{std::move(aResultCallback)}]() mutable {
+ uint32_t hstsSource = 0;
+ bool isStsHost = false;
+ nsresult rv = service->IsSecureURI(
+ nsISiteSecurityService::HEADER_HSTS, uri, flags,
+ originAttributes, nullptr, &hstsSource, &isStsHost);
+
+ // Successfully get the result from |IsSecureURI| implies that
+ // the storage is ready to read.
+ storageReady = NS_SUCCEEDED(rv);
+ bool shouldUpgrade = handleResultFunc(isStsHost, hstsSource);
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "net::NS_ShouldSecureUpgrade::ResultCallback",
+ [rv, shouldUpgrade,
+ resultCallback{std::move(resultCallback)}]() {
+ resultCallback(shouldUpgrade, rv);
+ }));
+ }),
+ NS_DISPATCH_NORMAL);
+ aWillCallback = NS_SUCCEEDED(rv);
+ return rv;
+ }
+
+ nsresult rv =
+ sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags,
+ aOriginAttributes, nullptr, &hstsSource, &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(isStsHost, hstsSource);
+ return NS_OK;
+ }
+
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_HTTP_SCHEME_UPGRADE_TYPE::AlreadyHTTPS);
+ aShouldUpgrade = false;
+ return NS_OK;
+}
+
+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(
+ NS_MutatorMethod(&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.
+ if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal() &&
+ ExtContentPolicy::TYPE_DOCUMENT != type) {
+ return false;
+ }
+
+ return true;
+}
+
+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");
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsNetUtil.h b/netwerk/base/nsNetUtil.h
new file mode 100644
index 0000000000..39e3d6f4bd
--- /dev/null
+++ b/netwerk/base/nsNetUtil.h
@@ -0,0 +1,984 @@
+/* -*- 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 "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"
+
+class nsIPrincipal;
+class nsIAsyncStreamCopier;
+class nsIAuthPrompt;
+class nsIAuthPrompt2;
+class nsIChannel;
+class nsIChannelPolicy;
+class nsICookieJarSettings;
+class nsIDownloadObserver;
+class nsIEventTarget;
+class nsIFileProtocolHandler;
+class nsIFileStream;
+class nsIHttpChannel;
+class nsIInputStream;
+class nsIInputStreamPump;
+class nsIInterfaceRequestor;
+class nsIOutputStream;
+class nsIParentChannel;
+class nsIPersistentProperties;
+class nsIProxyInfo;
+class nsIRequestObserver;
+class nsIStreamListener;
+class nsIStreamLoader;
+class nsIStreamLoaderObserver;
+class nsIIncrementalStreamLoader;
+class nsIIncrementalStreamLoaderObserver;
+
+namespace mozilla {
+class Encoding;
+class OriginAttributes;
+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** result, 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
+
+// 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);
+
+// 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);
+
+// 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);
+
+// 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);
+
+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,
+ nsIEventTarget* 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_NewLocalFileStream(nsIFileStream** result, nsIFile* file,
+ int32_t ioFlags = -1, int32_t perm = -1,
+ int32_t behaviorFlags = 0);
+
+mozilla::Result<nsCOMPtr<nsIFileStream>, nsresult> NS_NewLocalFileStream(
+ 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"
+
+/**
+ * Determines whether appcache should be checked for a given principal.
+ */
+bool NS_ShouldCheckAppCache(nsIPrincipal* aPrincipal);
+
+/**
+ * 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);
+
+nsresult NS_LinkRedirectChannels(uint64_t channelId,
+ nsIParentChannel* parentChannel,
+ nsIChannel** _result);
+
+/**
+ * Helper function which checks whether the channel can be
+ * openend using Open() or has to fall back to opening
+ * the channel using Open().
+ */
+nsresult NS_MaybeOpenChannelUsingOpen(nsIChannel* aChannel,
+ nsIInputStream** aStream);
+
+/**
+ * Helper function which checks whether the channel can be
+ * openend using AsyncOpen() or has to fall back to opening
+ * the channel using AsyncOpen().
+ */
+nsresult NS_MaybeOpenChannelUsingAsyncOpen(nsIChannel* aChannel,
+ nsIStreamListener* aListener);
+
+/**
+ * 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);
+
+/** 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);
+
+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);
+
+/**
+ * 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 aPrivateBrowsing, 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);
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsNetUtil_h__
diff --git a/netwerk/base/nsNetworkInfoService.cpp b/netwerk/base/nsNetworkInfoService.cpp
new file mode 100644
index 0000000000..4d3da66a8e
--- /dev/null
+++ b/netwerk/base/nsNetworkInfoService.cpp
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if defined(XP_MACOSX) || defined(XP_LINUX)
+# include <unistd.h>
+#elif defined(XP_WIN)
+# include <winsock2.h>
+#endif
+
+#include "nsNetworkInfoService.h"
+
+#if defined(XP_MACOSX) || defined(XP_WIN) || defined(XP_LINUX)
+# include "NetworkInfoServiceImpl.h"
+#else
+# error "Unsupported platform for nsNetworkInfoService! Check moz.build"
+#endif
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(nsNetworkInfoService, nsINetworkInfoService)
+
+nsNetworkInfoService::nsNetworkInfoService() = default;
+
+nsresult nsNetworkInfoService::Init() { return NS_OK; }
+
+nsresult nsNetworkInfoService::ListNetworkAddresses(
+ nsIListNetworkAddressesListener* aListener) {
+ nsresult rv;
+
+ AddrMapType addrMap;
+ rv = DoListAddresses(addrMap);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aListener->OnListNetworkAddressesFailed();
+ return NS_OK;
+ }
+
+ uint32_t addrCount = addrMap.Count();
+ nsTArray<nsCString> addrStrings;
+ if (!addrStrings.SetCapacity(addrCount, fallible)) {
+ aListener->OnListNetworkAddressesFailed();
+ return NS_OK;
+ }
+
+ for (auto iter = addrMap.Iter(); !iter.Done(); iter.Next()) {
+ addrStrings.AppendElement(iter.Data());
+ }
+ aListener->OnListedNetworkAddresses(addrStrings);
+ return NS_OK;
+}
+
+// TODO: Bug 1275373: https://bugzilla.mozilla.org/show_bug.cgi?id=1275373
+// Use platform-specific implementation of DoGetHostname on Cocoa and Windows.
+static nsresult DoGetHostname(nsACString& aHostname) {
+ char hostnameBuf[256];
+ int result = gethostname(hostnameBuf, 256);
+ if (result == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Ensure that there is always a terminating NUL byte.
+ hostnameBuf[255] = '\0';
+
+ // Find the first '.', terminate string there.
+ char* dotLocation = strchr(hostnameBuf, '.');
+ if (dotLocation) {
+ *dotLocation = '\0';
+ }
+
+ if (strlen(hostnameBuf) == 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aHostname.AssignASCII(hostnameBuf);
+ return NS_OK;
+}
+
+nsresult nsNetworkInfoService::GetHostname(nsIGetHostnameListener* aListener) {
+ nsresult rv;
+ nsCString hostnameStr;
+ rv = DoGetHostname(hostnameStr);
+ if (NS_FAILED(rv)) {
+ aListener->OnGetHostnameFailed();
+ return NS_OK;
+ }
+
+ aListener->OnGotHostname(hostnameStr);
+
+ return NS_OK;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/base/nsNetworkInfoService.h b/netwerk/base/nsNetworkInfoService.h
new file mode 100644
index 0000000000..d7b449b96f
--- /dev/null
+++ b/netwerk/base/nsNetworkInfoService.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_nsNetworkInfoService_h
+#define mozilla_net_nsNetworkInfoService_h
+
+#include "nsISupportsImpl.h"
+
+#include "nsINetworkInfoService.h"
+
+#define NETWORKINFOSERVICE_CID \
+ { \
+ 0x296d0900, 0xf8ef, 0x4df0, { \
+ 0x9c, 0x35, 0xdb, 0x58, 0x62, 0xab, 0xc5, 0x8d \
+ } \
+ }
+
+namespace mozilla {
+namespace net {
+
+class nsNetworkInfoService final : public nsINetworkInfoService {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKINFOSERVICE
+
+ nsresult Init();
+
+ explicit nsNetworkInfoService();
+
+ private:
+ virtual ~nsNetworkInfoService() = default;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_dom_nsNetworkInfoService_h
diff --git a/netwerk/base/nsPACMan.cpp b/netwerk/base/nsPACMan.cpp
new file mode 100644
index 0000000000..509c9a3508
--- /dev/null
+++ b/netwerk/base/nsPACMan.cpp
@@ -0,0 +1,964 @@
+/* -*- 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 "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Telemetry.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<nsIEventTarget> target = mPACMan->GetNeckoTarget();
+ mPACMan->mPAC.Init(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");
+ if (!sThreadLocalSetup) {
+ sThreadLocalSetup = true;
+ PR_NewThreadPrivateIndex(&sThreadLocalIndex, nullptr);
+ }
+ mPAC.SetThreadLocalIndex(sThreadLocalIndex);
+ mIncludePath = Preferences::GetBool(kPACIncludePath, false);
+}
+
+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)));
+ }
+
+ return mPACThread->Dispatch(
+ e.forget(),
+ aSync ? nsIEventTarget::DISPATCH_SYNC : nsIEventTarget::DISPATCH_NORMAL);
+}
+
+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())
+ : GetCurrentEventTarget()->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);
+
+ 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,
+ 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()));
+
+ if (mShutdown || IsLoading()) {
+ 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) {
+ nsresult status =
+ mPAC.GetProxyForURI(query->mSpec, query->mHost, pacString);
+ LOG(("Use proxy from PAC: %s\n", pacString.get()));
+ query->Complete(status, pacString);
+ }
+
+ mInProgress = false;
+ return true;
+}
+
+NS_IMPL_ISUPPORTS(nsPACMan, nsIStreamLoaderObserver, nsIInterfaceRequestor,
+ nsIChannelEventSink)
+
+NS_IMETHODIMP
+nsPACMan::OnStreamComplete(nsIStreamLoader* loader, nsISupports* context,
+ nsresult status, uint32_t dataLen,
+ const uint8_t* data) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+
+ {
+ 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;
+ }
+ }
+
+ LOG(("OnStreamComplete: entry\n"));
+
+ if (NS_SUCCEEDED(status) && HttpRequestSucceeded(loader)) {
+ // Get the URI spec used to load this PAC script.
+ nsAutoCString pacURI;
+ {
+ nsCOMPtr<nsIRequest> request;
+ loader->GetRequest(getter_AddRefs(request));
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri) uri->GetAsciiSpec(pacURI);
+ }
+ }
+
+ // We 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..373220ab30
--- /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 mustCallbackOnMainThread);
+
+ /**
+ * This method may be called to reload the PAC file. While we are loading
+ * the PAC file, any asynchronous PAC queries will be queued up to be
+ * processed once the PAC file finishes loading.
+ *
+ * @param 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);
+
+ ProxyAutoConfig 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;
+ uint32_t 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..4effa08066
--- /dev/null
+++ b/netwerk/base/nsPreloadedStream.cpp
@@ -0,0 +1,122 @@
+/* -*- 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)
+
+nsPreloadedStream::nsPreloadedStream(nsIAsyncInputStream* aStream,
+ const char* data, uint32_t datalen)
+ : mStream(aStream), mOffset(0), mLen(datalen) {
+ mBuf = (char*)moz_xmalloc(datalen);
+ memcpy(mBuf, data, datalen);
+}
+
+nsPreloadedStream::~nsPreloadedStream() { free(mBuf); }
+
+NS_IMETHODIMP
+nsPreloadedStream::Close() {
+ mLen = 0;
+ return mStream->Close();
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::Available(uint64_t* _retval) {
+ uint64_t avail = 0;
+
+ nsresult rv = mStream->Available(&avail);
+ if (NS_FAILED(rv)) return rv;
+ *_retval = avail + mLen;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
+ if (!mLen) return mStream->Read(aBuf, aCount, _retval);
+
+ uint32_t toRead = std::min(mLen, aCount);
+ memcpy(aBuf, mBuf + mOffset, toRead);
+ mOffset += toRead;
+ mLen -= toRead;
+ *_retval = toRead;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* result) {
+ if (!mLen) return mStream->ReadSegments(aWriter, aClosure, aCount, result);
+
+ *result = 0;
+ while (mLen > 0 && aCount > 0) {
+ uint32_t toRead = std::min(mLen, aCount);
+ uint32_t didRead = 0;
+ nsresult rv;
+
+ rv = aWriter(this, aClosure, mBuf + mOffset, *result, toRead, &didRead);
+
+ if (NS_FAILED(rv)) return NS_OK;
+
+ *result += didRead;
+ mOffset += didRead;
+ mLen -= didRead;
+ aCount -= didRead;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::IsNonBlocking(bool* _retval) {
+ return mStream->IsNonBlocking(_retval);
+}
+
+NS_IMETHODIMP
+nsPreloadedStream::CloseWithStatus(nsresult aStatus) {
+ mLen = 0;
+ return mStream->CloseWithStatus(aStatus);
+}
+
+class RunOnThread : public Runnable {
+ public:
+ RunOnThread(nsIAsyncInputStream* aStream, nsIInputStreamCallback* aCallback)
+ : 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)
+ return mStream->AsyncWait(aCallback, aFlags, aRequestedCount, aEventTarget);
+
+ if (!aCallback) return NS_OK;
+
+ if (!aEventTarget) return aCallback->OnInputStreamReady(this);
+
+ nsCOMPtr<nsIRunnable> event = new RunOnThread(this, aCallback);
+ return aEventTarget->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsPreloadedStream.h b/netwerk/base/nsPreloadedStream.h
new file mode 100644
index 0000000000..368cfbbe1e
--- /dev/null
+++ b/netwerk/base/nsPreloadedStream.h
@@ -0,0 +1,52 @@
+/* -*- 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"
+
+namespace mozilla {
+namespace net {
+
+class nsPreloadedStream final : public nsIAsyncInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ nsPreloadedStream(nsIAsyncInputStream* aStream, const char* data,
+ uint32_t datalen);
+
+ private:
+ ~nsPreloadedStream();
+
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+
+ char* mBuf;
+ uint32_t mOffset;
+ uint32_t mLen;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/base/nsProtocolProxyService.cpp b/netwerk/base/nsProtocolProxyService.cpp
new file mode 100644
index 0000000000..fdfe24cc4a
--- /dev/null
+++ b/netwerk/base/nsProtocolProxyService.cpp
@@ -0,0 +1,2376 @@
+/* -*- 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 "nsIDNSService.h"
+#include "nsPIDNSService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsContentUtils.h"
+#include "nsThreadUtils.h"
+#include "nsQueryObject.h"
+#include "nsSOCKSIOLayer.h"
+#include "nsString.h"
+#include "nsNetUtil.h"
+#include "nsNetCID.h"
+#include "plstr.h"
+#include "prnetdb.h"
+#include "nsPACMan.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/CondVar.h"
+#include "nsISystemProxySettings.h"
+#include "nsINetworkLinkService.h"
+#include "nsIHttpChannelInternal.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/Logging.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[];
+
+#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;
+ int32_t defaultPort;
+};
+
+//----------------------------------------------------------------------------
+
+// Return the channel's proxy URI, or if it doesn't exist, the
+// channel's main URI.
+static nsresult GetProxyURI(nsIChannel* channel, nsIURI** aOut) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> proxyURI;
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel(do_QueryInterface(channel));
+ if (httpChannel) {
+ rv = httpChannel->GetProxyURI(getter_AddRefs(proxyURI));
+ }
+ if (!proxyURI) {
+ rv = channel->GetURI(getter_AddRefs(proxyURI));
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ proxyURI.forget(aOut);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+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)
+ : mStatus(NS_OK),
+ mDispatched(false),
+ mResolveFlags(aResolveFlags),
+ mPPS(pps),
+ mXPComPPS(pps),
+ mChannel(channel),
+ mCallback(callback) {
+ NS_ASSERTION(mCallback, "null callback");
+ }
+
+ private:
+ ~nsAsyncResolveRequest() {
+ if (!NS_IsMainThread()) {
+ // these xpcom pointers might need to be proxied back to the
+ // main thread to delete safely, but if this request had its
+ // callbacks called normally they will all be null and this is a nop
+
+ if (mChannel) {
+ NS_ReleaseOnMainThread("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
+
+ typedef std::function<nsresult(nsAsyncResolveRequest*, nsIProxyInfo*, bool)>
+ Callback;
+
+ 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:
+ typedef nsProtocolProxyService::FilterLink 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<nsIProxyInfo> proxyInfo = mProxyInfo;
+ while (proxyInfo) {
+ proxyInfo->SetResolveFlags(mResolveFlags);
+ proxyInfo->GetFailoverProxy(getter_AddRefs(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;
+ nsCString mPACString;
+ nsCString mPACURL;
+ bool mDispatched;
+ 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_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()
+ : mFilterLocalHosts(false),
+ mProxyConfig(PROXYCONFIG_DIRECT),
+ mHTTPProxyPort(-1),
+ mFTPProxyPort(-1),
+ mHTTPSProxyPort(-1),
+ mSOCKSProxyPort(-1),
+ mSOCKSProxyVersion(4),
+ mSOCKSProxyRemoteDNS(false),
+ mProxyOverTLS(true),
+ mWPADOverDHCPEnabled(false),
+ mPACMan(nullptr),
+ mSessionStart(PR_Now()),
+ mFailedProxyTimeout(30 * 60) // 30 minute default
+ ,
+ mIsShutdown(false) {}
+
+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() {
+ mProxySettingTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+
+ // 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();
+ }
+
+ if (NS_WARN_IF(!mProxySettingTarget)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ return mProxySettingTarget->Dispatch(req, 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 (mProxySettingTarget) {
+ mProxySettingTarget = nullptr;
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ } else if (strcmp(aTopic, NS_NETWORK_LINK_TOPIC) == 0) {
+ nsCString converted = NS_ConvertUTF16toUTF8(aData);
+ const char* state = converted.get();
+ if (!strcmp(state, NS_NETWORK_LINK_DATA_CHANGED)) {
+ ReloadNetworkPAC();
+ }
+ } else {
+ NS_ASSERTION(strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0,
+ "what is this random observer event?");
+ nsCOMPtr<nsIPrefBranch> prefs = do_QueryInterface(aSubject);
+ if (prefs) PrefsChanged(prefs, NS_LossyConvertUTF16toASCII(aData).get());
+ }
+ return NS_OK;
+}
+
+void nsProtocolProxyService::PrefsChanged(nsIPrefBranch* prefBranch,
+ const char* pref) {
+ nsresult rv = NS_OK;
+ bool reloadPAC = false;
+ nsAutoCString tempString;
+
+ if (!pref || !strcmp(pref, PROXY_PREF("type"))) {
+ int32_t type = -1;
+ rv = prefBranch->GetIntPref(PROXY_PREF("type"), &type);
+ if (NS_SUCCEEDED(rv)) {
+ // bug 115720 - for ns4.x backwards compatibility
+ if (type == PROXYCONFIG_DIRECT4X) {
+ type = PROXYCONFIG_DIRECT;
+ // Reset the type so that the dialog looks correct, and we
+ // don't have to handle this case everywhere else
+ // I'm paranoid about a loop of some sort - only do this
+ // if we're enumerating all prefs, and ignore any error
+ if (!pref) prefBranch->SetIntPref(PROXY_PREF("type"), type);
+ } else if (type >= PROXYCONFIG_COUNT) {
+ LOG(("unknown proxy type: %" 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("ftp")))
+ proxy_GetStringPref(prefBranch, PROXY_PREF("ftp"), mFTPProxyHost);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("ftp_port")))
+ proxy_GetIntPref(prefBranch, PROXY_PREF("ftp_port"), mFTPProxyPort);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks")))
+ proxy_GetStringPref(prefBranch, PROXY_PREF("socks"), mSOCKSProxyTarget);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_port")))
+ proxy_GetIntPref(prefBranch, PROXY_PREF("socks_port"), mSOCKSProxyPort);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_version"))) {
+ int32_t version;
+ proxy_GetIntPref(prefBranch, PROXY_PREF("socks_version"), version);
+ // make sure this preference value remains sane
+ if (version == 5)
+ mSOCKSProxyVersion = 5;
+ else
+ mSOCKSProxyVersion = 4;
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("socks_remote_dns")))
+ proxy_GetBoolPref(prefBranch, PROXY_PREF("socks_remote_dns"),
+ mSOCKSProxyRemoteDNS);
+
+ if (!pref || !strcmp(pref, PROXY_PREF("proxy_over_tls"))) {
+ proxy_GetBoolPref(prefBranch, PROXY_PREF("proxy_over_tls"), mProxyOverTLS);
+ }
+
+ if (!pref || !strcmp(pref, PROXY_PREF("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 (!PL_strncasecmp(host_tail, hinfo->name.host, filter_host_len)) {
+ // If the tail of the host string matches the filter
+
+ if (filter_host_len > 0 && hinfo->name.host[0] == '.') {
+ // If the filter was of the form .foo.bar.tld, all such
+ // matches are correct
+ return false; // proxy disallowed
+ }
+
+ // abc-def.example.org should not match def.example.org
+ // however, *.def.example.org should match .def.example.org
+ // We check that the filter doesn't start with a `.`. If it does,
+ // then the strncasecmp above should suffice. If it doesn't,
+ // then we should only consider it a match if the strncasecmp happened
+ // at a subdomain boundary
+ if (host_len > filter_host_len && *(host_tail - 1) == '.') {
+ // If the host was something.foo.bar.tld and the filter
+ // was foo.bar.tld, it's still a match.
+ // the character right before the tail must be a
+ // `.` for this to work
+ return false; // proxy disallowed
+ }
+
+ if (host_len == filter_host_len) {
+ // If the host and filter are of the same length,
+ // they should match
+ return false; // proxy disallowed
+ }
+ }
+ }
+ }
+ }
+ return true;
+}
+
+// kProxyType\* may be referred to externally in
+// nsProxyInfo in order to compare by string pointer
+const char kProxyType_HTTP[] = "http";
+const char kProxyType_HTTPS[] = "https";
+const char kProxyType_PROXY[] = "proxy";
+const char kProxyType_SOCKS[] = "socks";
+const char kProxyType_SOCKS4[] = "socks4";
+const char kProxyType_SOCKS5[] = "socks5";
+const char kProxyType_DIRECT[] = "direct";
+
+const char* nsProtocolProxyService::ExtractProxyInfo(const char* start,
+ uint32_t aResolveFlags,
+ nsProxyInfo** result) {
+ *result = nullptr;
+ uint32_t flags = 0;
+
+ // see BNF in ProxyAutoConfig.h and notes in nsISystemProxySettings.idl
+
+ // find end of proxy info delimiter
+ const char* end = start;
+ while (*end && *end != ';') ++end;
+
+ // find end of proxy type delimiter
+ const char* sp = start;
+ while (sp < end && *sp != ' ' && *sp != '\t') ++sp;
+
+ uint32_t len = sp - start;
+ const char* type = nullptr;
+ switch (len) {
+ case 4:
+ if (PL_strncasecmp(start, kProxyType_HTTP, 4) == 0) {
+ type = kProxyType_HTTP;
+ }
+ break;
+ case 5:
+ if (PL_strncasecmp(start, kProxyType_PROXY, 5) == 0) {
+ type = kProxyType_HTTP;
+ } else if (PL_strncasecmp(start, kProxyType_SOCKS, 5) == 0) {
+ type = kProxyType_SOCKS4; // assume v4 for 4x compat
+ } else if (PL_strncasecmp(start, kProxyType_HTTPS, 5) == 0) {
+ type = kProxyType_HTTPS;
+ }
+ break;
+ case 6:
+ if (PL_strncasecmp(start, kProxyType_DIRECT, 6) == 0)
+ type = kProxyType_DIRECT;
+ else if (PL_strncasecmp(start, kProxyType_SOCKS4, 6) == 0)
+ type = kProxyType_SOCKS4;
+ else if (PL_strncasecmp(start, kProxyType_SOCKS5, 6) == 0)
+ // map "SOCKS5" to "socks" to match contract-id of registered
+ // SOCKS-v5 socket provider.
+ type = kProxyType_SOCKS;
+ break;
+ }
+ if (type) {
+ 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.Put(key, dsec);
+}
+
+bool nsProtocolProxyService::IsProxyDisabled(nsProxyInfo* pi) {
+ nsAutoCString key;
+ GetProxyKey(pi, key);
+
+ uint32_t val;
+ if (!mFailedProxies.Get(key, &val)) return false;
+
+ uint32_t dsec = SecondsSinceSessionStart();
+
+ // if time passed has exceeded interval, then try proxy again.
+ if (dsec > val) {
+ mFailedProxies.Remove(key);
+ return false;
+ }
+
+ return true;
+}
+
+nsresult nsProtocolProxyService::SetupPACThread(
+ 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"),
+ mStatus(NS_OK),
+ mCompleted(false) {}
+
+ 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() { mMutex.Lock(); }
+ void Unlock() { mMutex.Unlock(); }
+ void Wait() { mCondVar.Wait(TimeDuration::FromSeconds(3)); }
+
+ private:
+ ~nsAsyncBridgeRequest() = default;
+
+ friend class nsProtocolProxyService;
+
+ Mutex mMutex;
+ CondVar mCondVar;
+
+ nsresult mStatus;
+ nsCString mPACString;
+ nsCString mPACURL;
+ bool mCompleted;
+};
+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) {
+ // We only support failover when a PAC file is configured, either
+ // directly or via system settings
+ if (mProxyConfig != PROXYCONFIG_PAC && mProxyConfig != PROXYCONFIG_WPAD &&
+ mProxyConfig != PROXYCONFIG_SYSTEM)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Verify that |aProxy| is one of our nsProxyInfo objects.
+ nsCOMPtr<nsProxyInfo> pi = do_QueryInterface(aProxy);
+ NS_ENSURE_ARG(pi);
+ // OK, the QI checked out. We can proceed.
+
+ // Remember that this proxy is down.
+ DisableProxy(pi);
+
+ // NOTE: At this point, we might want to prompt the user if we have
+ // not already tried going DIRECT. This is something that the
+ // classic codebase supported; however, IE6 does not prompt.
+
+ if (!pi->mNext) return NS_ERROR_NOT_AVAILABLE;
+
+ LOG(("PAC failover from %s %s:%d to %s %s:%d\n", pi->mType, pi->mHost.get(),
+ pi->mPort, pi->mNext->mType, pi->mNext->mHost.get(), pi->mNext->mPort));
+
+ *aResult = do_AddRef(pi->mNext).take();
+ return NS_OK;
+}
+
+namespace { // anon
+
+class ProxyFilterPositionComparator {
+ typedef RefPtr<nsProtocolProxyService::FilterLink> FilterLinkRef;
+
+ 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 {
+ typedef RefPtr<nsProtocolProxyService::FilterLink> FilterLinkRef;
+
+ 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;
+ }
+
+ mFilters.AppendElement(link);
+ mFilters.Sort(ProxyFilterPositionComparator());
+ 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));
+
+ return mFilters.RemoveElement(givenObject, ProxyFilterObjectComparator())
+ ? NS_OK
+ : NS_ERROR_UNEXPECTED;
+}
+
+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) {
+ 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;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ios->GetProtocolHandler(info->scheme.get(), getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = handler->DoGetProtocolFlags(uri, &info->flags);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = handler->GetDefaultPort(&info->defaultPort);
+ return rv;
+}
+
+nsresult nsProtocolProxyService::NewProxyInfo_Internal(
+ const char* aType, const nsACString& aHost, int32_t aPort,
+ const nsACString& aUsername, const nsACString& aPassword,
+ 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 (!mFTPProxyHost.IsEmpty() && mFTPProxyPort > 0 &&
+ !(flags & RESOLVE_IGNORE_URI_SCHEME) &&
+ info.scheme.EqualsLiteral("ftp")) {
+ host = &mFTPProxyHost;
+ type = kProxyType_HTTP;
+ port = mFTPProxyPort;
+ } else if (!mSOCKSProxyTarget.IsEmpty() &&
+ (IsHostLocalTarget(mSOCKSProxyTarget) || mSOCKSProxyPort > 0)) {
+ host = &mSOCKSProxyTarget;
+ if (mSOCKSProxyVersion == 4)
+ type = kProxyType_SOCKS4;
+ else
+ type = kProxyType_SOCKS;
+ port = mSOCKSProxyPort;
+ if (mSOCKSProxyRemoteDNS)
+ proxyFlags |= nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST;
+ }
+
+ if (type) {
+ rv = NewProxyInfo_Internal(type, *host, port, ""_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;
+
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns) return;
+ nsCOMPtr<nsPIDNSService> pdns = do_QueryInterface(dns);
+ if (!pdns) return;
+
+ // We lose the prefetch optimization for the life of the dns service.
+ pdns->SetPrefetchEnabled(false);
+}
+
+void nsProtocolProxyService::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) {
+ 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();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsProtocolProxyService.h b/netwerk/base/nsProtocolProxyService.h
new file mode 100644
index 0000000000..6b748e45b8
--- /dev/null
+++ b/netwerk/base/nsProtocolProxyService.h
@@ -0,0 +1,414 @@
+/* -*- 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 "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "prio.h"
+#include "mozilla/Attributes.h"
+
+class nsIPrefBranch;
+class nsISystemProxySettings;
+
+namespace mozilla {
+namespace net {
+
+typedef nsDataHashtable<nsCStringHashKey, uint32_t> nsFailedProxyTable;
+
+class nsPACMan;
+class nsProxyInfo;
+struct nsProtocolInfo;
+
+// CID for the nsProtocolProxyService class
+// 091eedd8-8bae-4fe3-ad62-0c87351e640d
+#define NS_PROTOCOL_PROXY_SERVICE_IMPL_CID \
+ { \
+ 0x091eedd8, 0x8bae, 0x4fe3, { \
+ 0xad, 0x62, 0x0c, 0x87, 0x35, 0x1e, 0x64, 0x0d \
+ } \
+ }
+
+class nsProtocolProxyService final : public nsIProtocolProxyService2,
+ public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLPROXYSERVICE2
+ NS_DECL_NSIPROTOCOLPROXYSERVICE
+ NS_DECL_NSIOBSERVER
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROTOCOL_PROXY_SERVICE_IMPL_CID)
+
+ nsProtocolProxyService();
+
+ nsresult Init();
+
+ 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* prefs, const char* name);
+
+ /**
+ * This method is called to create a nsProxyInfo instance from the given
+ * PAC-style proxy string. It parses up to the end of the string, or to
+ * the next ';' character.
+ *
+ * @param proxy
+ * The PAC-style proxy string to parse. This must not be null.
+ * @param aResolveFlags
+ * The flags passed to Resolve or AsyncResolve that are stored in
+ * proxyInfo.
+ * @param result
+ * Upon return this points to a newly allocated nsProxyInfo or null
+ * if the proxy string was invalid.
+ *
+ * @return A pointer beyond the parsed proxy string (never null).
+ */
+ const char* ExtractProxyInfo(const char* proxy, uint32_t aResolveFlags,
+ nsProxyInfo** result);
+
+ /**
+ * Load the specified PAC file.
+ *
+ * @param pacURI
+ * The URI spec of the PAC file to load.
+ */
+ nsresult ConfigureFromPAC(const nsCString& pacURI, bool forceReload);
+
+ /**
+ * This method builds a list of nsProxyInfo objects from the given PAC-
+ * style string.
+ *
+ * @param pacString
+ * The PAC-style proxy string to parse. This may be empty.
+ * @param aResolveFlags
+ * The flags passed to Resolve or AsyncResolve that are stored in
+ * proxyInfo.
+ * @param result
+ * The resulting list of proxy info objects.
+ */
+ void ProcessPACString(const nsCString& pacString, uint32_t aResolveFlags,
+ nsIProxyInfo** result);
+
+ /**
+ * This method generates a string valued identifier for the given
+ * nsProxyInfo object.
+ *
+ * @param pi
+ * The nsProxyInfo object from which to generate the key.
+ * @param result
+ * Upon return, this parameter holds the generated key.
+ */
+ void GetProxyKey(nsProxyInfo* pi, nsCString& result);
+
+ /**
+ * @return Seconds since start of session.
+ */
+ uint32_t SecondsSinceSessionStart();
+
+ /**
+ * This method removes the specified proxy from the disabled list.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to enable.
+ */
+ void EnableProxy(nsProxyInfo* pi);
+
+ /**
+ * This method adds the specified proxy to the disabled list.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to disable.
+ */
+ void DisableProxy(nsProxyInfo* pi);
+
+ /**
+ * This method tests to see if the given proxy is disabled.
+ *
+ * @param pi
+ * The nsProxyInfo object identifying the proxy to test.
+ *
+ * @return True if the specified proxy is disabled.
+ */
+ bool IsProxyDisabled(nsProxyInfo* pi);
+
+ /**
+ * This method queries the protocol handler for the given scheme to check
+ * for the protocol flags and default port.
+ *
+ * @param uri
+ * The URI to query.
+ * @param info
+ * Holds information about the protocol upon return. Pass address
+ * of structure when you call this method. This parameter must not
+ * be null.
+ */
+ nsresult GetProtocolInfo(nsIURI* uri, nsProtocolInfo* result);
+
+ /**
+ * This method is an internal version nsIProtocolProxyService::newProxyInfo
+ * that expects a string literal for the type.
+ *
+ * @param type
+ * The proxy type.
+ * @param host
+ * The proxy host name (UTF-8 ok).
+ * @param port
+ * The proxy port number.
+ * @param username
+ * The username for the proxy (ASCII). May be "", but not null.
+ * @param password
+ * The password for the proxy (ASCII). May be "", but not null.
+ * @param flags
+ * The proxy flags (nsIProxyInfo::flags).
+ * @param timeout
+ * The failover timeout for this proxy.
+ * @param next
+ * The next proxy to try if this one fails.
+ * @param aResolveFlags
+ * The flags passed to resolve (from nsIProtocolProxyService).
+ * @param result
+ * The resulting nsIProxyInfo object.
+ */
+ nsresult NewProxyInfo_Internal(const char* type, const nsACString& host,
+ int32_t port, const nsACString& username,
+ const nsACString& password,
+ const nsACString& aProxyAuthorizationHeader,
+ const nsACString& aConnectionIsolationKey,
+ uint32_t flags, uint32_t timeout,
+ nsIProxyInfo* next, uint32_t aResolveFlags,
+ nsIProxyInfo** result);
+
+ /**
+ * This method is an internal version of Resolve that does not query PAC.
+ * It performs all of the built-in processing, and reports back to the
+ * caller with either the proxy info result or a flag to instruct the
+ * caller to use PAC instead.
+ *
+ * @param channel
+ * The channel to test.
+ * @param info
+ * Information about the URI's protocol.
+ * @param flags
+ * The flags passed to either the resolve or the asyncResolve method.
+ * @param usePAC
+ * If this flag is set upon return, then PAC should be queried to
+ * resolve the proxy info.
+ * @param result
+ * The resulting proxy info or null.
+ */
+ nsresult Resolve_Internal(nsIChannel* channel, const nsProtocolInfo& info,
+ uint32_t flags, bool* usePAC,
+ nsIProxyInfo** result);
+
+ /**
+ * 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> proxyInfo,
+ 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** proxyInfo);
+
+ /**
+ * 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& hostFilters);
+
+ /**
+ * This method checks the given URI against mHostFiltersArray.
+ *
+ * @param uri
+ * The URI to test.
+ * @param defaultPort
+ * The default port for the given URI.
+ *
+ * @return True if the URI can use the specified proxy.
+ */
+ bool CanUseProxy(nsIURI* uri, int32_t defaultPort);
+
+ /**
+ * Disable Prefetch in the DNS service if a proxy is in use.
+ *
+ * @param aProxy
+ * The proxy information
+ */
+ void MaybeDisableDNSPrefetch(nsIProxyInfo* aProxy);
+
+ private:
+ nsresult SetupPACThread(
+ 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;
+ int32_t port;
+ union {
+ HostInfoIP ip;
+ HostInfoName name;
+ };
+
+ HostInfo()
+ : is_ipaddr(false),
+ port(0) { /* other members intentionally uninitialized */
+ }
+ ~HostInfo() {
+ if (!is_ipaddr && name.host) free(name.host);
+ }
+ };
+
+ 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;
+
+ // Holds an array of HostInfo objects
+ nsTArray<UniquePtr<HostInfo>> mHostFiltersArray;
+
+ // Filters, always sorted by the position.
+ nsTArray<RefPtr<FilterLink>> mFilters;
+
+ uint32_t mProxyConfig;
+
+ nsCString mHTTPProxyHost;
+ int32_t mHTTPProxyPort;
+
+ nsCString mFTPProxyHost;
+ int32_t mFTPProxyPort;
+
+ nsCString mHTTPSProxyHost;
+ int32_t mHTTPSProxyPort;
+
+ // mSOCKSProxyTarget could be a host, a domain socket path,
+ // or a named-pipe name.
+ nsCString mSOCKSProxyTarget;
+ int32_t mSOCKSProxyPort;
+ int32_t mSOCKSProxyVersion;
+ bool mSOCKSProxyRemoteDNS;
+ bool mProxyOverTLS;
+ bool mWPADOverDHCPEnabled;
+
+ RefPtr<nsPACMan> mPACMan; // non-null if we are using PAC
+ nsCOMPtr<nsISystemProxySettings> mSystemProxySettings;
+
+ PRTime mSessionStart;
+ nsFailedProxyTable mFailedProxies;
+ int32_t mFailedProxyTimeout;
+
+ private:
+ nsresult AsyncResolveInternal(nsIChannel* channel, uint32_t flags,
+ nsIProtocolProxyCallback* callback,
+ nsICancelable** result, bool isSyncOK,
+ nsISerialEventTarget* mainThreadEventTarget);
+ bool mIsShutdown;
+ nsCOMPtr<nsISerialEventTarget> mProxySettingTarget;
+};
+
+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..8a72a91c42
--- /dev/null
+++ b/netwerk/base/nsProxyInfo.cpp
@@ -0,0 +1,196 @@
+/* -*- 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[];
+
+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::SetResolveFlags(uint32_t flags) {
+ mResolveFlags = flags;
+ 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;
+}
+
+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;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsProxyInfo.h b/netwerk/base/nsProxyInfo.h
new file mode 100644
index 0000000000..798f615707
--- /dev/null
+++ b/netwerk/base/nsProxyInfo.h
@@ -0,0 +1,102 @@
+/* -*- 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);
+
+ private:
+ friend class nsProtocolProxyService;
+
+ explicit nsProxyInfo(const char* type = nullptr)
+ : mType(type),
+ mPort(-1),
+ mFlags(0),
+ mResolveFlags(0),
+ mTimeout(UINT32_MAX),
+ mNext(nullptr) {}
+
+ 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;
+ int32_t mPort;
+ uint32_t mFlags;
+ // We need to read on multiple threads, but don't need to sync on anything
+ // else
+ Atomic<uint32_t, Relaxed> mResolveFlags;
+ uint32_t mTimeout;
+ nsProxyInfo* mNext;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsProxyInfo, NS_PROXYINFO_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsProxyInfo_h__
diff --git a/netwerk/base/nsReadLine.h b/netwerk/base/nsReadLine.h
new file mode 100644
index 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..527bb1597f
--- /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) {
+ NS_IF_ADDREF(*referrer = mReferrer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRedirectHistoryEntry::GetPrincipal(nsIPrincipal** principal) {
+ NS_IF_ADDREF(*principal = mPrincipal);
+ 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..d0b79d5094
--- /dev/null
+++ b/netwerk/base/nsRedirectHistoryEntry.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/. */
+
+#ifndef nsRedirectHistoryEntry_h__
+#define nsRedirectHistoryEntry_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..2f2e688a69
--- /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(GetMainThreadEventTarget());
+ 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..e0ac43ebd9
--- /dev/null
+++ b/netwerk/base/nsServerSocket.cpp
@@ -0,0 +1,550 @@
+/* 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 "nsServiceManagerUtils.h"
+#include "nsIFile.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+typedef void (nsServerSocket::*nsServerSocketFunc)(void);
+
+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()
+ : mFD(nullptr),
+ mLock("nsServerSocket.mLock"),
+ mAttached(false),
+ mKeepWhenOffline(false) {
+ this->mAddr.raw.family = 0;
+ this->mAddr.inet.family = 0;
+ this->mAddr.inet.port = 0;
+ this->mAddr.inet.ip = 0;
+ this->mAddr.ipv6.family = 0;
+ this->mAddr.ipv6.port = 0;
+ this->mAddr.ipv6.flowinfo = 0;
+ this->mAddr.ipv6.scope_id = 0;
+ this->mAddr.local.family = 0;
+ // 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::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) {
+ 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());
+ }
+
+ 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(GetCurrentEventTarget()) {}
+
+ 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 = GetCurrentEventTarget();
+ }
+
+ // 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..3690a99a9d
--- /dev/null
+++ b/netwerk/base/nsServerSocket.h
@@ -0,0 +1,66 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsServerSocket_h__
+#define nsServerSocket_h__
+
+#include "prio.h"
+#include "nsASocketHandler.h"
+#include "nsIServerSocket.h"
+#include "mozilla/Mutex.h"
+
+//-----------------------------------------------------------------------------
+
+class nsIEventTarget;
+namespace mozilla {
+namespace net {
+union NetAddr;
+
+class nsServerSocket : public nsASocketHandler, public nsIServerSocket {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISERVERSOCKET
+
+ // nsASocketHandler methods:
+ virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override;
+ virtual void OnSocketDetached(PRFileDesc* fd) override;
+ virtual void IsLocal(bool* aIsLocal) override;
+ virtual void KeepWhenOffline(bool* aKeepWhenOffline) override;
+
+ virtual uint64_t ByteCountSent() override { return 0; }
+ virtual uint64_t ByteCountReceived() override { return 0; }
+ nsServerSocket();
+
+ virtual void CreateClientTransport(PRFileDesc* clientFD,
+ const mozilla::net::NetAddr& clientAddr);
+ virtual nsresult SetSocketDefaults() { return NS_OK; }
+ virtual nsresult OnSocketListen() { return NS_OK; }
+
+ protected:
+ virtual ~nsServerSocket();
+ PRFileDesc* mFD;
+ nsCOMPtr<nsIServerSocketListener> mListener;
+
+ private:
+ void OnMsgClose();
+ void OnMsgAttach();
+
+ // try attaching our socket (mFD) to the STS's poll list.
+ nsresult TryAttach();
+
+ // lock protects access to mListener; so it is not cleared while being used.
+ mozilla::Mutex mLock;
+ PRNetAddr mAddr;
+ nsCOMPtr<nsIEventTarget> mListenerTarget;
+ bool mAttached;
+ bool mKeepWhenOffline;
+};
+
+} // namespace net
+} // namespace mozilla
+
+//-----------------------------------------------------------------------------
+
+#endif // nsServerSocket_h__
diff --git a/netwerk/base/nsSimpleNestedURI.cpp b/netwerk/base/nsSimpleNestedURI.cpp
new file mode 100644
index 0000000000..6427009c84
--- /dev/null
+++ b/netwerk/base/nsSimpleNestedURI.cpp
@@ -0,0 +1,227 @@
+/* -*- 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 "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..5361de2e93
--- /dev/null
+++ b/netwerk/base/nsSimpleURI.cpp
@@ -0,0 +1,744 @@
+/* -*- 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 "IPCMessageUtils.h"
+
+#include "nsSimpleURI.h"
+#include "nscore.h"
+#include "nsString.h"
+#include "plstr.h"
+#include "nsURLHelper.h"
+#include "nsNetCID.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsEscape.h"
+#include "nsError.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIURIMutator.h"
+#include "mozilla/net/MozURL.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:
+
+nsSimpleURI::nsSimpleURI() : mIsRefValid(false), mIsQueryValid(false) {}
+
+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;
+}
+
+nsresult nsSimpleURI::SetSpecInternal(const nsACString& aSpec) {
+ nsresult rv = net_ExtractURLScheme(aSpec, mScheme);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString spec;
+ rv = net_FilterAndEscapeURI(aSpec, esc_OnlyNonASCII, 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 SetPathQueryRefEscaped(Substring(spec, colonPos + 1),
+ /* aNeedsEscape = */ false);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetScheme(nsACString& result) {
+ result = mScheme;
+ return NS_OK;
+}
+
+nsresult nsSimpleURI::SetScheme(const nsACString& scheme) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(scheme);
+ if (!net_IsValidScheme(flat)) {
+ NS_WARNING("the given url scheme contains invalid characters");
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ mScheme = scheme;
+ ToLowerCase(mScheme);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetPrePath(nsACString& result) {
+ result = mScheme + ":"_ns;
+ 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& result) {
+ 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) {
+ return SetPathQueryRefEscaped(aPath, true);
+}
+nsresult nsSimpleURI::SetPathQueryRefEscaped(const nsACString& aPath,
+ bool aNeedsEscape) {
+ nsresult rv;
+ nsAutoCString path;
+ if (aNeedsEscape) {
+ rv = NS_EscapeURL(aPath, esc_OnlyNonASCII, path, fallible);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ if (!path.Assign(aPath, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ int32_t queryPos = path.FindChar('?');
+ int32_t hashPos = path.FindChar('#');
+
+ if (queryPos != kNotFound && hashPos != kNotFound && hashPos < queryPos) {
+ queryPos = kNotFound;
+ }
+
+ nsAutoCString query;
+ if (queryPos != kNotFound) {
+ query.Assign(Substring(path, queryPos));
+ path.Truncate(queryPos);
+ }
+
+ nsAutoCString hash;
+ if (hashPos != kNotFound) {
+ if (query.IsEmpty()) {
+ hash.Assign(Substring(path, hashPos));
+ path.Truncate(hashPos);
+ } else {
+ // We have to search the hash character in the query
+ hashPos = query.FindChar('#');
+ hash.Assign(Substring(query, hashPos));
+ query.Truncate(hashPos);
+ }
+ }
+
+ mIsQueryValid = false;
+ mQuery.Truncate();
+
+ mIsRefValid = false;
+ mRef.Truncate();
+
+ // The path
+ if (!mPath.Assign(path, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = SetQuery(query);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return SetRef(hash);
+}
+
+NS_IMETHODIMP
+nsSimpleURI::GetRef(nsACString& result) {
+ if (!mIsRefValid) {
+ MOZ_ASSERT(mRef.IsEmpty(), "mIsRefValid/mRef invariant broken");
+ result.Truncate();
+ } else {
+ result = mRef;
+ }
+
+ return NS_OK;
+}
+
+// NOTE: SetRef("") removes our ref, whereas SetRef("#") sets it to the empty
+// string (and will result in .spec and .path having a terminal #).
+nsresult nsSimpleURI::SetRef(const nsACString& aRef) {
+ 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
+ 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 = PL_strcasecmp(this_scheme, i_Scheme) ? false : true;
+ } else {
+ *o_Equals = false;
+ }
+
+ return NS_OK;
+}
+
+/* virtual */ nsSimpleURI* nsSimpleURI::StartClone(
+ nsSimpleURI::RefHandlingEnum refHandlingMode, const nsACString& newRef) {
+ nsSimpleURI* url = new nsSimpleURI();
+ SetRefOnClone(url, refHandlingMode, newRef);
+ return url;
+}
+
+/* virtual */
+void nsSimpleURI::SetRefOnClone(nsSimpleURI* url,
+ nsSimpleURI::RefHandlingEnum refHandlingMode,
+ const nsACString& newRef) {
+ if (refHandlingMode == eHonorRef) {
+ url->mRef = mRef;
+ url->mIsRefValid = mIsRefValid;
+ } else if (refHandlingMode == eReplaceRef) {
+ url->SetRef(newRef);
+ }
+}
+
+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;
+}
+
+nsresult nsSimpleURI::SetQuery(const nsACString& aQuery) {
+ nsAutoCString query;
+ nsresult rv = NS_EscapeURL(aQuery, esc_OnlyNonASCII, query, fallible);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (query.IsEmpty()) {
+ // Empty string means to remove query completely.
+ mIsQueryValid = false;
+ mQuery.Truncate(); // invariant: mQuery should be empty when it's not valid
+ return NS_OK;
+ }
+
+ mIsQueryValid = true;
+
+ // Gracefully skip initial question mark
+ if (query[0] == '?') {
+ mQuery = Substring(query, 1);
+ } else {
+ mQuery = query;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSimpleURI::SetQueryWithEncoding(const nsACString& aQuery,
+ const Encoding* aEncoding) {
+ return SetQuery(aQuery);
+}
+
+// Queries this list of interfaces. If none match, it queries mURI.
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsSimpleURI::Mutator, nsIURISetters,
+ nsIURIMutator, nsISerializable)
+
+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..4b32075927
--- /dev/null
+++ b/netwerk/base/nsSimpleURI.h
@@ -0,0 +1,143 @@
+/* -*- 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"
+
+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();
+ 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** aURI);
+ virtual nsresult SetSpecInternal(const nsACString& input);
+ 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& 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);
+ 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** clone);
+
+ nsresult SetPathQueryRefEscaped(const nsACString& aPath, bool aNeedsEscape);
+
+ 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 mMutable;
+ bool mIsRefValid; // To distinguish between empty-ref and no-ref.
+ bool mIsQueryValid; // To distinguish between empty-query and no-query.
+
+ public:
+ class Mutator final : public nsIURIMutator,
+ public BaseURIMutator<nsSimpleURI>,
+ 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);
+ }
+
+ 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..e029c8e6bd
--- /dev/null
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -0,0 +1,3610 @@
+/* -*- 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 "nsSocketTransport2.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/Telemetry.h"
+#include "nsIOService.h"
+#include "nsStreamUtils.h"
+#include "nsNetSegmentUtils.h"
+#include "nsNetAddr.h"
+#include "nsTransportUtils.h"
+#include "nsProxyInfo.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsCOMPtr.h"
+#include "plstr.h"
+#include "prerr.h"
+#include "IOActivityMonitor.h"
+#include "NSSErrorsService.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/net/NeckoChild.h"
+#include "nsThreadUtils.h"
+#include "nsSocketProviderService.h"
+#include "nsISocketProvider.h"
+#include "nsISSLSocketControl.h"
+#include "nsIPipe.h"
+#include "nsIClassInfoImpl.h"
+#include "nsURLHelper.h"
+#include "nsIDNSService.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSByTypeRecord.h"
+#include "nsICancelable.h"
+#include "NetworkDataCountLayer.h"
+#include "QuicSocketControl.h"
+#include "TCPFastOpenLayer.h"
+#include <algorithm>
+#include "sslexp.h"
+#include "mozilla/net/SSLTokensCache.h"
+#include "mozilla/StaticPrefs_network.h"
+
+#include "nsPrintfCString.h"
+#include "xpcpublic.h"
+
+#if defined(FUZZING)
+# include "FuzzyLayer.h"
+# include "FuzzySecurityInfo.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)
+ : Runnable("net::nsSocketEvent"),
+ mTransport(transport),
+ mType(type),
+ mStatus(status),
+ mParam(param) {}
+
+ NS_IMETHOD Run() override {
+ mTransport->OnSocketEvent(mType, mStatus, mParam);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsSocketTransport> mTransport;
+
+ uint32_t mType;
+ nsresult mStatus;
+ nsCOMPtr<nsISupports> mParam;
+};
+
+//-----------------------------------------------------------------------------
+
+//#define TEST_CONNECT_ERRORS
+#ifdef TEST_CONNECT_ERRORS
+# include <stdlib.h>
+static PRErrorCode RandomizeConnectError(PRErrorCode code) {
+ //
+ // To test out these errors, load http://www.yahoo.com/. It should load
+ // correctly despite the random occurrence of these errors.
+ //
+ int n = rand();
+ if (n > RAND_MAX / 2) {
+ struct {
+ PRErrorCode err_code;
+ const char* err_name;
+ } errors[] = {
+ //
+ // These errors should be recoverable provided there is another
+ // IP address in mDNSRecord.
+ //
+ {PR_CONNECT_REFUSED_ERROR, "PR_CONNECT_REFUSED_ERROR"},
+ {PR_CONNECT_TIMEOUT_ERROR, "PR_CONNECT_TIMEOUT_ERROR"},
+ //
+ // This error will cause this socket transport to error out;
+ // however, if the consumer is HTTP, then the HTTP transaction
+ // should be restarted when this error occurs.
+ //
+ {PR_CONNECT_RESET_ERROR, "PR_CONNECT_RESET_ERROR"},
+ };
+ n = n % (sizeof(errors) / sizeof(errors[0]));
+ code = errors[n].err_code;
+ SOCKET_LOG(("simulating NSPR error %d [%s]\n", code, errors[n].err_name));
+ }
+ return code;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+
+nsresult ErrorAccordingToNSPR(PRErrorCode errorCode) {
+ nsresult rv = NS_ERROR_FAILURE;
+ switch (errorCode) {
+ case PR_WOULD_BLOCK_ERROR:
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ break;
+ case PR_CONNECT_ABORTED_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ rv = NS_ERROR_NET_RESET;
+ break;
+ case PR_END_OF_FILE_ERROR: // XXX document this correlation
+ rv = NS_ERROR_NET_INTERRUPT;
+ break;
+ case PR_CONNECT_REFUSED_ERROR:
+ // We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We
+ // could get better diagnostics by adding distinct XPCOM error codes for
+ // each of these, but there are a lot of places in Gecko that check
+ // specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to
+ // be checked.
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_HOST_UNREACHABLE_ERROR:
+ case PR_ADDRESS_NOT_AVAILABLE_ERROR:
+ // Treat EACCES as a soft error since (at least on Linux) connect() returns
+ // EACCES when an IPv6 connection is blocked by a firewall. See bug 270784.
+ case PR_NO_ACCESS_RIGHTS_ERROR:
+ rv = NS_ERROR_CONNECTION_REFUSED;
+ break;
+ case PR_ADDRESS_NOT_SUPPORTED_ERROR:
+ rv = NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
+ break;
+ case PR_IO_TIMEOUT_ERROR:
+ case PR_CONNECT_TIMEOUT_ERROR:
+ rv = NS_ERROR_NET_TIMEOUT;
+ break;
+ case PR_OUT_OF_MEMORY_ERROR:
+ // These really indicate that the descriptor table filled up, or that the
+ // kernel ran out of network buffers - but nobody really cares which part of
+ // the system ran out of memory.
+ case PR_PROC_DESC_TABLE_FULL_ERROR:
+ case PR_SYS_DESC_TABLE_FULL_ERROR:
+ case PR_INSUFFICIENT_RESOURCES_ERROR:
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ case PR_ADDRESS_IN_USE_ERROR:
+ rv = NS_ERROR_SOCKET_ADDRESS_IN_USE;
+ break;
+ // These filename-related errors can arise when using Unix-domain sockets.
+ case PR_FILE_NOT_FOUND_ERROR:
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ break;
+ case PR_IS_DIRECTORY_ERROR:
+ rv = NS_ERROR_FILE_IS_DIRECTORY;
+ break;
+ case PR_LOOP_ERROR:
+ rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
+ break;
+ case PR_NAME_TOO_LONG_ERROR:
+ rv = NS_ERROR_FILE_NAME_TOO_LONG;
+ break;
+ case PR_NO_DEVICE_SPACE_ERROR:
+ rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
+ break;
+ case PR_NOT_DIRECTORY_ERROR:
+ rv = NS_ERROR_FILE_NOT_DIRECTORY;
+ break;
+ case PR_READ_ONLY_FILESYSTEM_ERROR:
+ rv = NS_ERROR_FILE_READ_ONLY;
+ break;
+ 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),
+ mReaderRefCnt(0),
+ mCondition(NS_OK),
+ mCallbackFlags(0),
+ mByteCount(0) {}
+
+// 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::Read(char* buf, uint32_t count, uint32_t* countRead) {
+ SOCKET_LOG(("nsSocketInputStream::Read [this=%p count=%u]\n", this, count));
+
+ *countRead = 0;
+
+ PRFileDesc* fd = nullptr;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition))
+ return (mCondition == NS_BASE_STREAM_CLOSED) ? NS_OK : mCondition;
+
+ fd = mTransport->GetFD_Locked();
+ if (!fd) return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ SOCKET_LOG((" calling PR_Read [count=%u]\n", count));
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Read(fd, buf, count);
+
+ SOCKET_LOG((" PR_Read returned [n=%d]\n", n));
+
+ nsresult rv = NS_OK;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+#ifdef ENABLE_SOCKET_TRACING
+ if (n > 0) mTransport->TraceInBuf(buf, n);
+#endif
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n > 0)
+ mByteCount += (*countRead = n);
+ else if (n < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) return NS_BASE_STREAM_WOULD_BLOCK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
+
+ // only send this notification if we have indeed read some data.
+ // see bug 196827 for an example of why this is important.
+ if (n > 0) mTransport->SendStatus(NS_NET_STATUS_RECEIVING_FROM);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* countRead) {
+ // socket stream is unbuffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::IsNonBlocking(bool* nonblocking) {
+ *nonblocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketInputStream::CloseWithStatus(nsresult reason) {
+ SOCKET_LOG(("nsSocketInputStream::CloseWithStatus [this=%p reason=%" 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),
+ mWriterRefCnt(0),
+ mCondition(NS_OK),
+ mCallbackFlags(0),
+ mByteCount(0) {}
+
+// 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::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;
+ bool fastOpenInProgress;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+ if (NS_FAILED(mCondition)) return mCondition;
+
+ fd = mTransport->GetFD_LockedAlsoDuringFastOpen();
+ if (!fd) return NS_BASE_STREAM_WOULD_BLOCK;
+
+ fastOpenInProgress = mTransport->FastOpenInProgress();
+ }
+
+ if (fastOpenInProgress) {
+ // If we are in the fast open phase, we should not write more data
+ // than TCPFastOpenLayer can accept. If we write more data, this data
+ // will be buffered in tls and we want to avoid that.
+ uint32_t availableSpace = TCPFastOpenGetBufferSizeLeft(fd);
+ count = (count > availableSpace) ? availableSpace : count;
+ if (!count) {
+ {
+ MutexAutoLock lock(mTransport->mLock);
+ mTransport->ReleaseFD_Locked(fd);
+ }
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ }
+
+ SOCKET_LOG((" calling PR_Write [count=%u]\n", count));
+
+ // cannot hold lock while calling NSPR. (worried about the fact that PSM
+ // synchronously proxies notifications over to the UI thread, which could
+ // mistakenly try to re-enter this code.)
+ int32_t n = PR_Write(fd, buf, count);
+
+ SOCKET_LOG((" PR_Write returned [n=%d]\n", n));
+
+ nsresult rv = NS_OK;
+ {
+ MutexAutoLock lock(mTransport->mLock);
+
+#ifdef ENABLE_SOCKET_TRACING
+ if (n > 0) mTransport->TraceOutBuf(buf, n);
+#endif
+
+ mTransport->ReleaseFD_Locked(fd);
+
+ if (n > 0)
+ mByteCount += (*countWritten = n);
+ else if (n < 0) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) return NS_BASE_STREAM_WOULD_BLOCK;
+ mCondition = ErrorAccordingToNSPR(code);
+ }
+ rv = mCondition;
+ }
+ if (NS_FAILED(rv)) mTransport->OnOutputClosed(rv);
+
+ // only send this notification if we have indeed written some data.
+ // see bug 196827 for an example of why this is important.
+ // During a fast open we are actually not sending data, the data will be
+ // only buffered in the TCPFastOpenLayer. Therefore we will call
+ // SendStatus(NS_NET_STATUS_SENDING_TO) when we really send data (i.e. when
+ // TCPFastOpenFinish is called.
+ if ((n > 0) && !fastOpenInProgress) {
+ 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()
+ : mPort(0),
+ mProxyPort(0),
+ mOriginPort(0),
+ mProxyTransparent(false),
+ mProxyTransparentResolvesHost(false),
+ mHttpsProxy(false),
+ mConnectionFlags(0),
+ mResetFamilyPreference(false),
+ mTlsFlags(0),
+ mReuseAddrPort(false),
+ mState(STATE_CLOSED),
+ mAttached(false),
+ mInputClosed(true),
+ mOutputClosed(true),
+ mResolving(false),
+ mEchConfigUsed(false),
+ mResolvedByTRR(false),
+ mNetAddrIsSet(false),
+ mSelfAddrIsSet(false),
+ mLock("nsSocketTransport.mLock"),
+ mFD(this),
+ mFDref(0),
+ mFDconnected(false),
+ mFDFastOpenInProgress(false),
+ mSocketTransportService(gSocketTransportService),
+ mInput(this),
+ mOutput(this),
+ mLingerPolarity(false),
+ mLingerTimeout(0),
+ mQoSBits(0x00),
+ mKeepaliveEnabled(false),
+ mKeepaliveIdleTimeS(-1),
+ mKeepaliveRetryIntervalS(-1),
+ mKeepaliveProbeCount(-1),
+ mFastOpenCallback(nullptr),
+ mFastOpenLayerHasBufferedData(false),
+ mFastOpenStatus(TFO_NOT_SET),
+ mFirstRetryError(NS_OK),
+ mDoNotRetryToConnect(false),
+ mUsingQuic(false) {
+ this->mNetAddr.raw.family = 0;
+ this->mNetAddr.inet = {};
+ this->mSelfAddr.raw.family = 0;
+ this->mSelfAddr.inet = {};
+ SOCKET_LOG(("creating nsSocketTransport @%p\n", this));
+
+ mTimeouts[TIMEOUT_CONNECT] = UINT16_MAX; // no timeout
+ mTimeouts[TIMEOUT_READ_WRITE] = UINT16_MAX; // no timeout
+}
+
+nsSocketTransport::~nsSocketTransport() {
+ SOCKET_LOG(("destroying nsSocketTransport @%p\n", this));
+}
+
+nsresult nsSocketTransport::Init(const nsTArray<nsCString>& types,
+ const nsACString& host, uint16_t port,
+ const nsACString& hostRoute,
+ uint16_t portRoute,
+ nsIProxyInfo* givenProxyInfo) {
+ nsCOMPtr<nsProxyInfo> proxyInfo;
+ if (givenProxyInfo) {
+ proxyInfo = do_QueryInterface(givenProxyInfo);
+ NS_ENSURE_ARG(proxyInfo);
+ }
+
+ // init socket type info
+
+ mOriginHost = host;
+ mOriginPort = port;
+ if (!hostRoute.IsEmpty()) {
+ mHost = hostRoute;
+ mPort = portRoute;
+ } else {
+ mHost = host;
+ mPort = port;
+ }
+
+ // 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++]);
+
+ // quic does not have a socketProvider.
+ if (!mTypes[i].EqualsLiteral("quic")) {
+ 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");
+ NS_ASSERTION(!mFD.IsInitialized(), "already initialized");
+
+ 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);
+
+ 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,
+ nsISupports* aSecInfo) {
+ mSecInfo = aSecInfo;
+ return InitWithConnectedSocket(aFD, aAddr);
+}
+
+nsresult nsSocketTransport::PostEvent(uint32_t type, nsresult status,
+ nsISupports* param) {
+ SOCKET_LOG(("nsSocketTransport::PostEvent [this=%p type=%u status=%" PRIx32
+ " param=%p]\n",
+ this, type, static_cast<uint32_t>(status), param));
+
+ nsCOMPtr<nsIRunnable> event = new nsSocketEvent(this, type, status, param);
+ if (!event) return NS_ERROR_OUT_OF_MEMORY;
+
+ return mSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+void nsSocketTransport::SendStatus(nsresult status) {
+ SOCKET_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();
+ // If Fast Open is used, we buffer some data in TCPFastOpenLayer,
+ // This data can be only tls data or application data as well.
+ // socketTransport should send status only if it really has sent
+ // application data. socketTransport cannot query transaction for
+ // that info but it can know if transaction has send data if
+ // mOutput.ByteCount() is > 0.
+ if (progress == 0) {
+ return;
+ }
+ break;
+ case NS_NET_STATUS_RECEIVING_FROM:
+ progress = mInput.ByteCount();
+ break;
+ default:
+ progress = 0;
+ break;
+ }
+ }
+ if (sink) {
+ sink->OnTransportStatus(this, status, progress, -1);
+ }
+}
+
+nsresult nsSocketTransport::ResolveHost() {
+ SOCKET_LOG((
+ "nsSocketTransport::ResolveHost [this=%p %s:%d%s] "
+ "mProxyTransparentResolvesHost=%d\n",
+ this, SocketHost().get(), SocketPort(),
+ mConnectionFlags & nsSocketTransport::BYPASS_CACHE ? " bypass cache" : "",
+ mProxyTransparentResolvesHost));
+
+ 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);
+ }
+ }
+
+ 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,
+ new SyncRunnable(NS_NewRunnableFunction(
+ "nsSocketTransport::ResolveHost->GetDNSService", initTask)));
+ } else {
+ initTask();
+ }
+ if (!dns) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mResolving = true;
+
+ uint32_t dnsFlags = 0;
+ 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;
+
+ // 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;
+
+ if (mTypes[0].EqualsLiteral("quic")) {
+ fd = PR_OpenUDPSocket(mNetAddr.raw.family);
+ if (!fd) {
+ SOCKET_LOG((" error creating UDP nspr socket [rv=%" PRIx32 "]\n",
+ static_cast<uint32_t>(rv)));
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mUsingQuic = true;
+ // Create security control and info object for quic.
+ RefPtr<QuicSocketControl> quicCtrl = new QuicSocketControl(controlFlags);
+ quicCtrl->SetHostName(mHttpsProxy ? mProxyHost.get() : host);
+ quicCtrl->SetPort(mHttpsProxy ? mProxyPort : port);
+ nsCOMPtr<nsISupports> secinfo;
+ quicCtrl->QueryInterface(NS_GET_IID(nsISupports), (void**)(&secinfo));
+
+ // remember security info and give notification callbacks to PSM...
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ {
+ MutexAutoLock lock(mLock);
+ mSecInfo = secinfo;
+ callbacks = mCallbacks;
+ SOCKET_LOG(
+ (" [secinfo=%p callbacks=%p]\n", mSecInfo.get(), mCallbacks.get()));
+ }
+ // don't call into PSM while holding mLock!!
+ quicCtrl->SetNotificationCallbacks(callbacks);
+
+ return NS_OK;
+ }
+
+ 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<nsISupports> secinfo;
+ 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(secinfo));
+
+ 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(secinfo));
+ }
+
+ // controlFlags = 0; not used below this point...
+ if (NS_FAILED(rv)) break;
+
+ // if the service was ssl or starttls, we want to hold onto the socket
+ // info
+ bool isSSL = mTypes[i].EqualsLiteral("ssl");
+ if (isSSL || mTypes[i].EqualsLiteral("starttls")) {
+ // remember security info and give notification callbacks to PSM...
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ {
+ MutexAutoLock lock(mLock);
+ mSecInfo = secinfo;
+ callbacks = mCallbacks;
+ SOCKET_LOG((" [secinfo=%p callbacks=%p]\n", mSecInfo.get(),
+ mCallbacks.get()));
+ }
+ // don't call into PSM while holding mLock!!
+ nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(secinfo));
+ if (secCtrl) secCtrl->SetNotificationCallbacks(callbacks);
+ // remember if socket type is SSL so we can ProxyStartSSL if need be.
+ usingSSL = isSSL;
+ } else if (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));
+
+ nsresult rv;
+ bool isLocal;
+ IsLocal(&isLocal);
+
+ if (gIOService->IsNetTearingDown()) {
+ return NS_ERROR_ABORT;
+ }
+ if (gIOService->IsOffline()) {
+ if (!isLocal) return NS_ERROR_OFFLINE;
+ } else if (!isLocal) {
+#ifdef DEBUG
+ // all IP networking has to be done from the parent
+ if (NS_SUCCEEDED(mCondition) && ((mNetAddr.raw.family == AF_INET) ||
+ (mNetAddr.raw.family == AF_INET6))) {
+ MOZ_ASSERT(!IsNeckoChild());
+ }
+#endif
+
+ if (NS_SUCCEEDED(mCondition) && xpc::AreNonLocalConnectionsDisabled() &&
+ !(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());
+ MOZ_CRASH("Attempting to connect to non-local address!");
+ }
+ }
+
+ // Hosts/Proxy Hosts that are Local IP Literals should not be speculatively
+ // connected - Bug 853423.
+ if (mConnectionFlags & nsISocketTransport::DISABLE_RFC1918 &&
+ 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.
+ //
+ 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) {
+ mSecInfo = static_cast<nsISupports*>(
+ static_cast<nsISSLSocketControl*>(new FuzzySecurityInfo()));
+ }
+ }
+#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 (mUsingQuic) {
+ opt.option = PR_SockOpt_RecvBufferSize;
+ opt.value.recv_buffer_size =
+ StaticPrefs::network_http_http3_recvBufferSize();
+ status = PR_SetSocketOption(fd, &opt);
+ if (status != PR_SUCCESS) {
+ SOCKET_LOG((" Couldn't set recv buffer size"));
+ }
+ }
+
+ if (!mUsingQuic) {
+ 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
+ }
+
+ // inform socket transport about this newly created socket...
+ rv = mSocketTransportService->AttachSocket(fd, this);
+ if (NS_FAILED(rv)) {
+ CloseSocket(fd,
+ mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
+ return rv;
+ }
+ mAttached = true;
+
+ // assign mFD so that we can properly handle OnSocketDetached before we've
+ // established a connection.
+ {
+ MutexAutoLock lock(mLock);
+ mFD = fd;
+ mFDref = 1;
+ mFDconnected = false;
+ 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
+
+ nsCOMPtr<nsISSLSocketControl> secCtrl = do_QueryInterface(mSecInfo);
+ if (secCtrl) {
+ if (!mEchConfig.IsEmpty() &&
+ !(mConnectionFlags & (DONT_TRY_ECH | BE_CONSERVATIVE))) {
+ SOCKET_LOG(("nsSocketTransport::InitiateSocket set echconfig."));
+ rv = secCtrl->SetEchConfig(mEchConfig);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mEchConfigUsed = true;
+ }
+ }
+
+ if (mUsingQuic) {
+ //
+ // we pretend that we are connected!
+ //
+ if (PR_Connect(fd, &prAddr, NS_SOCKET_CONNECT_TIMEOUT) == PR_SUCCESS) {
+ OnSocketConnected();
+ return NS_OK;
+ }
+ PRErrorCode code = PR_GetError();
+ return ErrorAccordingToNSPR(code);
+ }
+
+ // 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();
+ }
+
+ bool tfo = false;
+ if (!mProxyTransparent && mFastOpenCallback &&
+ mFastOpenCallback->FastOpenEnabled()) {
+ if (NS_SUCCEEDED(AttachTCPFastOpenIOLayer(fd))) {
+ tfo = true;
+ SOCKET_LOG(
+ ("nsSocketTransport::InitiateSocket TCP Fast Open "
+ "started [this=%p]\n",
+ this));
+ }
+ }
+
+ 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 ((status == PR_SUCCESS) && tfo) {
+ {
+ MutexAutoLock lock(mLock);
+ mFDFastOpenInProgress = true;
+ }
+ SOCKET_LOG(("Using TCP Fast Open."));
+ rv = mFastOpenCallback->StartFastOpen();
+ if (NS_FAILED(rv)) {
+ if (NS_SUCCEEDED(mCondition)) {
+ mCondition = rv;
+ }
+ mFastOpenCallback = nullptr;
+ MutexAutoLock lock(mLock);
+ mFDFastOpenInProgress = false;
+ return rv;
+ }
+ status = PR_FAILURE;
+ connectCalled = false;
+ bool fastOpenNotSupported = false;
+ TCPFastOpenFinish(fd, code, fastOpenNotSupported, mFastOpenStatus);
+
+ // If we have sent data, trigger a socket status event.
+ if (mFastOpenStatus == TFO_DATA_SENT) {
+ SendStatus(NS_NET_STATUS_SENDING_TO);
+ }
+
+ // If we have still some data buffered this data must be flush before
+ // mOutput.OnSocketReady(NS_OK) is called in
+ // nsSocketTransport::OnSocketReady, partially to keep socket status
+ // event in order.
+ mFastOpenLayerHasBufferedData = TCPFastOpenGetCurrentBufferSize(fd);
+
+ MOZ_ASSERT((mFastOpenStatus == TFO_NOT_TRIED) ||
+ (mFastOpenStatus == TFO_DISABLED) ||
+ (mFastOpenStatus == TFO_DATA_SENT) ||
+ (mFastOpenStatus == TFO_TRIED));
+ mFastOpenCallback->SetFastOpenStatus(mFastOpenStatus);
+ SOCKET_LOG(
+ ("called StartFastOpen - code=%d; fastOpen is %s "
+ "supported.\n",
+ code, fastOpenNotSupported ? "not" : ""));
+ SOCKET_LOG(("TFO status %d\n", mFastOpenStatus));
+
+ if (fastOpenNotSupported) {
+ // When TCP_FastOpen is turned off on the local host
+ // SendTo will return PR_NOT_TCP_SOCKET_ERROR. This is only
+ // on Linux.
+ // If a windows version does not support Fast Open, the return value
+ // will be PR_NOT_IMPLEMENTED_ERROR. This is only for windows 10
+ // versions older than version 1607, because we do not have subverion
+ // to check, we need to call PR_SendTo to check if it is supported.
+ mFastOpenCallback->FastOpenNotSupported();
+ // FastOpenNotSupported will set Fast Open as not supported globally.
+ // For this connection we will pretend that we still use fast open,
+ // because of the fallback mechanism in case we need to restart the
+ // attached transaction.
+ connectCalled = true;
+ }
+ } else {
+ mFastOpenCallback = nullptr;
+ }
+
+ 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 (mSecInfo && !mProxyHost.IsEmpty() && proxyTransparent && usingSSL) {
+ // if the connection phase is finished, and the ssl layer has
+ // been pushed, and we were proxying (transparently; ie. nothing
+ // has to happen in the protocol layer above us), it's time for
+ // the ssl to start doing it's thing.
+ nsCOMPtr<nsISSLSocketControl> secCtrl = do_QueryInterface(mSecInfo);
+ if (secCtrl) {
+ SOCKET_LOG((" calling ProxyStartSSL()\n"));
+ secCtrl->ProxyStartSSL();
+ }
+ // XXX what if we were forced to poll on the socket for a successful
+ // connection... wouldn't we need to call ProxyStartSSL after a call
+ // to PR_ConnectContinue indicates that we are connected?
+ //
+ // XXX this appears to be what the old socket transport did. why
+ // isn't this broken?
+ }
+ }
+ //
+ // A SOCKS request was rejected; get the actual error code from
+ // the OS error
+ //
+ else if (PR_UNKNOWN_ERROR == code && mProxyTransparent &&
+ !mProxyHost.IsEmpty()) {
+ code = PR_GetOSError();
+ rv = ErrorAccordingToNSPR(code);
+ }
+ //
+ // The connection was refused...
+ //
+ else {
+ if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
+ connectStarted && 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;
+
+ // OK to check this outside mLock
+ NS_ASSERTION(!mFDconnected, "socket should not be connected");
+
+ // all connection failures need to be reported to DNS so that the next
+ // time we will use a different address if available.
+ // Skip conditions that can be cause by TCP Fast Open.
+ if ((!mFDFastOpenInProgress ||
+ ((mCondition != NS_ERROR_CONNECTION_REFUSED) &&
+ (mCondition != NS_ERROR_NET_TIMEOUT) &&
+ (mCondition != NS_BASE_STREAM_CLOSED) &&
+ (mCondition != NS_ERROR_PROXY_CONNECTION_REFUSED))) &&
+ mState == STATE_CONNECTING && mDNSRecord) {
+ mDNSRecord->ReportUnusable(SocketPort());
+ }
+
+#if defined(_WIN64) && defined(WIN95)
+ // can only recover from these errors
+ if (mCondition != NS_ERROR_CONNECTION_REFUSED &&
+ mCondition != NS_ERROR_PROXY_CONNECTION_REFUSED &&
+ mCondition != NS_ERROR_NET_TIMEOUT &&
+ mCondition != NS_ERROR_UNKNOWN_HOST &&
+ mCondition != NS_ERROR_UNKNOWN_PROXY_HOST &&
+ !(mFDFastOpenInProgress && (mCondition == NS_ERROR_FAILURE))) {
+ SOCKET_LOG((" not a recoverable error %" PRIx32,
+ static_cast<uint32_t>(mCondition)));
+ return false;
+ }
+#else
+ 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;
+ }
+#endif
+
+ bool tryAgain = false;
+ if (mFDFastOpenInProgress &&
+ ((mCondition == NS_ERROR_CONNECTION_REFUSED) ||
+ (mCondition == NS_ERROR_NET_TIMEOUT) ||
+#if defined(_WIN64) && defined(WIN95)
+ // On Windows PR_ContinueConnect can return NS_ERROR_FAILURE.
+ // This will be fixed in bug 1386719 and this is just a temporary
+ // work around.
+ (mCondition == NS_ERROR_FAILURE) ||
+#endif
+ (mCondition == NS_ERROR_PROXY_CONNECTION_REFUSED))) {
+ // TCP Fast Open can be blocked by middle boxes so we will retry
+ // without it.
+ tryAgain = true;
+ // If we cancel the connection because backup socket was successfully
+ // connected, mFDFastOpenInProgress will be true but mFastOpenCallback
+ // will be nullptr.
+ if (mFastOpenCallback) {
+ mFastOpenCallback->SetFastOpenConnected(mCondition, true);
+ }
+ mFastOpenCallback = nullptr;
+
+ } else {
+ // This is only needed for telemetry.
+ if (NS_SUCCEEDED(mFirstRetryError)) {
+ mFirstRetryError = mCondition;
+ }
+ 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);
+ if (NS_SUCCEEDED(rv)) {
+ SOCKET_LOG((" trying again with next ip address\n"));
+ tryAgain = true;
+ } 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) {
+ uint32_t trrMode = 0;
+ mDNSRecord->GetEffectiveTRRMode(&trrMode);
+ // If current trr mode is trr only, we should not retry.
+ if (trrMode != 3) {
+ // 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;
+
+ if (mFDFastOpenInProgress && mFastOpenCallback) {
+ // mFastOpenCallback can be null when for example h2 is negotiated on
+ // another connection to the same host and all connections are
+ // abandoned.
+ mFastOpenCallback->SetFastOpenConnected(NS_OK, false);
+ }
+ mFastOpenCallback = nullptr;
+
+ // 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;
+ mFDFastOpenInProgress = false;
+ 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;
+}
+
+PRFileDesc* nsSocketTransport::GetFD_LockedAlsoDuringFastOpen() {
+ mLock.AssertCurrentThreadOwns();
+
+ // mFD is not available to the streams while disconnected.
+ if (!mFDconnected && !mFDFastOpenInProgress) {
+ return nullptr;
+ }
+
+ if (mFD.IsInitialized()) {
+ mFDref++;
+ }
+
+ return mFD;
+}
+
+bool nsSocketTransport::FastOpenInProgress() {
+ mLock.AssertCurrentThreadOwns();
+ return mFDFastOpenInProgress;
+}
+
+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) {
+ 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"));
+
+ // 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
+#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);
+ }
+ // 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
+}
+
+//-----------------------------------------------------------------------------
+// socket handler impl
+
+void nsSocketTransport::OnSocketReady(PRFileDesc* fd, int16_t outFlags) {
+ 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) && mFastOpenLayerHasBufferedData) {
+ // We have some data buffered in TCPFastOpenLayer. We will flush them
+ // first. We need to do this first before calling OnSocketReady below
+ // so that the socket status events are kept in the correct order.
+ mFastOpenLayerHasBufferedData = TCPFastOpenFlushBuffer(fd);
+ if (mFastOpenLayerHasBufferedData) {
+ return;
+ }
+ SendStatus(NS_NET_STATUS_SENDING_TO);
+
+ // If we are done sending the buffered data continue with the normal
+ // path.
+ // In case of an error, TCPFastOpenFlushBuffer will return false and
+ // the normal code path will pick up the error.
+ mFastOpenLayerHasBufferedData = false;
+ }
+
+ 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 defined(_WIN64) && defined(WIN95)
+# ifndef TCP_FASTOPEN
+# define TCP_FASTOPEN 15
+# endif
+
+ if (mFDFastOpenInProgress && mFastOpenCallback &&
+ (mFastOpenStatus == TFO_DATA_SENT)) {
+ PROsfd osfd = PR_FileDesc2NativeHandle(fd);
+ BOOL option = 0;
+ int len = sizeof(option);
+ PRInt32 rv = getsockopt((SOCKET)osfd, IPPROTO_TCP, TCP_FASTOPEN,
+ (char*)&option, &len);
+ if (!rv && !option) {
+ // On error, I will let the normal necko paths pickup the error.
+ mFastOpenCallback->SetFastOpenStatus(TFO_DATA_COOKIE_NOT_ACCEPTED);
+ }
+ }
+#endif
+
+ 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;
+ }
+ }
+
+ mFastOpenLayerHasBufferedData = false;
+
+ // If we are not shutting down try again.
+ if (!gIOService->IsNetTearingDown() && RecoverFromError())
+ mCondition = NS_OK;
+ else {
+ mState = STATE_CLOSED;
+
+ // The error can happened before we start fast open. In that case do not
+ // call mFastOpenCallback->SetFastOpenConnected; If error happends during
+ // fast open, inform the halfOpenSocket.
+ // If we cancel the connection because backup socket was successfully
+ // connected, mFDFastOpenInProgress will be true but mFastOpenCallback
+ // will be nullptr.
+ if (mFDFastOpenInProgress && mFastOpenCallback) {
+ mFastOpenCallback->SetFastOpenConnected(mCondition, false);
+ }
+ mFastOpenCallback = nullptr;
+
+ // 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 FastOpen has been used (mFDFastOpenInProgress==true),
+ // mFastOpenCallback must be nullptr now. We decided to recover from
+ // error like NET_TIMEOUT, CONNECTION_REFUSED or we have called
+ // SetFastOpenConnected(mCondition) in this function a couple of lines
+ // above.
+ // If FastOpen has not been used (mFDFastOpenInProgress==false) it can be
+ // that mFastOpenCallback is no null, this is the case when we recover from
+ // errors like UKNOWN_HOST in which case socket was not been connected yet
+ // and mFastOpenCallback-StartFastOpen was not be called yet (but we can
+ // still call it in the next try).
+ MOZ_ASSERT(!(mFDFastOpenInProgress && mFastOpenCallback));
+
+ // break any potential reference cycle between the security info object
+ // and ourselves by resetting its notification callbacks object. see
+ // bug 285991 for details.
+ nsCOMPtr<nsISSLSocketControl> secCtrl = do_QueryInterface(mSecInfo);
+ if (secCtrl) secCtrl->SetNotificationCallbacks(nullptr);
+
+ // finally, release our reference to the socket (must do this within
+ // the transport lock) possibly closing the socket. Also release our
+ // listeners to break potential refcount cycles.
+
+ // We should be careful not to release mEventSink and mCallbacks while
+ // we're locked, because releasing it might require acquiring the lock
+ // again, so we just null out mEventSink and mCallbacks while we're
+ // holding the lock, and let the stack based objects' destuctors take
+ // care of destroying it if needed.
+ nsCOMPtr<nsIInterfaceRequestor> ourCallbacks;
+ nsCOMPtr<nsITransportEventSink> ourEventSink;
+ {
+ MutexAutoLock lock(mLock);
+ if (mFD.IsInitialized()) {
+ ReleaseFD_Locked(mFD);
+ // flag mFD as unusable; this prevents other consumers from
+ // acquiring a reference to mFD.
+ mFDconnected = false;
+ mFDFastOpenInProgress = 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** result) {
+ SOCKET_LOG(
+ ("nsSocketTransport::OpenInputStream [this=%p flags=%x]\n", this, flags));
+
+ NS_ENSURE_TRUE(!mInput.IsReferenced(), NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+
+ if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
+ // XXX if the caller wants blocking, then the caller also gets buffered!
+ // bool openBuffered = !(flags & OPEN_UNBUFFERED);
+ bool openBlocking = (flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ // create a pipe
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut),
+ !openBlocking, true, segsize, segcount);
+ if (NS_FAILED(rv)) return rv;
+
+ // async copy from socket to pipe
+ rv = NS_AsyncCopy(&mInput, pipeOut, mSocketTransportService,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize);
+ if (NS_FAILED(rv)) return rv;
+
+ *result = pipeIn;
+ } else
+ *result = &mInput;
+
+ // flag input stream as open
+ mInputClosed = false;
+
+ rv = PostEvent(MSG_ENSURE_CONNECT);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ADDREF(*result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::OpenOutputStream(uint32_t flags, uint32_t segsize,
+ uint32_t segcount,
+ nsIOutputStream** result) {
+ SOCKET_LOG(("nsSocketTransport::OpenOutputStream [this=%p flags=%x]\n", this,
+ flags));
+
+ NS_ENSURE_TRUE(!mOutput.IsReferenced(), NS_ERROR_UNEXPECTED);
+
+ nsresult rv;
+ nsCOMPtr<nsIAsyncOutputStream> pipeOut;
+ if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
+ // XXX if the caller wants blocking, then the caller also gets buffered!
+ // bool openBuffered = !(flags & OPEN_UNBUFFERED);
+ bool openBlocking = (flags & OPEN_BLOCKING);
+
+ net_ResolveSegmentParams(segsize, segcount);
+
+ // create a pipe
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+ rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true,
+ !openBlocking, segsize, segcount);
+ if (NS_FAILED(rv)) return rv;
+
+ // async copy from socket to pipe
+ rv = NS_AsyncCopy(pipeIn, &mOutput, mSocketTransportService,
+ NS_ASYNCCOPY_VIA_READSEGMENTS, segsize);
+ if (NS_FAILED(rv)) return rv;
+
+ *result = pipeOut;
+ } else
+ *result = &mOutput;
+
+ // flag output stream as open
+ mOutputClosed = false;
+
+ rv = PostEvent(MSG_ENSURE_CONNECT);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ADDREF(*result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::Close(nsresult reason) {
+ SOCKET_LOG(("nsSocketTransport::Close %p reason=%" PRIx32, this,
+ static_cast<uint32_t>(reason)));
+
+ if (NS_SUCCEEDED(reason)) reason = NS_BASE_STREAM_CLOSED;
+
+ mDoNotRetryToConnect = true;
+
+ if (mFDFastOpenInProgress && mFastOpenCallback) {
+ mFastOpenCallback->SetFastOpenConnected(reason, false);
+ }
+ mFastOpenCallback = nullptr;
+
+ mInput.CloseWithStatus(reason);
+ mOutput.CloseWithStatus(reason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSecurityInfo(nsISupports** secinfo) {
+ MutexAutoLock lock(mLock);
+ NS_IF_ADDREF(*secinfo = mSecInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetSecurityCallbacks(nsIInterfaceRequestor** callbacks) {
+ MutexAutoLock lock(mLock);
+ NS_IF_ADDREF(*callbacks = mCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor* callbacks) {
+ nsCOMPtr<nsIInterfaceRequestor> threadsafeCallbacks;
+ NS_NewNotificationCallbacksAggregation(callbacks, nullptr,
+ GetCurrentEventTarget(),
+ getter_AddRefs(threadsafeCallbacks));
+
+ nsCOMPtr<nsISupports> secinfo;
+ {
+ MutexAutoLock lock(mLock);
+ mCallbacks = threadsafeCallbacks;
+ SOCKET_LOG(("Reset callbacks for secinfo=%p callbacks=%p\n", mSecInfo.get(),
+ mCallbacks.get()));
+
+ secinfo = mSecInfo;
+ }
+
+ // don't call into PSM while holding mLock!!
+ nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(secinfo));
+ if (secCtrl) secCtrl->SetNotificationCallbacks(threadsafeCallbacks);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::SetEventSink(nsITransportEventSink* sink,
+ nsIEventTarget* target) {
+ nsCOMPtr<nsITransportEventSink> temp;
+ if (target) {
+ nsresult rv =
+ net_NewTransportEventSinkProxy(getter_AddRefs(temp), sink, target);
+ if (NS_FAILED(rv)) return rv;
+ sink = temp.get();
+ }
+
+ MutexAutoLock lock(mLock);
+ mEventSink = sink;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::IsAlive(bool* result) {
+ *result = false;
+
+ // During Fast Open we need to return true here.
+ if (mFDFastOpenInProgress) {
+ *result = true;
+ return NS_OK;
+ }
+
+ nsresult conditionWhileLocked = NS_OK;
+ PRFileDescAutoLock fd(this, false, &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);
+ 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, false);
+ 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, false);
+ 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, false);
+ 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, false);
+ 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);
+ }
+
+ // 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, true);
+ 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, true);
+ 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();
+ LPVOID errMessage;
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, errCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR)&errMessage, 0, NULL);
+# else
+ int errCode = errno;
+ char* errMessage = strerror(errno);
+# endif
+ NS_WARNING(nsPrintfCString("%s [%p] OS error[0x%x] %s",
+ aPrefix ? aPrefix : "nsSocketTransport", aObjPtr,
+ errCode,
+ errMessage ? errMessage : "<no error text>")
+ .get());
+# ifdef XP_WIN
+ LocalFree(errMessage);
+# endif
+#endif
+}
+
+/* XXX PR_SetSockOpt does not support setting keepalive values, so native
+ * handles and platform specific apis (setsockopt, WSAIOCtl) are used in this
+ * file. Requires inclusion of NSPR private/pprio.h, and platform headers.
+ */
+
+nsresult nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals(
+ bool aEnabled, int aIdleTime, int aRetryInterval, int aProbeCount) {
+#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
+ MOZ_ASSERT(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::SetFastOpenCallback(TCPFastOpen* aFastOpen) {
+ mFastOpenCallback = aFastOpen;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSocketTransport::GetFirstRetryError(nsresult* aFirstRetryError) {
+ *aFirstRetryError = mFirstRetryError;
+ return NS_OK;
+}
+
+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;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsSocketTransport2.h b/netwerk/base/nsSocketTransport2.h
new file mode 100644
index 0000000000..a3f38450eb
--- /dev/null
+++ b/netwerk/base/nsSocketTransport2.h
@@ -0,0 +1,486 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSocketTransport2_h__
+#define nsSocketTransport2_h__
+
+#ifdef DEBUG_darinf
+# define ENABLE_SOCKET_TRACING
+#endif
+
+#include "mozilla/Mutex.h"
+#include "nsSocketTransportService2.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsIClassInfo.h"
+#include "TCPFastOpen.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 nsSocketTransport;
+
+class nsSocketInputStream : public nsIAsyncInputStream {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ explicit nsSocketInputStream(nsSocketTransport*);
+ virtual ~nsSocketInputStream() = default;
+
+ bool IsReferenced() { return mReaderRefCnt > 0; }
+ nsresult Condition() { return mCondition; }
+ uint64_t ByteCount() { return mByteCount; }
+
+ // called by the socket transport on the socket thread...
+ void OnSocketReady(nsresult condition);
+
+ private:
+ nsSocketTransport* mTransport;
+ ThreadSafeAutoRefCnt mReaderRefCnt;
+
+ // access to these is protected by mTransport->mLock
+ nsresult mCondition;
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ uint32_t mCallbackFlags;
+ uint64_t mByteCount;
+};
+
+//-----------------------------------------------------------------------------
+
+class nsSocketOutputStream : public nsIAsyncOutputStream {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ explicit nsSocketOutputStream(nsSocketTransport*);
+ virtual ~nsSocketOutputStream() = default;
+
+ bool IsReferenced() { return mWriterRefCnt > 0; }
+ nsresult Condition() { return mCondition; }
+ uint64_t ByteCount() { return mByteCount; }
+
+ // called by the socket transport on the socket thread...
+ void OnSocketReady(nsresult condition);
+
+ private:
+ static nsresult WriteFromSegments(nsIInputStream*, void*, const char*,
+ uint32_t offset, uint32_t count,
+ uint32_t* countRead);
+
+ nsSocketTransport* mTransport;
+ ThreadSafeAutoRefCnt mWriterRefCnt;
+
+ // access to these is protected by mTransport->mLock
+ nsresult mCondition;
+ nsCOMPtr<nsIOutputStreamCallback> mCallback;
+ uint32_t mCallbackFlags;
+ uint64_t mByteCount;
+};
+
+//-----------------------------------------------------------------------------
+
+class nsSocketTransport final : public nsASocketHandler,
+ public nsISocketTransport,
+ public nsIDNSListener,
+ public nsIClassInfo,
+ public nsIInterfaceRequestor {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSISOCKETTRANSPORT
+ NS_DECL_NSIDNSLISTENER
+ NS_DECL_NSICLASSINFO
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ nsSocketTransport();
+
+ // this method instructs the socket transport to open a socket of the
+ // given type(s) to the given host or proxy.
+ nsresult Init(const nsTArray<nsCString>& socketTypes, const nsACString& host,
+ uint16_t port, const nsACString& hostRoute, uint16_t portRoute,
+ nsIProxyInfo* proxyInfo);
+
+ // this method instructs the socket transport to use an already connected
+ // socket with the given address.
+ nsresult InitWithConnectedSocket(PRFileDesc* socketFD, const NetAddr* addr);
+
+ // this method instructs the socket transport to use an already connected
+ // socket with the given address, and additionally supplies security info.
+ nsresult InitWithConnectedSocket(PRFileDesc* aSocketFD, const NetAddr* aAddr,
+ nsISupports* aSecInfo);
+
+#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);
+
+ uint64_t ByteCountReceived() override { return mInput.ByteCount(); }
+ uint64_t ByteCountSent() override { return mOutput.ByteCount(); }
+ static void CloseSocket(PRFileDesc* aFd, bool aTelemetryEnabled);
+ static void SendPRBlockingTelemetry(
+ PRIntervalTime aStart, Telemetry::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);
+
+ 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,
+ bool aAlsoDuringFastOpen,
+ 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;
+ }
+ }
+ if (!aAlsoDuringFastOpen) {
+ mFd = mSocketTransport->GetFD_Locked();
+ } else {
+ mFd = mSocketTransport->GetFD_LockedAlsoDuringFastOpen();
+ }
+ }
+ ~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;
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+ uint16_t mProxyPort;
+ uint16_t mOriginPort;
+ bool mProxyTransparent;
+ bool mProxyTransparentResolvesHost;
+ bool mHttpsProxy;
+ uint32_t mConnectionFlags;
+ // 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;
+ uint32_t mTlsFlags;
+ bool mReuseAddrPort;
+
+ // 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;
+ }
+
+ //-------------------------------------------------------------------------
+ // members accessible only on the socket transport thread:
+ // (the exception being initialization/shutdown time)
+ //-------------------------------------------------------------------------
+
+ // socket state vars:
+ uint32_t mState; // STATE_??? flags
+ bool mAttached;
+ bool mInputClosed;
+ bool mOutputClosed;
+
+ // this flag is used to determine if the results of a host lookup arrive
+ // recursively or not. this flag is not protected by any lock.
+ bool mResolving;
+
+ nsCOMPtr<nsICancelable> mDNSRequest;
+ nsCOMPtr<nsIDNSAddrRecord> mDNSRecord;
+
+ nsCString mEchConfig;
+ bool mEchConfigUsed = false;
+ bool mResolvedByTRR;
+
+ // mNetAddr/mSelfAddr is valid from GetPeerAddr()/GetSelfAddr() once we have
+ // reached STATE_TRANSFERRING. It must not change after that.
+ void SetSocketName(PRFileDesc* fd);
+ NetAddr mNetAddr;
+ NetAddr mSelfAddr; // getsockname()
+ Atomic<bool, Relaxed> mNetAddrIsSet;
+ Atomic<bool, Relaxed> mSelfAddrIsSet;
+
+ 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() {
+ if (mState == STATE_TRANSFERRING)
+ mPollFlags |= (PR_POLL_READ | PR_POLL_EXCEPT);
+ }
+ void OnMsgOutputPending() {
+ if (mState == STATE_TRANSFERRING)
+ mPollFlags |= (PR_POLL_WRITE | PR_POLL_EXCEPT);
+ }
+ void OnMsgInputClosed(nsresult reason);
+ void OnMsgOutputClosed(nsresult reason);
+
+ // called when the socket is connected
+ void OnSocketConnected();
+
+ //-------------------------------------------------------------------------
+ // socket input/output objects. these may be accessed on any thread with
+ // the exception of some specific methods (XXX).
+
+ Mutex mLock; // protects members in this section.
+ LockedPRFileDesc mFD;
+ nsrefcnt mFDref; // mFD is closed when mFDref goes to zero.
+ bool mFDconnected; // mFD is available to consumer when TRUE.
+ bool mFDFastOpenInProgress; // Fast Open is in progress, so
+ // socket available for some
+ // operations.
+
+ // A delete protector reference to gSocketTransportService held for lifetime
+ // of 'this'. Sometimes used interchangably with gSocketTransportService due
+ // to scoping.
+ RefPtr<nsSocketTransportService> mSocketTransportService;
+
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsITransportEventSink> mEventSink;
+ nsCOMPtr<nsISupports> mSecInfo;
+
+ nsSocketInputStream mInput;
+ nsSocketOutputStream mOutput;
+
+ friend class nsSocketInputStream;
+ friend class nsSocketOutputStream;
+
+ // socket timeouts are protected by mLock.
+ uint16_t mTimeouts[2];
+
+ // linger options to use when closing
+ bool mLingerPolarity;
+ int16_t mLingerTimeout;
+
+ // QoS setting for socket
+ uint8_t mQoSBits;
+
+ //
+ // mFD access methods: called with mLock held.
+ //
+ PRFileDesc* GetFD_Locked();
+ PRFileDesc* GetFD_LockedAlsoDuringFastOpen();
+ void ReleaseFD_Locked(PRFileDesc* fd);
+ bool FastOpenInProgress();
+
+ //
+ // 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;
+
+ // Keepalive config (support varies by platform).
+ int32_t mKeepaliveIdleTimeS;
+ int32_t mKeepaliveRetryIntervalS;
+ int32_t mKeepaliveProbeCount;
+
+ // A Fast Open callback.
+ TCPFastOpen* mFastOpenCallback;
+ bool mFastOpenLayerHasBufferedData;
+ uint8_t mFastOpenStatus;
+ nsresult mFirstRetryError;
+
+ bool mDoNotRetryToConnect;
+
+ // If the connection is used for QUIC this is set to true. That will mean
+ // that UDP will be used. QUIC do not have a SocketProvider because it is a
+ // mix of transport and application(HTTP) level protocol. nsSocketTransport
+ // will creat a UDP socket and SecInfo(QuicSocketControl). The protocol
+ // handler will be created by nsHttpconnectionMgr.
+ bool mUsingQuic;
+
+ // Whether the port remapping has already been applied. We definitely want to
+ // prevent duplicate calls in case of chaining remapping.
+ bool mPortRemappingApplied = false;
+};
+
+} // 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..3b2bec6536
--- /dev/null
+++ b/netwerk/base/nsSocketTransportService2.cpp
@@ -0,0 +1,1892 @@
+// 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/PublicSSL.h"
+#include "mozilla/ReverseIterator.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "nsASocketHandler.h"
+#include "nsError.h"
+#include "nsIFile.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"
+
+#ifdef MOZ_TASK_TRACER
+# include "GeckoTaskTracer.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+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 SOCKET_LIMIT_MIN 50U
+#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));
+ if (!mPollStartEpoch) {
+ SOCKET_LOG((" engaging"));
+ mPollStartEpoch = now;
+ }
+}
+
+void nsSocketTransportService::SocketContext::DisengageTimeout() {
+ SOCKET_LOG(("SocketContext::DisengageTimeout socket=%p", mHandler));
+ mPollStartEpoch = 0;
+}
+
+PRIntervalTime nsSocketTransportService::SocketContext::TimeoutIn(
+ PRIntervalTime now) const {
+ SOCKET_LOG(("SocketContext::TimeoutIn socket=%p, timeout=%us", mHandler,
+ 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()
+ : mRawThread(nullptr),
+ mInitialized(false),
+ mShuttingDown(false),
+ mLock("nsSocketTransportService::mLock"),
+ mOffline(false),
+ mGoingOffline(false),
+ mActiveListSize(SOCKET_LIMIT_MIN),
+ mIdleListSize(SOCKET_LIMIT_MIN),
+ mActiveCount(0),
+ mIdleCount(0),
+ mSentBytesCount(0),
+ mReceivedBytesCount(0),
+ mSendBufferSize(0),
+ mKeepaliveIdleTimeS(600),
+ mKeepaliveRetryIntervalS(1),
+ mKeepaliveProbeCount(kDefaultTCPKeepCount),
+ mKeepaliveEnabledPref(false),
+ mPollableEventTimeout(TimeDuration::FromSeconds(6)),
+ mServingPendingQueue(false),
+ mMaxTimePerPollIter(100),
+ mMaxTimeForPrClosePref(PR_SecondsToInterval(5)),
+ mLastNetworkLinkChangeTime(0),
+ mNetworkLinkChangeBusyWaitPeriod(PR_SecondsToInterval(50)),
+ mNetworkLinkChangeBusyWaitTimeout(PR_SecondsToInterval(7)),
+ mSleepPhase(false),
+ mProbedMaxCount(false)
+#if defined(XP_WIN)
+ ,
+ mPolling(false)
+#endif
+ ,
+ mNotTrustedMitmDetected(false) {
+ NS_ASSERTION(NS_IsMainThread(), "wrong thread");
+
+ PR_CallOnce(&gMaxCountInitOnce, DiscoverMaxCount);
+ mActiveList =
+ (SocketContext*)moz_xmalloc(sizeof(SocketContext) * mActiveListSize);
+ mIdleList =
+ (SocketContext*)moz_xmalloc(sizeof(SocketContext) * mIdleListSize);
+ mPollList =
+ (PRPollDesc*)moz_xmalloc(sizeof(PRPollDesc) * (mActiveListSize + 1));
+
+ NS_ASSERTION(!gSocketTransportService, "must not instantiate twice");
+ gSocketTransportService = this;
+}
+
+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 < Get<0>(portMapping)) {
+ continue;
+ }
+ if (*aPort > Get<1>(portMapping)) {
+ continue;
+ }
+
+ *aPort = 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<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(MakeTuple(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(
+ MakeTuple(Get<0>(range), 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");
+
+ free(mActiveList);
+ free(mIdleList);
+ free(mPollList);
+ gSocketTransportService = nullptr;
+}
+
+//-----------------------------------------------------------------------------
+// event queue (any thread)
+
+already_AddRefed<nsIThread> nsSocketTransportService::GetThreadSafely() {
+ MutexAutoLock lock(mLock);
+ nsCOMPtr<nsIThread> result = mThread;
+ return result.forget();
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::DispatchFromScript(nsIRunnable* event,
+ uint32_t flags) {
+ nsCOMPtr<nsIRunnable> event_ref(event);
+ return Dispatch(event_ref.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::Dispatch(already_AddRefed<nsIRunnable> event,
+ uint32_t flags) {
+ nsCOMPtr<nsIRunnable> event_ref(event);
+ SOCKET_LOG(("STS dispatch [%p]\n", event_ref.get()));
+
+ nsCOMPtr<nsIThread> thread = GetThreadSafely();
+ nsresult rv;
+ rv = thread ? thread->Dispatch(event_ref.forget(), flags)
+ : NS_ERROR_NOT_INITIALIZED;
+ if (rv == NS_ERROR_UNEXPECTED) {
+ // Thread is no longer accepting events. We must have just shut it
+ // down on the main thread. Pretend we never saw it.
+ rv = NS_ERROR_NOT_INITIALIZED;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::DelayedDispatch(already_AddRefed<nsIRunnable>,
+ uint32_t) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::IsOnCurrentThread(bool* result) {
+ *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;
+ sock.mFD = fd;
+ sock.mHandler = handler;
+ sock.mPollStartEpoch = 0;
+
+ nsresult rv = AddToIdleList(&sock);
+ if (NS_SUCCEEDED(rv)) NS_ADDREF(handler);
+ return rv;
+}
+
+// the number of sockets that can be attached at any given time is
+// limited. this is done because some operating systems (e.g., Win9x)
+// limit the number of sockets that can be created by an application.
+// AttachSocket will fail if the limit is exceeded. consumers should
+// call CanAttachSocket and check the result before creating a socket.
+
+bool nsSocketTransportService::CanAttachSocket() {
+ static bool reported900FDLimit = false;
+
+ uint32_t total = mActiveCount + mIdleCount;
+ bool rv = total < gMaxCount;
+
+ if (Telemetry::CanRecordPrereleaseData() &&
+ (((total >= 900) || !rv) && !reported900FDLimit)) {
+ reported900FDLimit = true;
+ Telemetry::Accumulate(Telemetry::NETWORK_SESSION_AT_900FD, true);
+ }
+
+ return rv;
+}
+
+nsresult nsSocketTransportService::DetachSocket(SocketContext* listHead,
+ SocketContext* sock) {
+ SOCKET_LOG(("nsSocketTransportService::DetachSocket [handler=%p]\n",
+ sock->mHandler));
+ MOZ_ASSERT((listHead == mActiveList) || (listHead == mIdleList),
+ "DetachSocket invalid head");
+
+ {
+#ifdef MOZ_TASK_TRACER
+ tasktracer::AutoSourceEvent taskTracerEvent(
+ tasktracer::SourceEventType::SocketIO);
+#endif
+ // inform the handler that this socket is going away
+ sock->mHandler->OnSocketDetached(sock->mFD);
+ }
+ mSentBytesCount += sock->mHandler->ByteCountSent();
+ mReceivedBytesCount += sock->mHandler->ByteCountReceived();
+
+ // cleanup
+ sock->mFD = nullptr;
+ NS_RELEASE(sock->mHandler);
+
+ if (listHead == mActiveList)
+ RemoveFromPollList(sock);
+ else
+ RemoveFromIdleList(sock);
+
+ // NOTE: sock is now an invalid pointer
+
+ //
+ // notify the first element on the pending socket queue...
+ //
+ nsCOMPtr<nsIRunnable> event;
+ LinkedRunnableEvent* runnable = mPendingSocketQueue.getFirst();
+ if (runnable) {
+ event = runnable->TakeEvent();
+ runnable->remove();
+ delete runnable;
+ }
+ if (event) {
+ // move event from pending queue to dispatch queue
+ return Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ return NS_OK;
+}
+
+nsresult nsSocketTransportService::AddToPollList(SocketContext* sock) {
+ MOZ_ASSERT(!(static_cast<uint32_t>(sock - mActiveList) < mActiveListSize),
+ "AddToPollList Socket Already Active");
+
+ SOCKET_LOG(("nsSocketTransportService::AddToPollList [handler=%p]\n",
+ sock->mHandler));
+ if (mActiveCount == mActiveListSize) {
+ SOCKET_LOG((" Active List size of %d met\n", mActiveCount));
+ if (!GrowActiveList()) {
+ NS_ERROR("too many active sockets");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ uint32_t newSocketIndex = mActiveCount;
+ if (ChaosMode::isActive(ChaosFeature::NetworkScheduling)) {
+ newSocketIndex = ChaosMode::randomUint32LessThan(mActiveCount + 1);
+ PodMove(mActiveList + newSocketIndex + 1, mActiveList + newSocketIndex,
+ mActiveCount - newSocketIndex);
+ PodMove(mPollList + newSocketIndex + 2, mPollList + newSocketIndex + 1,
+ mActiveCount - newSocketIndex);
+ }
+
+ sock->EnsureTimeout(PR_IntervalNow());
+ mActiveList[newSocketIndex] = *sock;
+ mActiveCount++;
+
+ mPollList[newSocketIndex + 1].fd = sock->mFD;
+ mPollList[newSocketIndex + 1].in_flags = sock->mHandler->mPollFlags;
+ mPollList[newSocketIndex + 1].out_flags = 0;
+
+ SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount));
+ return NS_OK;
+}
+
+void nsSocketTransportService::RemoveFromPollList(SocketContext* sock) {
+ SOCKET_LOG(("nsSocketTransportService::RemoveFromPollList [handler=%p]\n",
+ sock->mHandler));
+
+ uint32_t index = sock - mActiveList;
+ MOZ_ASSERT(index < mActiveListSize, "invalid index");
+
+ SOCKET_LOG((" index=%u mActiveCount=%u\n", index, mActiveCount));
+
+ if (index != mActiveCount - 1) {
+ mActiveList[index] = mActiveList[mActiveCount - 1];
+ mPollList[index + 1] = mPollList[mActiveCount];
+ }
+ mActiveCount--;
+
+ SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount));
+}
+
+nsresult nsSocketTransportService::AddToIdleList(SocketContext* sock) {
+ MOZ_ASSERT(!(static_cast<uint32_t>(sock - mIdleList) < mIdleListSize),
+ "AddToIdlelList Socket Already Idle");
+
+ SOCKET_LOG(("nsSocketTransportService::AddToIdleList [handler=%p]\n",
+ sock->mHandler));
+ if (mIdleCount == mIdleListSize) {
+ SOCKET_LOG((" Idle List size of %d met\n", mIdleCount));
+ if (!GrowIdleList()) {
+ NS_ERROR("too many idle sockets");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ mIdleList[mIdleCount] = *sock;
+ mIdleCount++;
+
+ SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount));
+ return NS_OK;
+}
+
+void nsSocketTransportService::RemoveFromIdleList(SocketContext* sock) {
+ SOCKET_LOG(("nsSocketTransportService::RemoveFromIdleList [handler=%p]\n",
+ sock->mHandler));
+
+ uint32_t index = sock - mIdleList;
+ NS_ASSERTION(index < mIdleListSize, "invalid index in idle list");
+
+ if (index != mIdleCount - 1) mIdleList[index] = mIdleList[mIdleCount - 1];
+ mIdleCount--;
+
+ SOCKET_LOG((" active=%u idle=%u\n", mActiveCount, mIdleCount));
+}
+
+void nsSocketTransportService::MoveToIdleList(SocketContext* sock) {
+ nsresult rv = AddToIdleList(sock);
+ if (NS_FAILED(rv))
+ DetachSocket(mActiveList, sock);
+ else
+ RemoveFromPollList(sock);
+}
+
+void nsSocketTransportService::MoveToPollList(SocketContext* sock) {
+ nsresult rv = AddToPollList(sock);
+ if (NS_FAILED(rv))
+ DetachSocket(mIdleList, sock);
+ else
+ RemoveFromIdleList(sock);
+}
+
+bool nsSocketTransportService::GrowActiveList() {
+ int32_t toAdd = gMaxCount - mActiveListSize;
+ if (toAdd > 100) {
+ toAdd = 100;
+ } else if (toAdd < 1) {
+ MOZ_ASSERT(false, "CanAttachSocket() should prevent this");
+ return false;
+ }
+
+ mActiveListSize += toAdd;
+ mActiveList = (SocketContext*)moz_xrealloc(
+ mActiveList, sizeof(SocketContext) * mActiveListSize);
+ mPollList = (PRPollDesc*)moz_xrealloc(
+ mPollList, sizeof(PRPollDesc) * (mActiveListSize + 1));
+ return true;
+}
+
+bool nsSocketTransportService::GrowIdleList() {
+ int32_t toAdd = gMaxCount - mIdleListSize;
+ if (toAdd > 100) {
+ toAdd = 100;
+ } else if (toAdd < 1) {
+ MOZ_ASSERT(false, "CanAttachSocket() should prevent this");
+ return false;
+ }
+
+ mIdleListSize += toAdd;
+ mIdleList = (SocketContext*)moz_xrealloc(
+ mIdleList, sizeof(SocketContext) * mIdleListSize);
+ return true;
+}
+
+void nsSocketTransportService::ApplyPortRemapPreference(
+ TPortRemapping const& portRemapping) {
+ MOZ_ASSERT(IsOnCurrentThreadInfallible());
+
+ mPortRemapping.reset();
+ if (!portRemapping.IsEmpty()) {
+ mPortRemapping.emplace(portRemapping);
+ }
+}
+
+PRIntervalTime nsSocketTransportService::PollTimeout(PRIntervalTime now) {
+ if (mActiveCount == 0) {
+ 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 < mActiveCount; ++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* pollList;
+ 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;
+ pollList = mPollList;
+ pollCount = mActiveCount + 1;
+ pollTimeout = pendingEvents ? PR_INTERVAL_NO_WAIT : PollTimeout(ts);
+ } else {
+ // no pollable event, so busy wait...
+ pollCount = mActiveCount;
+ if (pollCount)
+ pollList = &mPollList[1];
+ else
+ pollList = nullptr;
+ pollTimeout =
+ pendingEvents ? PR_INTERVAL_NO_WAIT : PR_MillisecondsToInterval(25);
+ }
+
+ 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 rv = [&]() {
+ if (pollTimeout != PR_INTERVAL_NO_WAIT) {
+ // There will be an actual non-zero wait, let the profiler record
+ // idle time and mark thread as sleeping around the polling call.
+ AUTO_PROFILER_LABEL("nsSocketTransportService::Poll", IDLE);
+ AUTO_PROFILER_THREAD_SLEEP;
+ return PR_Poll(pollList, pollCount, pollTimeout);
+ }
+ return PR_Poll(pollList, pollCount, pollTimeout);
+ }();
+
+ if (Telemetry::CanRecordPrereleaseData() && !pollStart.IsNull()) {
+ *pollDuration = TimeStamp::NowLoRes() - pollStart;
+ }
+
+ SOCKET_LOG((" ...returned after %i milliseconds\n",
+ PR_IntervalToMilliseconds(PR_IntervalNow() - ts)));
+
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// xpcom api
+
+NS_IMPL_ISUPPORTS(nsSocketTransportService, nsISocketTransportService,
+ nsIRoutedSocketTransportService, nsIEventTarget,
+ nsISerialEventTarget, nsIThreadObserver, nsIRunnable,
+ nsPISocketTransportService, nsIObserver,
+ 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();
+}
+
+// called from main thread only
+NS_IMETHODIMP
+nsSocketTransportService::Init() {
+ if (!NS_IsMainThread()) {
+ NS_ERROR("wrong thread");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mInitialized) {
+ return NS_OK;
+ }
+
+ if (mShuttingDown) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv =
+ NS_NewNamedThread("Socket Thread", getter_AddRefs(thread), this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ {
+ 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) {
+ return NS_OK;
+ }
+
+ if (mShuttingDown) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ {
+ auto observersCopy = mShutdownObservers;
+ for (auto& observer : observersCopy) {
+ observer->Observe();
+ }
+ }
+
+ // signal the socket thread to shutdown
+ mShuttingDown = true;
+
+ {
+ MutexAutoLock lock(mLock);
+
+ if (mPollableEvent) {
+ mPollableEvent->Signal();
+ }
+ }
+
+ if (!aXpcomShutdown) {
+ return ShutdownThread();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSocketTransportService::ShutdownThread() {
+ SOCKET_LOG(("nsSocketTransportService::ShutdownThread\n"));
+
+ NS_ENSURE_STATE(NS_IsMainThread());
+
+ if (!mInitialized || !mShuttingDown) {
+ return NS_OK;
+ }
+
+ // join with thread
+ mThread->Shutdown();
+ {
+ MutexAutoLock lock(mLock);
+ // Drop our reference to mThread and make sure that any concurrent readers
+ // are excluded
+ mThread = nullptr;
+ 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) {
+ *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,
+ nsISocketTransport** result) {
+ return CreateRoutedTransport(types, host, port, ""_ns, 0, proxyInfo, result);
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::CreateRoutedTransport(
+ const nsTArray<nsCString>& types, const nsACString& host, int32_t port,
+ const nsACString& hostRoute, int32_t portRoute, nsIProxyInfo* proxyInfo,
+ 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);
+ 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();
+
+ {
+ MutexAutoLock lock(mLock);
+ mPollableEvent.reset(new PollableEvent());
+ //
+ // NOTE: per bug 190000, this failure could be caused by Zone-Alarm
+ // or similar software.
+ //
+ // NOTE: per bug 191739, this failure could also be caused by lack
+ // of a loopback device on Windows and OS/2 platforms (it creates
+ // a loopback socket pair on these platforms to implement a pollable
+ // event object). if we can't create a pollable event, then we'll
+ // have to "busy wait" to implement the socket event queue :-(
+ //
+ if (!mPollableEvent->Valid()) {
+ mPollableEvent = nullptr;
+ NS_WARNING("running socket transport thread without a pollable event");
+ SOCKET_LOG(("running socket transport thread without a pollable event"));
+ }
+
+ mPollList[0].fd = mPollableEvent ? mPollableEvent->PollableFD() : nullptr;
+ mPollList[0].in_flags = PR_POLL_READ | PR_POLL_EXCEPT;
+ mPollList[0].out_flags = 0;
+ }
+
+ mRawThread = NS_GetCurrentThread();
+
+ // 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 SLL 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"));
+
+ return NS_OK;
+}
+
+void nsSocketTransportService::DetachSocketWithGuard(bool aGuardLocals,
+ SocketContext* socketList,
+ int32_t index) {
+ bool isGuarded = false;
+ if (aGuardLocals) {
+ socketList[index].mHandler->IsLocal(&isGuarded);
+ if (!isGuarded) socketList[index].mHandler->KeepWhenOffline(&isGuarded);
+ }
+ if (!isGuarded) DetachSocket(socketList, &socketList[index]);
+}
+
+void nsSocketTransportService::Reset(bool aGuardLocals) {
+ // detach any sockets
+ int32_t i;
+ for (i = mActiveCount - 1; i >= 0; --i) {
+ DetachSocketWithGuard(aGuardLocals, mActiveList, i);
+ }
+ for (i = mIdleCount - 1; i >= 0; --i) {
+ DetachSocketWithGuard(aGuardLocals, mIdleList, i);
+ }
+}
+
+nsresult nsSocketTransportService::DoPollIteration(TimeDuration* pollDuration) {
+ SOCKET_LOG(("STS poll iter\n"));
+
+ PRIntervalTime now = PR_IntervalNow();
+
+ int32_t i, count;
+ //
+ // poll loop
+ //
+ // walk active list backwards to see if any sockets should actually be
+ // idle, then walk the idle list backwards to see if any idle sockets
+ // should become active. take care to check only idle sockets that
+ // were idle to begin with ;-)
+ //
+ count = mIdleCount;
+ for (i = mActiveCount - 1; i >= 0; --i) {
+ //---
+ SOCKET_LOG((" active [%u] { handler=%p condition=%" PRIx32
+ " pollflags=%hu }\n",
+ i, mActiveList[i].mHandler,
+ 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,
+ 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=%u idle=%u]\n", mActiveCount, mIdleCount));
+
+#if defined(XP_WIN)
+ // 30 active connections is the historic limit before firefox 7's 256. A few
+ // windows systems have troubles with the higher limit, so actively probe a
+ // limit the first time we exceed 30.
+ if ((mActiveCount > 30) && !mProbedMaxCount) ProbeMaxCount();
+#endif
+
+ // Measures seconds spent while blocked on PR_Poll
+ 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();
+
+ 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(mActiveCount); ++i) {
+ PRPollDesc& desc = mPollList[i + 1];
+ SocketContext& s = mActiveList[i];
+ if (n > 0 && desc.out_flags != 0) {
+#ifdef MOZ_TASK_TRACER
+ tasktracer::AutoSourceEvent taskTracerEvent(
+ tasktracer::SourceEventType::SocketIO);
+#endif
+ s.DisengageTimeout();
+ s.mHandler->OnSocketReady(desc.fd, desc.out_flags);
+ } else if (s.IsTimedOut(now)) {
+#ifdef MOZ_TASK_TRACER
+ tasktracer::AutoSourceEvent taskTracerEvent(
+ tasktracer::SourceEventType::SocketIO);
+#endif
+ SOCKET_LOG(("socket %p timed out", s.mHandler));
+ 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 = mActiveCount - 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();
+ }
+ }
+ }
+
+ 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 = mActiveCount - 1; i >= 0; --i) {
+ NotifyKeepaliveEnabledPrefChange(&mActiveList[i]);
+ }
+ for (int32_t i = mIdleCount - 1; i >= 0; --i) {
+ NotifyKeepaliveEnabledPrefChange(&mIdleList[i]);
+ }
+}
+
+void nsSocketTransportService::NotifyKeepaliveEnabledPrefChange(
+ SocketContext* sock) {
+ MOZ_ASSERT(sock, "SocketContext cannot be null!");
+ MOZ_ASSERT(sock->mHandler, "SocketContext does not have a handler!");
+
+ if (!sock || !sock->mHandler) {
+ return;
+ }
+
+#ifdef MOZ_TASK_TRACER
+ tasktracer::AutoSourceEvent taskTracerEvent(
+ tasktracer::SourceEventType::SocketIO);
+#endif
+ sock->mHandler->OnKeepaliveEnabledPrefChange(mKeepaliveEnabledPref);
+}
+
+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();
+ mNotTrustedMitmDetected = false;
+ }
+
+ return NS_OK;
+}
+
+void nsSocketTransportService::ClosePrivateConnections() {
+ MOZ_ASSERT(IsOnCurrentThread(), "Must be called on the socket thread");
+
+ for (int32_t i = mActiveCount - 1; i >= 0; --i) {
+ if (mActiveList[i].mHandler->mIsPrivate) {
+ DetachSocket(mActiveList, &mActiveList[i]);
+ }
+ }
+ for (int32_t i = mIdleCount - 1; i >= 0; --i) {
+ if (mIdleList[i].mHandler->mIsPrivate) {
+ DetachSocket(mIdleList, &mIdleList[i]);
+ }
+ }
+
+ ClearPrivateSSLState();
+}
+
+NS_IMETHODIMP
+nsSocketTransportService::GetSendBufferSize(int32_t* value) {
+ *value = mSendBufferSize;
+ return NS_OK;
+}
+
+/// ugly OS specific includes are placed at the bottom of the src for clarity
+
+#if defined(XP_WIN)
+# include <windows.h>
+#elif defined(XP_UNIX) && !defined(AIX) && !defined(NEXTSTEP) && !defined(QNX)
+# include <sys/resource.h>
+#endif
+
+// Right now the only need to do this is on windows.
+#if defined(XP_WIN)
+void nsSocketTransportService::ProbeMaxCount() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mProbedMaxCount) {
+ return;
+ }
+ mProbedMaxCount = true;
+
+ // Allocate and test a PR_Poll up to the gMaxCount number of unconnected
+ // sockets. See bug 692260 - windows should be able to handle 1000 sockets
+ // in select() without a problem, but LSPs have been known to balk at lower
+ // numbers. (64 in the bug).
+
+ // Allocate
+ struct PRPollDesc pfd[SOCKET_LIMIT_TARGET];
+ uint32_t numAllocated = 0;
+
+ for (uint32_t index = 0; index < gMaxCount; ++index) {
+ pfd[index].in_flags = PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT;
+ pfd[index].out_flags = 0;
+ pfd[index].fd = PR_OpenTCPSocket(PR_AF_INET);
+ if (!pfd[index].fd) {
+ SOCKET_LOG(("Socket Limit Test index %d failed\n", index));
+ if (index < SOCKET_LIMIT_MIN)
+ gMaxCount = SOCKET_LIMIT_MIN;
+ else
+ gMaxCount = index;
+ break;
+ }
+ ++numAllocated;
+ }
+
+ // Test
+ static_assert(SOCKET_LIMIT_MIN >= 32U, "Minimum Socket Limit is >= 32");
+ while (gMaxCount <= numAllocated) {
+ int32_t rv = PR_Poll(pfd, gMaxCount, PR_MillisecondsToInterval(0));
+
+ SOCKET_LOG(("Socket Limit Test poll() size=%d rv=%d\n", gMaxCount, rv));
+
+ if (rv >= 0) break;
+
+ SOCKET_LOG(("Socket Limit Test poll confirmationSize=%d rv=%d error=%d\n",
+ gMaxCount, rv, PR_GetError()));
+
+ gMaxCount -= 32;
+ if (gMaxCount <= SOCKET_LIMIT_MIN) {
+ gMaxCount = SOCKET_LIMIT_MIN;
+ break;
+ }
+ }
+
+ // Free
+ for (uint32_t index = 0; index < numAllocated; ++index)
+ if (pfd[index].fd) PR_Close(pfd[index].fd);
+
+ Telemetry::Accumulate(Telemetry::NETWORK_PROBE_MAXCOUNT, gMaxCount);
+ SOCKET_LOG(("Socket Limit Test max was confirmed at %d\n", gMaxCount));
+}
+#endif // windows
+
+PRStatus nsSocketTransportService::DiscoverMaxCount() {
+ gMaxCount = SOCKET_LIMIT_MIN;
+
+#if defined(XP_UNIX) && !defined(AIX) && !defined(NEXTSTEP) && !defined(QNX)
+ // On unix and os x network sockets and file
+ // descriptors are the same. OS X comes defaulted at 256,
+ // most linux at 1000. We can reliably use [sg]rlimit to
+ // query that and raise it if needed.
+
+ struct rlimit rlimitData;
+ if (getrlimit(RLIMIT_NOFILE, &rlimitData) == -1) // rlimit broken - use min
+ return PR_SUCCESS;
+
+ if (rlimitData.rlim_cur >= SOCKET_LIMIT_TARGET) { // larger than target!
+ gMaxCount = SOCKET_LIMIT_TARGET;
+ return PR_SUCCESS;
+ }
+
+ int32_t maxallowed = rlimitData.rlim_max;
+ if ((uint32_t)maxallowed <= SOCKET_LIMIT_MIN) {
+ return PR_SUCCESS; // so small treat as if rlimit is broken
+ }
+
+ if ((maxallowed == -1) || // no hard cap - ok to set target
+ ((uint32_t)maxallowed >= SOCKET_LIMIT_TARGET)) {
+ maxallowed = SOCKET_LIMIT_TARGET;
+ }
+
+ rlimitData.rlim_cur = maxallowed;
+ setrlimit(RLIMIT_NOFILE, &rlimitData);
+ if ((getrlimit(RLIMIT_NOFILE, &rlimitData) != -1) &&
+ (rlimitData.rlim_cur > SOCKET_LIMIT_MIN)) {
+ gMaxCount = rlimitData.rlim_cur;
+ }
+
+#elif defined(XP_WIN) && !defined(WIN_CE)
+ // >= XP is confirmed to have at least 1000
+ static_assert(SOCKET_LIMIT_TARGET <= 1000,
+ "SOCKET_LIMIT_TARGET max value is 1000");
+ gMaxCount = SOCKET_LIMIT_TARGET;
+#else
+ // other platforms are harder to test - so leave at safe legacy value
+#endif
+
+ return PR_SUCCESS;
+}
+
+// Used to return connection info to Dashboard.cpp
+void nsSocketTransportService::AnalyzeConnection(nsTArray<SocketInfo>* data,
+ struct SocketContext* context,
+ bool aActive) {
+ if (context->mHandler->mIsPrivate) {
+ return;
+ }
+ PRFileDesc* aFD = context->mFD;
+
+ PRFileDesc* idLayer = PR_GetIdentitiesLayer(aFD, PR_NSPR_IO_LAYER);
+
+ NS_ENSURE_TRUE_VOID(idLayer);
+
+ bool tcp = PR_GetDescType(idLayer) == PR_DESC_SOCKET_TCP;
+
+ PRNetAddr peer_addr;
+ PodZero(&peer_addr);
+ PRStatus rv = PR_GetPeerName(aFD, &peer_addr);
+ if (rv != PR_SUCCESS) {
+ return;
+ }
+
+ char host[64] = {0};
+ rv = PR_NetAddrToString(&peer_addr, host, sizeof(host));
+ if (rv != PR_SUCCESS) {
+ return;
+ }
+
+ uint16_t port;
+ if (peer_addr.raw.family == PR_AF_INET)
+ port = peer_addr.inet.port;
+ else
+ port = peer_addr.ipv6.port;
+ port = PR_ntohs(port);
+ uint64_t sent = context->mHandler->ByteCountSent();
+ uint64_t received = context->mHandler->ByteCountReceived();
+ SocketInfo info = {nsCString(host), sent, received, port, aActive, tcp};
+
+ data->AppendElement(info);
+}
+
+void nsSocketTransportService::GetSocketConnections(
+ nsTArray<SocketInfo>* data) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ for (uint32_t i = 0; i < mActiveCount; i++)
+ AnalyzeConnection(data, &mActiveList[i], true);
+ for (uint32_t i = 0; i < mIdleCount; i++)
+ AnalyzeConnection(data, &mIdleList[i], false);
+}
+
+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.
+ MOZ_ASSERT(gIOService->IsNetTearingDown());
+ if (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..875028c0d6
--- /dev/null
+++ b/netwerk/base/nsSocketTransportService2.h
@@ -0,0 +1,353 @@
+/* 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/Tuple.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "nsCOMPtr.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"
+
+class nsASocketHandler;
+struct PRPollDesc;
+class nsIPrefBranch;
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+//
+// set MOZ_LOG=nsSocketTransport:5
+//
+extern LazyLogModule gSocketTransportLog;
+#define SOCKET_LOG(args) MOZ_LOG(gSocketTransportLog, LogLevel::Debug, args)
+#define SOCKET_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 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_NSIDIRECTTASKDISPATCHER
+
+ 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; }
+
+ void SetNotTrustedMitmDetected() { mNotTrustedMitmDetected = true; }
+
+ // 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;
+
+ // 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;
+ // indicates whether we are currently in the process of shutting down
+ Atomic<bool> mShuttingDown;
+ Mutex 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;
+ // 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;
+ UniquePtr<PollableEvent> mPollableEvent;
+ bool mOffline;
+ bool mGoingOffline;
+
+ // Detaches all sockets.
+ void Reset(bool aGuardLocals);
+
+ nsresult ShutdownThread();
+
+ //-------------------------------------------------------------------------
+ // socket lists (socket thread only)
+ //
+ // only "active" sockets are on the poll list. the active list is kept
+ // in sync with the poll list such that:
+ //
+ // mActiveList[k].mFD == mPollList[k+1].fd
+ //
+ // where k=0,1,2,...
+ //-------------------------------------------------------------------------
+
+ struct SocketContext {
+ PRFileDesc* mFD;
+ nsASocketHandler* mHandler;
+ PRIntervalTime mPollStartEpoch; // time we started to poll this socket
+
+ public:
+ // 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();
+ };
+
+ SocketContext* mActiveList; /* mListSize entries */
+ SocketContext* mIdleList; /* mListSize entries */
+
+ uint32_t mActiveListSize;
+ uint32_t mIdleListSize;
+ uint32_t mActiveCount;
+ uint32_t mIdleCount;
+
+ nsresult DetachSocket(SocketContext*, SocketContext*);
+ nsresult AddToIdleList(SocketContext*);
+ nsresult AddToPollList(SocketContext*);
+ void RemoveFromIdleList(SocketContext*);
+ void RemoveFromPollList(SocketContext*);
+ void MoveToIdleList(SocketContext* sock);
+ void MoveToPollList(SocketContext* sock);
+
+ bool GrowActiveList();
+ bool GrowIdleList();
+ void InitMaxCount();
+
+ // Total bytes number transfered through all the sockets except active ones
+ uint64_t mSentBytesCount;
+ uint64_t mReceivedBytesCount;
+ //-------------------------------------------------------------------------
+ // poll list (socket thread only)
+ //
+ // first element of the poll list is mPollableEvent (or null if the pollable
+ // event cannot be created).
+ //-------------------------------------------------------------------------
+
+ PRPollDesc* mPollList; /* mListSize + 1 entries */
+
+ PRIntervalTime PollTimeout(
+ PRIntervalTime now); // computes ideal poll timeout
+ nsresult DoPollIteration(TimeDuration* pollDuration);
+ // perfoms a single poll iteration
+ int32_t Poll(TimeDuration* pollDuration, PRIntervalTime now);
+ // 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;
+ // Number of seconds of connection is idle before first keepalive ping.
+ int32_t mKeepaliveIdleTimeS;
+ // Number of seconds between retries should keepalive pings fail.
+ int32_t mKeepaliveRetryIntervalS;
+ // Number of keepalive probes to send.
+ int32_t mKeepaliveProbeCount;
+ // True if TCP keepalive is enabled globally.
+ bool mKeepaliveEnabledPref;
+ // Timeout of pollable event signalling.
+ TimeDuration mPollableEventTimeout;
+
+ Atomic<bool> mServingPendingQueue;
+ Atomic<int32_t, Relaxed> mMaxTimePerPollIter;
+ Atomic<PRIntervalTime, Relaxed> mMaxTimeForPrClosePref;
+ // Timestamp of the last network link change event, tracked
+ // also on child processes.
+ Atomic<PRIntervalTime, Relaxed> mLastNetworkLinkChangeTime;
+ // 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;
+ 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
+ typedef CopyableTArray<Tuple<uint16_t, uint16_t, uint16_t>> TPortRemapping;
+ 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);
+
+ // Socket thread only for dynamically adjusting max socket size
+#if defined(XP_WIN)
+ void ProbeMaxCount();
+#endif
+ bool mProbedMaxCount;
+
+ void AnalyzeConnection(nsTArray<SocketInfo>* data, SocketContext* context,
+ bool aActive);
+
+ void ClosePrivateConnections();
+ void DetachSocketWithGuard(bool aGuardLocals, SocketContext* socketList,
+ int32_t index);
+
+ void MarkTheLastElementOfPendingQueue();
+
+#if defined(XP_WIN)
+ Atomic<bool> mPolling;
+ nsCOMPtr<nsITimer> mPollRepairTimer;
+ void StartPollWatchdog();
+ void DoPollRepair();
+ void StartPolling();
+ void EndPolling();
+#endif
+
+ void TryRepairPollableEvent();
+
+ bool mNotTrustedMitmDetected;
+
+ 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..181e3c3d53
--- /dev/null
+++ b/netwerk/base/nsStandardURL.cpp
@@ -0,0 +1,3717 @@
+/* -*- 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 "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 "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"
+
+//
+// setenv MOZ_LOG nsStandardURL:5
+//
+static LazyLogModule gStandardURLLog("nsStandardURL");
+
+// The Chromium code defines its own LOG macro which we don't want
+#undef LOG
+#define LOG(args) MOZ_LOG(gStandardURLLog, LogLevel::Debug, args)
+#undef LOG_ENABLED
+#define LOG_ENABLED() MOZ_LOG_TEST(gStandardURLLog, LogLevel::Debug)
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+static NS_DEFINE_CID(kThisImplCID, NS_THIS_STANDARDURL_IMPL_CID);
+static NS_DEFINE_CID(kStandardURLCID, NS_STANDARDURL_CID);
+
+// 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. Worker threads
+// may race when reading this values, but that's OK because in the worst
+// case we will just dispatch a noop runnable to the main thread.
+bool nsStandardURL::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 == ' ' || 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 (;;) {
+ uint32_t encoderResult;
+ size_t read;
+ size_t written;
+ Tie(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;
+static LinkedList<nsStandardURL> gAllURLs;
+#endif
+
+nsStandardURL::nsStandardURL(bool aSupportsFileURL, bool aTrackURL)
+ : mDefaultPort(-1),
+ mPort(-1),
+ mDisplayHost(nullptr),
+ 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
+}
+
+// static
+void nsStandardURL::SanityCheck(const URLSegment& aSeg,
+ const nsCString& aSpec) {
+ MOZ_RELEASE_ASSERT(aSeg.mLen >= -1);
+ MOZ_RELEASE_ASSERT(aSeg.mLen < 0 ||
+ (aSeg.mPos + aSeg.mLen <= aSpec.Length() &&
+ aSeg.mPos + aSeg.mLen >= aSeg.mPos));
+}
+
+nsStandardURL::~nsStandardURL() {
+ LOG(("Destroying nsStandardURL @%p\n", this));
+
+#ifdef DEBUG_DUMP_URLS_AT_SHUTDOWN
+ {
+ StaticMutexAutoLock lock(gAllURLsMutex);
+ if (isInList()) {
+ remove();
+ }
+ }
+#endif
+
+ SanityCheck(mScheme, mSpec);
+ SanityCheck(mAuthority, mSpec);
+ SanityCheck(mUsername, mSpec);
+ SanityCheck(mPassword, mSpec);
+ SanityCheck(mHost, mSpec);
+ SanityCheck(mPath, mSpec);
+ SanityCheck(mFilepath, mSpec);
+ SanityCheck(mDirectory, mSpec);
+ SanityCheck(mBasename, mSpec);
+ SanityCheck(mExtension, mSpec);
+ SanityCheck(mQuery, mSpec);
+ SanityCheck(mRef, mSpec);
+}
+
+#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.
+// Since the trailing dot is allowed, we pass and adjust "length".
+//
+// length is assumed to be <= host.Length(); the callers 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) {
+ 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 == '.') {
+ if (!lastWasNumber) { // A dot should not follow an X or a dot, or be
+ // first
+ return -1;
+ }
+
+ if (dotCount > 0 &&
+ i == (length - 1)) { // Trailing dot is OK; shorten and return
+ length--;
+ return dotCount;
+ }
+
+ 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
+
+ // The length may be adjusted by ValidateIPv4Number (ignoring the trailing
+ // period) so use "length", rather than host.Length() after that call.
+ int32_t length = static_cast<int32_t>(host.Length());
+ int32_t dotCount =
+ ValidateIPv4Number(host, bases, dotIndex, onlyBase10, length);
+ 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);
+
+ 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;
+ }
+
+ 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));
+ }
+
+ 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 nsACString& host,
+ nsCString& result) {
+ // If host is ACE, then convert to UTF-8. Else, if host is already UTF-8,
+ // then make sure it is normalized per IDN.
+
+ // this function returns true if normalization succeeds.
+
+ result.Truncate();
+ nsresult rv;
+
+ if (!gIDN) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ bool isAscii;
+ nsAutoCString normalized;
+ rv = gIDN->ConvertToDisplayIDN(host, &isAscii, normalized);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // The result is ASCII. No need to convert to ACE.
+ if (isAscii) {
+ result = normalized;
+ mCheckedIfHostA = true;
+ mDisplayHost.Truncate();
+ return NS_OK;
+ }
+
+ rv = gIDN->ConvertUTF8toACE(normalized, result);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mCheckedIfHostA = true;
+ mDisplayHost = normalized;
+
+ 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;
+}
+
+// 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 (NS_SUCCEEDED(NormalizeIPv4(encHost, ipString))) {
+ encHost = ipString;
+ }
+ }
+
+ // NormalizeIDN always copies, if the call was successful.
+ useEncHost = true;
+ approxLen += encHost.Length();
+
+ if (!ValidIPv6orHostname(encHost.BeginReading(), encHost.Length())) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ } 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 !PL_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 !PL_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 !PL_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
+ //
+ rv = mParser->ParseURL(spec, specLen, &mScheme.mPos, &mScheme.mLen,
+ &mAuthority.mPos, &mAuthority.mLen, &mPath.mPos,
+ &mPath.mLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+#ifdef DEBUG
+ if (mScheme.mLen <= 0) {
+ printf("spec=%s\n", spec);
+ NS_WARNING("malformed url: no scheme");
+ }
+#endif
+
+ if (mAuthority.mLen > 0) {
+ rv = mParser->ParseAuthority(spec + mAuthority.mPos, mAuthority.mLen,
+ &mUsername.mPos, &mUsername.mLen,
+ &mPassword.mPos, &mPassword.mLen, &mHost.mPos,
+ &mHost.mLen, &mPort);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Don't allow mPort to be set to this URI's default port
+ if (mPort == mDefaultPort) {
+ mPort = -1;
+ }
+
+ mUsername.mPos += mAuthority.mPos;
+ mPassword.mPos += mAuthority.mPos;
+ mHost.mPos += mAuthority.mPos;
+ }
+
+ if (mPath.mLen > 0) {
+ rv = ParsePath(spec, mPath.mPos, mPath.mLen);
+ }
+
+ return rv;
+}
+
+nsresult nsStandardURL::ParsePath(const char* spec, uint32_t pathPos,
+ int32_t pathLen) {
+ LOG(("ParsePath: %s pathpos %d len %d\n", spec, pathPos, pathLen));
+
+ if (pathLen > (int32_t)StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsresult rv = mParser->ParsePath(spec + pathPos, pathLen, &mFilepath.mPos,
+ &mFilepath.mLen, &mQuery.mPos, &mQuery.mLen,
+ &mRef.mPos, &mRef.mLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mFilepath.mPos += pathPos;
+ mQuery.mPos += pathPos;
+ mRef.mPos += pathPos;
+
+ if (mFilepath.mLen > 0) {
+ rv = mParser->ParseFilePath(spec + mFilepath.mPos, mFilepath.mLen,
+ &mDirectory.mPos, &mDirectory.mLen,
+ &mBasename.mPos, &mBasename.mLen,
+ &mExtension.mPos, &mExtension.mLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mDirectory.mPos += mFilepath.mPos;
+ mBasename.mPos += mFilepath.mPos;
+ mExtension.mPos += mFilepath.mPos;
+ }
+ return NS_OK;
+}
+
+char* nsStandardURL::AppendToSubstring(uint32_t pos, int32_t len,
+ const char* tail) {
+ // Verify pos and length are within boundaries
+ if (pos > mSpec.Length()) {
+ return nullptr;
+ }
+ if (len < 0) {
+ return nullptr;
+ }
+ if ((uint32_t)len > (mSpec.Length() - pos)) {
+ return nullptr;
+ }
+ if (!tail) {
+ return nullptr;
+ }
+
+ uint32_t tailLen = strlen(tail);
+
+ // Check for int overflow for proposed length of combined string
+ if (UINT32_MAX - ((uint32_t)len + 1) < tailLen) {
+ return nullptr;
+ }
+
+ char* result = (char*)moz_xmalloc(len + tailLen + 1);
+ memcpy(result, mSpec.get() + pos, len);
+ memcpy(result + len, tail, tailLen);
+ result[len + tailLen] = '\0';
+ return result;
+}
+
+nsresult nsStandardURL::ReadSegment(nsIBinaryInputStream* stream,
+ URLSegment& seg) {
+ nsresult rv;
+
+ rv = stream->Read32(&seg.mPos);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->Read32((uint32_t*)&seg.mLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsStandardURL::WriteSegment(nsIBinaryOutputStream* stream,
+ const URLSegment& seg) {
+ nsresult rv;
+
+ rv = stream->Write32(seg.mPos);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->Write32(uint32_t(seg.mLen));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+#define SHIFT_FROM(name, what) \
+ void nsStandardURL::name(int32_t diff) { \
+ if (!diff) return; \
+ if ((what).mLen >= 0) { \
+ CheckedInt<int32_t> pos = (what).mPos; \
+ pos += diff; \
+ MOZ_ASSERT(pos.isValid()); \
+ (what).mPos = pos.value(); \
+ } 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", mScheme.mPos, mScheme.mLen));
+ LOG((" authority = (%u,%d)\n", mAuthority.mPos, mAuthority.mLen));
+ LOG((" username = (%u,%d)\n", mUsername.mPos, mUsername.mLen));
+ LOG((" password = (%u,%d)\n", mPassword.mPos, mPassword.mLen));
+ LOG((" hostname = (%u,%d)\n", mHost.mPos, mHost.mLen));
+ LOG((" path = (%u,%d)\n", mPath.mPos, mPath.mLen));
+ LOG((" filepath = (%u,%d)\n", mFilepath.mPos, mFilepath.mLen));
+ LOG((" directory = (%u,%d)\n", mDirectory.mPos, mDirectory.mLen));
+ LOG((" basename = (%u,%d)\n", mBasename.mPos, mBasename.mLen));
+ LOG((" extension = (%u,%d)\n", mExtension.mPos, mExtension.mLen));
+ LOG((" query = (%u,%d)\n", mQuery.mPos, mQuery.mLen));
+ LOG((" ref = (%u,%d)\n", mRef.mPos, mRef.mLen));
+ }
+
+ return rv;
+}
+
+nsresult nsStandardURL::SetScheme(const nsACString& input) {
+ const nsPromiseFlatCString& scheme = PromiseFlatCString(input);
+
+ LOG(("nsStandardURL::SetScheme [scheme=%s]\n", scheme.get()));
+
+ if (scheme.IsEmpty()) {
+ NS_WARNING("cannot remove the scheme from an url");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (mScheme.mLen < 0) {
+ NS_WARNING("uninitialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!net_IsValidScheme(scheme)) {
+ NS_WARNING("the given url scheme contains invalid characters");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mSpec.Length() + input.Length() - Scheme().Length() >
+ StaticPrefs::network_standard_url_max_length()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ InvalidateCache();
+
+ int32_t shift = ReplaceSegment(mScheme.mPos, mScheme.mLen, scheme);
+
+ if (shift) {
+ mScheme.mLen = scheme.Length();
+ ShiftFromAuthority(shift);
+ }
+
+ // ensure new scheme is lowercase
+ //
+ // XXX the string code unfortunately doesn't provide a ToLowerCase
+ // that operates on a substring.
+ net_ToLowerCase((char*)mSpec.get(), mScheme.mLen);
+ return NS_OK;
+}
+
+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;
+ }
+
+ 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;
+ }
+
+ 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
+ });
+
+ 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;
+ }
+ }
+
+ 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) {
+ const nsPromiseFlatCString& hostname = PromiseFlatCString(input);
+
+ nsACString::const_iterator start, end;
+ hostname.BeginReading(start);
+ hostname.EndReading(end);
+
+ FindHostLimit(start, end);
+
+ const nsCString unescapedHost(Substring(start, end));
+ // Do percent decoding on the the input.
+ nsAutoCString flat;
+ NS_UnescapeURL(unescapedHost.BeginReading(), unescapedHost.Length(),
+ esc_AlwaysCopy | esc_Host, flat);
+ const char* host = flat.get();
+
+ LOG(("nsStandardURL::SetHost [host=%s]\n", host));
+
+ if (mURLType == URLTYPE_NO_AUTHORITY) {
+ if (flat.IsEmpty()) {
+ return NS_OK;
+ }
+ NS_WARNING("cannot set host on no-auth url");
+ return NS_ERROR_UNEXPECTED;
+ }
+ 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;
+ }
+
+ 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 (NS_SUCCEEDED(NormalizeIPv4(hostBuf, ipString))) {
+ hostBuf = ipString;
+ }
+ }
+
+ // NormalizeIDN always copies if the call was successful
+ host = hostBuf.get();
+ len = hostBuf.Length();
+
+ if (!ValidIPv6orHostname(host, len)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (mHost.mLen < 0) {
+ int port_length = 0;
+ if (mPort != -1) {
+ nsAutoCString buf;
+ buf.Assign(':');
+ buf.AppendInt(mPort);
+ port_length = buf.Length();
+ }
+ if (mAuthority.mLen > 0) {
+ mHost.mPos = mAuthority.mPos + mAuthority.mLen - port_length;
+ mHost.mLen = 0;
+ } else if (mScheme.mLen > 0) {
+ mHost.mPos = mScheme.mPos + mScheme.mLen + 3;
+ mHost.mLen = 0;
+ }
+ }
+
+ int32_t shift = ReplaceSegment(mHost.mPos, mHost.mLen, host, len);
+
+ if (shift) {
+ mHost.mLen = len;
+ mAuthority.mLen += shift;
+ ShiftFromPath(shift);
+ }
+
+ // Now canonicalize the host to lowercase
+ net_ToLowerCase(mSpec.BeginWriting() + mHost.mPos, mHost.mLen);
+ return NS_OK;
+}
+
+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;
+ }
+
+ 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");
+
+ // 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()));
+
+ 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) {
+ 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
+ rv = mParser->ParseURL(relpath, relpathLen, &scheme.mPos, &scheme.mLen,
+ nullptr, nullptr, nullptr, nullptr);
+
+ // if the parser fails (for example because there is no valid scheme)
+ // reset the scheme and assume a relative url
+ if (NS_FAILED(rv)) {
+ scheme.Reset();
+ }
+
+ 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 = PL_strstr(result, "://");
+ if (resultPath) {
+ resultPath = PL_strchr(resultPath + 3, '/');
+ if (resultPath) {
+ net_CoalesceDirs(coalesceFlag, resultPath);
+ }
+ }
+ }
+ out.Adopt(result);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetCommonBaseSpec(nsIURI* uri2, nsACString& aResult) {
+ NS_ENSURE_ARG_POINTER(uri2);
+
+ // if uri's are equal, then return uri as is
+ bool isEquals = false;
+ if (NS_SUCCEEDED(Equals(uri2, &isEquals)) && isEquals) {
+ return GetSpec(aResult);
+ }
+
+ aResult.Truncate();
+
+ // check pre-path; if they don't match, then return empty string
+ 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;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetRef(nsACString& result) {
+ result = Ref();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandardURL::GetHasRef(bool* result) {
+ *result = (mRef.mLen >= 0);
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetDirectory(nsACString& result) {
+ result = Directory();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileName(nsACString& result) {
+ result = Filename();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileBaseName(nsACString& result) {
+ result = Basename();
+ return NS_OK;
+}
+
+// result may contain unescaped UTF-8 characters
+NS_IMETHODIMP
+nsStandardURL::GetFileExtension(nsACString& result) {
+ result = Extension();
+ return NS_OK;
+}
+
+nsresult nsStandardURL::SetFilePath(const nsACString& input) {
+ const nsPromiseFlatCString& flat = PromiseFlatCString(input);
+ const char* filepath = flat.get();
+
+ LOG(("nsStandardURL::SetFilePath [filepath=%s]\n", filepath));
+
+ // if there isn't a filepath, then there can't be anything
+ // after the path either. this url is likely uninitialized.
+ if (mFilepath.mLen < 0) {
+ return SetPathQueryRef(flat);
+ }
+
+ if (filepath && *filepath) {
+ nsAutoCString spec;
+ uint32_t dirPos, basePos, extPos;
+ int32_t dirLen, baseLen, extLen;
+ nsresult rv;
+
+ rv = mParser->ParseFilePath(filepath, flat.Length(), &dirPos, &dirLen,
+ &basePos, &baseLen, &extPos, &extLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // build up new candidate spec
+ spec.Assign(mSpec.get(), mPath.mPos);
+
+ // ensure leading '/'
+ if (filepath[dirPos] != '/') {
+ spec.Append('/');
+ }
+
+ 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));
+
+ 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 (!query || !*query) {
+ // remove existing query
+ if (mQuery.mLen >= 0) {
+ // remove query and leading '?'
+ mSpec.Cut(mQuery.mPos - 1, mQuery.mLen + 1);
+ ShiftFromRef(-(mQuery.mLen + 1));
+ mPath.mLen -= (mQuery.mLen + 1);
+ mQuery.mPos = 0;
+ mQuery.mLen = -1;
+ }
+ return NS_OK;
+ }
+
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString filteredURI(flat);
+ const ASCIIMaskArray& mask = ASCIIMask::MaskCRLFTab();
+ filteredURI.StripTaggedASCII(mask);
+
+ 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));
+
+ 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 (!ref || !*ref) {
+ // remove existing ref
+ if (mRef.mLen >= 0) {
+ // remove ref and leading '#'
+ mSpec.Cut(mRef.mPos - 1, mRef.mLen + 1);
+ mPath.mLen -= (mRef.mLen + 1);
+ mRef.mPos = 0;
+ mRef.mLen = -1;
+ }
+ return NS_OK;
+ }
+
+ // filter out unexpected chars "\r\n\t" if necessary
+ nsAutoCString filteredURI(flat);
+ const ASCIIMaskArray& mask = ASCIIMask::MaskCRLFTab();
+ filteredURI.StripTaggedASCII(mask);
+
+ 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));
+
+ 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;
+ URLSegment basename, extension;
+
+ // let the parser locate the basename and extension
+ rv = mParser->ParseFileName(filename, flat.Length(), &basename.mPos,
+ &basename.mLen, &extension.mPos,
+ &extension.mLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (basename.mLen < 0) {
+ // remove existing filename
+ if (mBasename.mLen >= 0) {
+ uint32_t len = mBasename.mLen;
+ if (mExtension.mLen >= 0) {
+ len += (mExtension.mLen + 1);
+ }
+ mSpec.Cut(mBasename.mPos, len);
+ shift = -int32_t(len);
+ mBasename.mLen = 0;
+ mExtension.mLen = -1;
+ }
+ } else {
+ nsAutoCString newFilename;
+ bool ignoredOut;
+ 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) {
+ 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) {
+ 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");
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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);
+
+ 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);
+}
diff --git a/netwerk/base/nsStandardURL.h b/netwerk/base/nsStandardURL.h
new file mode 100644
index 0000000000..6efb58648d
--- /dev/null
+++ b/netwerk/base/nsStandardURL.h
@@ -0,0 +1,550 @@
+/* -*- 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 "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 {
+
+//-----------------------------------------------------------------------------
+// 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 {
+ uint32_t mPos;
+ int32_t mLen;
+
+ URLSegment() : mPos(0), mLen(-1) {}
+ URLSegment(uint32_t pos, int32_t len) : mPos(pos), mLen(len) {}
+ URLSegment(const URLSegment& aCopy) = 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 nsACString& 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
+ const nsDependentCSubstring Segment(uint32_t pos, int32_t len); // see below
+ const nsDependentCSubstring Segment(const URLSegment& s) {
+ return Segment(s.mPos, s.mLen);
+ }
+
+ // dependent substring getters
+ const nsDependentCSubstring Prepath(); // see below
+ const nsDependentCSubstring Scheme() { return Segment(mScheme); }
+ const nsDependentCSubstring Userpass(bool includeDelim = false); // see below
+ const nsDependentCSubstring Username() { return Segment(mUsername); }
+ const nsDependentCSubstring Password() { return Segment(mPassword); }
+ const nsDependentCSubstring Hostport(); // see below
+ const nsDependentCSubstring Host(); // see below
+ const nsDependentCSubstring Path() { return Segment(mPath); }
+ const nsDependentCSubstring Filepath() { return Segment(mFilepath); }
+ const nsDependentCSubstring Directory() { return Segment(mDirectory); }
+ const nsDependentCSubstring Filename(); // see below
+ const nsDependentCSubstring Basename() { return Segment(mBasename); }
+ const nsDependentCSubstring Extension() { return Segment(mExtension); }
+ const nsDependentCSubstring Query() { return Segment(mQuery); }
+ const nsDependentCSubstring Ref() { return Segment(mRef); }
+
+ // shift the URLSegments to the right by diff
+ void ShiftFromAuthority(int32_t diff);
+ void ShiftFromUsername(int32_t diff);
+ void ShiftFromPassword(int32_t diff);
+ void ShiftFromHost(int32_t diff);
+ void ShiftFromPath(int32_t diff);
+ void ShiftFromFilepath(int32_t diff);
+ void ShiftFromDirectory(int32_t diff);
+ void ShiftFromBasename(int32_t diff);
+ void ShiftFromExtension(int32_t diff);
+ void ShiftFromQuery(int32_t diff);
+ void ShiftFromRef(int32_t diff);
+
+ // fastload helper functions
+ nsresult ReadSegment(nsIBinaryInputStream*, URLSegment&);
+ nsresult WriteSegment(nsIBinaryOutputStream*, const URLSegment&);
+
+ void FindHostLimit(nsACString::const_iterator& aStart,
+ nsACString::const_iterator& aEnd);
+
+ // Asserts that the URLSegment has sane values
+ static void SanityCheck(const URLSegment&, const nsCString&);
+
+ // mSpec contains the normalized version of the URL spec (UTF-8 encoded).
+ nsCString mSpec;
+ int32_t mDefaultPort;
+ int32_t mPort;
+
+ // url parts (relative to mSpec)
+ URLSegment mScheme;
+ URLSegment mAuthority;
+ URLSegment mUsername;
+ URLSegment mPassword;
+ URLSegment mHost;
+ URLSegment mPath;
+ URLSegment mFilepath;
+ URLSegment mDirectory;
+ URLSegment mBasename;
+ URLSegment mExtension;
+ URLSegment mQuery;
+ URLSegment mRef;
+
+ 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[];
+ static bool gInitialized;
+
+ 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() : mMarkedFileURL(false) {}
+
+ 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 const nsDependentCSubstring nsStandardURL::Segment(uint32_t pos,
+ int32_t len) {
+ if (len < 0) {
+ pos = 0;
+ len = 0;
+ }
+ return Substring(mSpec, pos, uint32_t(len));
+}
+
+inline const nsDependentCSubstring nsStandardURL::Prepath() {
+ uint32_t len = 0;
+ if (mAuthority.mLen >= 0) len = mAuthority.mPos + mAuthority.mLen;
+ return Substring(mSpec, 0, len);
+}
+
+inline const nsDependentCSubstring nsStandardURL::Userpass(bool includeDelim) {
+ uint32_t pos = 0, len = 0;
+ if (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 const nsDependentCSubstring nsStandardURL::Hostport() {
+ uint32_t pos = 0, len = 0;
+ if (mAuthority.mLen > 0) {
+ pos = mHost.mPos;
+ len = mAuthority.mPos + mAuthority.mLen - pos;
+ }
+ return Substring(mSpec, pos, len);
+}
+
+inline const nsDependentCSubstring nsStandardURL::Host() {
+ uint32_t pos = 0, len = 0;
+ if (mHost.mLen > 0) {
+ pos = mHost.mPos;
+ len = mHost.mLen;
+ if (mSpec.CharAt(pos) == '[' && mSpec.CharAt(pos + len - 1) == ']') {
+ pos++;
+ len -= 2;
+ }
+ }
+ return Substring(mSpec, pos, len);
+}
+
+inline const nsDependentCSubstring nsStandardURL::Filename() {
+ uint32_t pos = 0, len = 0;
+ // if there is no basename, then there can be no extension
+ if (mBasename.mLen > 0) {
+ pos = mBasename.mPos;
+ len = mBasename.mLen;
+ if (mExtension.mLen >= 0) len += (mExtension.mLen + 1);
+ }
+ return Substring(mSpec, pos, len);
+}
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsStandardURL_h__
diff --git a/netwerk/base/nsStreamListenerTee.cpp b/netwerk/base/nsStreamListenerTee.cpp
new file mode 100644
index 0000000000..308b21e841
--- /dev/null
+++ b/netwerk/base/nsStreamListenerTee.cpp
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamListenerTee.h"
+#include "nsProxyRelease.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsStreamListenerTee, nsIStreamListener, nsIRequestObserver,
+ nsIStreamListenerTee, nsIThreadRetargetableStreamListener,
+ 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 (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::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..0b54bbf5dd
--- /dev/null
+++ b/netwerk/base/nsStreamListenerTee.h
@@ -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/. */
+
+#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 nsIThreadRetargetableStreamListener,
+ 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..d72a5c66e4
--- /dev/null
+++ b/netwerk/base/nsStreamListenerWrapper.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 "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)) {
+ return listener->OnAfterLastPart(aStatus);
+ }
+ 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;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamListenerWrapper.h b/netwerk/base/nsStreamListenerWrapper.h
new file mode 100644
index 0000000000..9871adf71c
--- /dev/null
+++ b/netwerk/base/nsStreamListenerWrapper.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamListenerWrapper_h__
+#define nsStreamListenerWrapper_h__
+
+#include "nsCOMPtr.h"
+#include "nsIStreamListener.h"
+#include "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 nsIStreamListener,
+ 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_NSIREQUESTOBSERVER(mListener)
+ NS_FORWARD_SAFE_NSISTREAMLISTENER(mListener)
+ NS_DECL_NSIMULTIPARTCHANNELLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ private:
+ ~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..e72384d903
--- /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 "GeckoProfiler.h"
+
+#include <limits>
+
+namespace mozilla {
+namespace net {
+
+nsStreamLoader::nsStreamLoader() : mData() {}
+
+NS_IMETHODIMP
+nsStreamLoader::Init(nsIStreamLoaderObserver* aStreamObserver,
+ nsIRequestObserver* aRequestObserver) {
+ NS_ENSURE_ARG_POINTER(aStreamObserver);
+ mObserver = aStreamObserver;
+ mRequestObserver = aRequestObserver;
+ return NS_OK;
+}
+
+nsresult nsStreamLoader::Create(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ if (aOuter) return NS_ERROR_NO_AGGREGATION;
+
+ 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; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsStreamLoader.h b/netwerk/base/nsStreamLoader.h
new file mode 100644
index 0000000000..645260644c
--- /dev/null
+++ b/netwerk/base/nsStreamLoader.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamLoader_h__
+#define nsStreamLoader_h__
+
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIStreamLoader.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Vector.h"
+
+class nsIRequest;
+
+namespace mozilla {
+namespace net {
+
+class nsStreamLoader final : public nsIStreamLoader,
+ public nsIThreadRetargetableStreamListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMLOADER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ nsStreamLoader();
+
+ static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+ protected:
+ ~nsStreamLoader() = 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..8a528df2d6
--- /dev/null
+++ b/netwerk/base/nsStreamTransportService.cpp
@@ -0,0 +1,408 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsStreamTransportService.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsNetSegmentUtils.h"
+#include "nsTransportUtils.h"
+#include "nsStreamUtils.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "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)
+ : mMutex("nsInputStreamTransport::mMutex"),
+ mSource(source),
+ mOffset(0),
+ mCloseWhenDone(closeWhenDone),
+ mInProgress(false) {
+ mAsyncSource = do_QueryInterface(mSource);
+ }
+
+ private:
+ virtual ~nsInputStreamTransport() = default;
+
+ Mutex 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;
+ 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;
+};
+
+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;
+ rv = NS_NewPipe2(getter_AddRefs(mPipeIn), getter_AddRefs(pipeOut),
+ nonblocking, true, segsize, segcount);
+ if (NS_FAILED(rv)) return rv;
+
+ mInProgress = true;
+
+ // startup async copy process...
+ rv = NS_AsyncCopy(this, pipeOut, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS,
+ segsize);
+ if (NS_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::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 (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() {
+ NS_ASSERTION(!mPool, "thread pool wasn't shutdown");
+}
+
+nsresult nsStreamTransportService::Init() {
+ mPool = new nsThreadPool();
+
+ // Configure the pool
+ mPool->SetName("StreamTrans"_ns);
+ mPool->SetThreadLimit(25);
+ mPool->SetIdleThreadLimit(5);
+ mPool->SetIdleThreadTimeoutRegressive(true);
+ mPool->SetIdleThreadTimeout(PR_SecondsToInterval(30));
+
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsStreamTransportService, nsIStreamTransportService,
+ nsIEventTarget, nsIObserver)
+
+NS_IMETHODIMP
+nsStreamTransportService::DispatchFromScript(nsIRunnable* task,
+ uint32_t flags) {
+ nsCOMPtr<nsIRunnable> event(task);
+ return Dispatch(event.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::Dispatch(already_AddRefed<nsIRunnable> task,
+ uint32_t flags) {
+ nsCOMPtr<nsIRunnable> event(task); // so it gets released on failure paths
+ nsCOMPtr<nsIThreadPool> pool;
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ if (mIsShutdown) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ pool = mPool;
+ }
+ NS_ENSURE_TRUE(pool, NS_ERROR_NOT_INITIALIZED);
+ return pool->Dispatch(event.forget(), flags);
+}
+
+NS_IMETHODIMP
+nsStreamTransportService::DelayedDispatch(already_AddRefed<nsIRunnable>,
+ uint32_t) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP_(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");
+
+ {
+ mozilla::MutexAutoLock lock(mShutdownLock);
+ mIsShutdown = true;
+ }
+
+ if (mPool) {
+ mPool->Shutdown();
+ mPool = nullptr;
+ }
+ 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 = GetCurrentEventTarget();
+ }
+
+ 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..187db4f87c
--- /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 "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+
+class nsIThreadPool;
+
+namespace mozilla {
+namespace net {
+
+class nsStreamTransportService final : public nsIStreamTransportService,
+ public nsIEventTarget,
+ public nsIObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMTRANSPORTSERVICE
+ NS_DECL_NSIEVENTTARGET_FULL
+ NS_DECL_NSIOBSERVER
+
+ nsresult Init();
+
+ nsStreamTransportService()
+ : mShutdownLock("nsStreamTransportService.mShutdownLock"),
+ mIsShutdown(false) {}
+
+ private:
+ ~nsStreamTransportService();
+
+ nsCOMPtr<nsIThreadPool> mPool;
+
+ mozilla::Mutex mShutdownLock;
+ bool mIsShutdown;
+};
+
+} // namespace net
+} // namespace mozilla
+#endif
diff --git a/netwerk/base/nsSyncStreamListener.cpp b/netwerk/base/nsSyncStreamListener.cpp
new file mode 100644
index 0000000000..9bfcd63d67
--- /dev/null
+++ b/netwerk/base/nsSyncStreamListener.cpp
@@ -0,0 +1,167 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsIOService.h"
+#include "nsSyncStreamListener.h"
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+using namespace mozilla::net;
+
+nsresult nsSyncStreamListener::Init() {
+ return NS_NewPipe(getter_AddRefs(mPipeIn), getter_AddRefs(mPipeOut),
+ mozilla::net::nsIOService::gDefaultSegmentSize,
+ UINT32_MAX, // no size limit
+ false, false);
+}
+
+// static
+already_AddRefed<nsISyncStreamListener> nsSyncStreamListener::Create() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<nsSyncStreamListener> inst = new nsSyncStreamListener();
+ nsresult rv = inst->Init();
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return inst.forget();
+}
+
+nsresult nsSyncStreamListener::WaitForData() {
+ mKeepWaiting = true;
+
+ if (!mozilla::SpinEventLoopUntil([&]() { 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::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..8ed2b5c21a
--- /dev/null
+++ b/netwerk/base/nsSyncStreamListener.h
@@ -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/. */
+
+#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
+
+ static already_AddRefed<nsISyncStreamListener> Create();
+
+ private:
+ nsSyncStreamListener() : mStatus(NS_OK), mKeepWaiting(false), mDone(false) {}
+ ~nsSyncStreamListener() = default;
+
+ nsresult Init();
+
+ nsresult WaitForData();
+
+ nsCOMPtr<nsIInputStream> mPipeIn;
+ nsCOMPtr<nsIOutputStream> mPipeOut;
+ nsresult mStatus;
+ bool mKeepWaiting;
+ bool mDone;
+};
+
+#endif // nsSyncStreamListener_h__
diff --git a/netwerk/base/nsTransportUtils.cpp b/netwerk/base/nsTransportUtils.cpp
new file mode 100644
index 0000000000..4b965a4fdb
--- /dev/null
+++ b/netwerk/base/nsTransportUtils.cpp
@@ -0,0 +1,126 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Mutex.h"
+#include "nsTransportUtils.h"
+#include "nsITransport.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+
+using namespace mozilla;
+
+//-----------------------------------------------------------------------------
+
+class nsTransportStatusEvent;
+
+class nsTransportEventSinkProxy : public nsITransportEventSink {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORTEVENTSINK
+
+ nsTransportEventSinkProxy(nsITransportEventSink* sink, nsIEventTarget* target)
+ : mSink(sink),
+ mTarget(target),
+ mLock("nsTransportEventSinkProxy.mLock"),
+ mLastEvent(nullptr) {
+ NS_ADDREF(mSink);
+ }
+
+ private:
+ virtual ~nsTransportEventSinkProxy() {
+ // our reference to mSink could be the last, so be sure to release
+ // it on the target thread. otherwise, we could get into trouble.
+ NS_ProxyRelease("nsTransportEventSinkProxy::mSink", mTarget,
+ dont_AddRef(mSink));
+ }
+
+ public:
+ nsITransportEventSink* mSink;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ Mutex mLock;
+ nsTransportStatusEvent* mLastEvent;
+};
+
+class nsTransportStatusEvent : public Runnable {
+ public:
+ nsTransportStatusEvent(nsTransportEventSinkProxy* proxy,
+ nsITransport* transport, nsresult status,
+ int64_t progress, int64_t progressMax)
+ : Runnable("nsTransportStatusEvent"),
+ mProxy(proxy),
+ mTransport(transport),
+ mStatus(status),
+ mProgress(progress),
+ mProgressMax(progressMax) {}
+
+ ~nsTransportStatusEvent() = default;
+
+ 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..e9e3541d00
--- /dev/null
+++ b/netwerk/base/nsUDPSocket.cpp
@@ -0,0 +1,1455 @@
+/* vim:set ts=2 sw=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/Telemetry.h"
+
+#include "nsSocketTransport2.h"
+#include "nsUDPSocket.h"
+#include "nsProxyRelease.h"
+#include "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 "nsWrapperCacheInlines.h"
+
+namespace mozilla {
+namespace net {
+
+static const uint32_t UDP_PACKET_CHUNK_SIZE = 1400;
+
+//-----------------------------------------------------------------------------
+
+typedef void (nsUDPSocket::*nsUDPSocketFunc)(void);
+
+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, 0,
+ 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() && !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::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);
+ NS_IF_ADDREF(*aOutputStream = mOutputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPMessage::GetRawData(JSContext* cx, JS::MutableHandleValue aRawData) {
+ if (!mJsobj) {
+ mJsobj =
+ dom::Uint8Array::Create(cx, nullptr, mData.Length(), mData.Elements());
+ HoldJSObjects(this);
+ }
+ aRawData.setObject(*mJsobj);
+ return NS_OK;
+}
+
+FallibleTArray<uint8_t>& nsUDPMessage::GetDataAsTArray() { return mData; }
+
+//-----------------------------------------------------------------------------
+// nsUDPSocket
+//-----------------------------------------------------------------------------
+
+nsUDPSocket::nsUDPSocket()
+ : mLock("nsUDPSocket.mLock"),
+ mFD(nullptr),
+ mOriginAttributes(),
+ mAttached(false),
+ mByteReadCount(0),
+ mByteWriteCount(0) {
+ this->mAddr.inet = {};
+ mAddr.raw.family = PR_AF_UNSPEC;
+ // we want to be able to access the STS directly, and it may not have been
+ // constructed yet. the STS constructor sets gSocketTransportService.
+ if (!gSocketTransportService) {
+ // This call can fail if we're offline, for example.
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ }
+
+ mSts = gSocketTransportService;
+}
+
+nsUDPSocket::~nsUDPSocket() { CloseSocket(); }
+
+void nsUDPSocket::AddOutputBytes(uint64_t aBytes) { mByteWriteCount += 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::MutableHandleValue aRawData) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+UDPMessageProxy::GetOutputStream(nsIOutputStream** aOutputStream) {
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ NS_IF_ADDREF(*aOutputStream = mOutputStream);
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+//-----------------------------------------------------------------------------
+// nsUDPSocket::nsASocketHandler
+//-----------------------------------------------------------------------------
+
+void nsUDPSocket::OnSocketReady(PRFileDesc* fd, int16_t outFlags) {
+ 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;
+ }
+
+ 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;
+
+ 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);
+ nsresult rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut),
+ true, true, segsize, segcount);
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ RefPtr<nsUDPOutputStream> os = new nsUDPOutputStream(this, mFD, prClientAddr);
+ rv = NS_AsyncCopy(pipeIn, os, mSts, NS_ASYNCCOPY_VIA_READSEGMENTS,
+ UDP_PACKET_CHUNK_SIZE);
+
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ NetAddr netAddr(&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 (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();
+}
+
+//-----------------------------------------------------------------------------
+// nsSocket::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsUDPSocket, nsIUDPSocket)
+
+//-----------------------------------------------------------------------------
+// nsSocket::nsISocket
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsUDPSocket::Init(int32_t aPort, bool aLoopbackOnly, nsIPrincipal* aPrincipal,
+ bool aAddressReuse, uint8_t aOptionalArgc) {
+ NetAddr addr;
+
+ if (aPort < 0) aPort = 0;
+
+ addr.raw.family = AF_INET;
+ addr.inet.port = htons(aPort);
+
+ if (aLoopbackOnly)
+ addr.inet.ip = htonl(INADDR_LOOPBACK);
+ else
+ addr.inet.ip = htonl(INADDR_ANY);
+
+ return InitWithAddress(&addr, aPrincipal, aAddressReuse, aOptionalArgc);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::Init2(const nsACString& aAddr, int32_t aPort,
+ nsIPrincipal* aPrincipal, bool aAddressReuse,
+ uint8_t aOptionalArgc) {
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PRNetAddr prAddr;
+ memset(&prAddr, 0, sizeof(prAddr));
+ if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aPort < 0) {
+ aPort = 0;
+ }
+
+ switch (prAddr.raw.family) {
+ case PR_AF_INET:
+ prAddr.inet.port = PR_htons(aPort);
+ break;
+ case PR_AF_INET6:
+ prAddr.ipv6.port = PR_htons(aPort);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Dont accept address other than IPv4 and IPv6");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ NetAddr addr;
+ PRNetAddrToNetAddr(&prAddr, &addr);
+
+ 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;
+ }
+
+ 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) {
+ // 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(GetCurrentEventTarget()) {}
+
+ 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(GetCurrentEventTarget()) {}
+
+ 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, &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, &count);
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMETHODIMP
+nsUDPSocket::AsyncListen(nsIUDPSocketListener* aListener) {
+ // ensuring mFD implies ensuring mLock
+ NS_ENSURE_TRUE(mFD, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mListener == nullptr, NS_ERROR_IN_PROGRESS);
+ {
+ MutexAutoLock lock(mLock);
+ mListenerTarget = GetCurrentEventTarget();
+ 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::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, _retval);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SendWithAddress(const NetAddr* aAddr,
+ const nsTArray<uint8_t>& aData,
+ uint32_t* _retval) {
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = 0;
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(aAddr, &prAddr);
+
+ bool onSTSThread = false;
+ mSts->IsOnCurrentThread(&onSTSThread);
+
+ if (onSTSThread) {
+ MutexAutoLock lock(mLock);
+ if (!mFD) {
+ // socket is not initialized or has been closed
+ return NS_ERROR_FAILURE;
+ }
+ int32_t count =
+ PR_SendTo(mFD, aData.Elements(), sizeof(uint8_t) * aData.Length(), 0,
+ &prAddr, PR_INTERVAL_NO_WAIT);
+ if (count < 0) {
+ PRErrorCode code = PR_GetError();
+ return ErrorAccordingToNSPR(code);
+ }
+ this->AddOutputBytes(count);
+ *_retval = count;
+ } else {
+ FallibleTArray<uint8_t> fallibleArray;
+ if (!fallibleArray.InsertElementsAt(0, aData, 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 = aData.Length();
+ }
+ 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);
+}
+
+nsresult nsUDPSocket::SetSocketOption(const PRSocketOptionData& aOpt) {
+ bool onSTSThread = false;
+ mSts->IsOnCurrentThread(&onSTSThread);
+
+ if (!onSTSThread) {
+ // Dispatch to STS thread and re-enter this method there
+ nsCOMPtr<nsIRunnable> runnable = new SetSocketOptionRunnable(this, aOpt);
+ nsresult rv = mSts->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (PR_SetSocketOption(mFD, &aOpt) != PR_SUCCESS) {
+ UDPSOCKET_LOG(
+ ("nsUDPSocket::SetSocketOption [this=%p] failed for type %d, "
+ "error %d\n",
+ this, aOpt.option, PR_GetError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::JoinMulticast(const nsACString& aAddr, const nsACString& aIface) {
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return JoinMulticastInternal(prAddr, prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::JoinMulticastAddr(const NetAddr aAddr, const NetAddr* aIface) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(&aAddr, &prAddr);
+
+ PRNetAddr prIface;
+ if (!aIface) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ NetAddrToPRNetAddr(aIface, &prIface);
+ }
+
+ return JoinMulticastInternal(prAddr, prIface);
+}
+
+nsresult nsUDPSocket::JoinMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface) {
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_AddMember;
+ opt.value.add_member.mcaddr = aAddr;
+ opt.value.add_member.ifaddr = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::LeaveMulticast(const nsACString& aAddr, const nsACString& aIface) {
+ if (NS_WARN_IF(aAddr.IsEmpty())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ if (PR_StringToNetAddr(aAddr.BeginReading(), &prAddr) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return LeaveMulticastInternal(prAddr, prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::LeaveMulticastAddr(const NetAddr aAddr, const NetAddr* aIface) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prAddr;
+ NetAddrToPRNetAddr(&aAddr, &prAddr);
+
+ PRNetAddr prIface;
+ if (!aIface) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ NetAddrToPRNetAddr(aIface, &prIface);
+ }
+
+ return LeaveMulticastInternal(prAddr, prIface);
+}
+
+nsresult nsUDPSocket::LeaveMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface) {
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_DropMember;
+ opt.value.drop_member.mcaddr = aAddr;
+ opt.value.drop_member.ifaddr = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastLoopback(bool* aLoopback) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastLoopback(bool aLoopback) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_McastLoopback;
+ opt.value.mcast_loopback = aLoopback;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetRecvBufferSize(int* size) {
+ // Bug 1252759 - missing support for GetSocketOption
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetRecvBufferSize(int size) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_RecvBufferSize;
+ opt.value.recv_buffer_size = size;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetSendBufferSize(int* size) {
+ // Bug 1252759 - missing support for GetSocketOption
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetSendBufferSize(int size) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_SendBufferSize;
+ opt.value.send_buffer_size = size;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastInterface(nsACString& aIface) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::GetMulticastInterfaceAddr(NetAddr* aIface) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastInterface(const nsACString& aIface) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prIface;
+ if (aIface.IsEmpty()) {
+ PR_InitializeNetAddr(PR_IpAddrAny, 0, &prIface);
+ } else {
+ if (PR_StringToNetAddr(aIface.BeginReading(), &prIface) != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return SetMulticastInterfaceInternal(prIface);
+}
+
+NS_IMETHODIMP
+nsUDPSocket::SetMulticastInterfaceAddr(NetAddr aIface) {
+ if (NS_WARN_IF(!mFD)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ PRNetAddr prIface;
+ NetAddrToPRNetAddr(&aIface, &prIface);
+
+ return SetMulticastInterfaceInternal(prIface);
+}
+
+nsresult nsUDPSocket::SetMulticastInterfaceInternal(const PRNetAddr& aIface) {
+ PRSocketOptionData opt;
+
+ opt.option = PR_SockOpt_McastInterface;
+ opt.value.mcast_if = aIface;
+
+ nsresult rv = SetSocketOption(opt);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/nsUDPSocket.h b/netwerk/base/nsUDPSocket.h
new file mode 100644
index 0000000000..77ca7e32f2
--- /dev/null
+++ b/netwerk/base/nsUDPSocket.h
@@ -0,0 +1,113 @@
+/* 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 "nsCycleCollectionParticipant.h"
+
+//-----------------------------------------------------------------------------
+
+namespace mozilla {
+namespace net {
+
+class nsUDPSocket final : public nsASocketHandler, public nsIUDPSocket {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKET
+
+ // nsASocketHandler methods:
+ virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override;
+ virtual void OnSocketDetached(PRFileDesc* fd) override;
+ virtual void IsLocal(bool* aIsLocal) override;
+
+ uint64_t ByteCountSent() override { return mByteWriteCount; }
+ uint64_t ByteCountReceived() override { return mByteReadCount; }
+
+ void AddOutputBytes(uint64_t aBytes);
+
+ nsUDPSocket();
+
+ private:
+ virtual ~nsUDPSocket();
+
+ void OnMsgClose();
+ void OnMsgAttach();
+
+ // try attaching our socket (mFD) to the STS's poll list.
+ nsresult TryAttach();
+
+ friend class SetSocketOptionRunnable;
+ nsresult SetSocketOption(const PRSocketOptionData& aOpt);
+ nsresult JoinMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface);
+ nsresult LeaveMulticastInternal(const PRNetAddr& aAddr,
+ const PRNetAddr& aIface);
+ nsresult SetMulticastInterfaceInternal(const PRNetAddr& aIface);
+
+ void CloseSocket();
+
+ // lock protects access to mListener;
+ // so mListener is not cleared while being used/locked.
+ Mutex mLock;
+ PRFileDesc* mFD;
+ NetAddr mAddr;
+ OriginAttributes mOriginAttributes;
+ nsCOMPtr<nsIUDPSocketListener> mListener;
+ nsCOMPtr<nsIEventTarget> mListenerTarget;
+ bool mAttached;
+ RefPtr<nsSocketTransportService> mSts;
+
+ uint64_t mByteReadCount;
+ uint64_t mByteWriteCount;
+};
+
+//-----------------------------------------------------------------------------
+
+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..2ddee86603
--- /dev/null
+++ b/netwerk/base/nsURLHelper.cpp
@@ -0,0 +1,1135 @@
+/* -*- 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"
+
+using namespace mozilla;
+
+//----------------------------------------------------------------------------
+// Init/Shutdown
+//----------------------------------------------------------------------------
+
+static bool gInitialized = false;
+static nsIURLParser* gNoAuthURLParser = nullptr;
+static nsIURLParser* gAuthURLParser = nullptr;
+static nsIURLParser* gStdURLParser = nullptr;
+
+static void InitGlobals() {
+ nsCOMPtr<nsIURLParser> parser;
+
+ parser = do_GetService(NS_NOAUTHURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'noauth' url parser");
+ if (parser) {
+ gNoAuthURLParser = parser.get();
+ NS_ADDREF(gNoAuthURLParser);
+ }
+
+ parser = do_GetService(NS_AUTHURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'auth' url parser");
+ if (parser) {
+ gAuthURLParser = parser.get();
+ NS_ADDREF(gAuthURLParser);
+ }
+
+ parser = do_GetService(NS_STDURLPARSER_CONTRACTID);
+ NS_ASSERTION(parser, "failed getting 'std' url parser");
+ if (parser) {
+ gStdURLParser = parser.get();
+ NS_ADDREF(gStdURLParser);
+ }
+
+ gInitialized = true;
+}
+
+void net_ShutdownURLHelper() {
+ if (gInitialized) {
+ NS_IF_RELEASE(gNoAuthURLParser);
+ NS_IF_RELEASE(gAuthURLParser);
+ NS_IF_RELEASE(gStdURLParser);
+ gInitialized = false;
+ }
+}
+
+//----------------------------------------------------------------------------
+// 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();
+
+ auto start = input.BeginReading();
+ auto end = input.EndReading();
+
+ // Trim off leading and trailing invalid chars.
+ auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; };
+ auto newStart = std::find_if(start, end, charFilter);
+ auto newEnd =
+ std::find_if(std::reverse_iterator<decltype(end)>(end),
+ std::reverse_iterator<decltype(newStart)>(newStart),
+ charFilter)
+ .base();
+
+ // Check if chars need to be stripped.
+ bool needsStrip = false;
+ const ASCIIMaskArray& mask = ASCIIMask::MaskCRLFTab();
+ for (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,
+ nsACString& aResult) {
+ aResult.Truncate();
+
+ auto start = aInput.BeginReading();
+ auto end = aInput.EndReading();
+
+ // Trim off leading and trailing invalid chars.
+ auto charFilter = [](char c) { return static_cast<uint8_t>(c) > 0x20; };
+ auto newStart = std::find_if(start, end, charFilter);
+ auto newEnd =
+ std::find_if(std::reverse_iterator<decltype(end)>(end),
+ std::reverse_iterator<decltype(newStart)>(newStart),
+ charFilter)
+ .base();
+
+ const ASCIIMaskArray& mask = ASCIIMask::MaskCRLFTab();
+ return NS_EscapeAndFilterURL(Substring(newStart, newEnd), aFlags, &mask,
+ 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 (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. We include '(' in
+ // the trailing trim set to catch media-type comments, which are not at all
+ // standard, but may occur in rare cases.
+ const char* type = net_FindCharNotInSet(start, end, HTTP_LWS);
+ const char* typeEnd = net_FindCharInSet(type, end, HTTP_LWS ";(");
+
+ const char* charset = "";
+ const char* charsetEnd = charset;
+ int32_t charsetParamStart = 0;
+ int32_t charsetParamEnd = 0;
+
+ uint32_t consumed = typeEnd - type;
+
+ // Iterate over parameters
+ bool typeHasCharset = false;
+ uint32_t paramStart = flatStr.FindChar(';', typeEnd - start);
+ if (paramStart != uint32_t(kNotFound)) {
+ // We have parameters. Iterate over them.
+ uint32_t curParamStart = paramStart + 1;
+ do {
+ uint32_t curParamEnd =
+ net_FindMediaDelimiter(flatStr, curParamStart, ';');
+
+ const char* paramName = net_FindCharNotInSet(
+ start + curParamStart, start + curParamEnd, HTTP_LWS);
+ static const char charsetStr[] = "charset=";
+ if (PL_strncasecmp(paramName, charsetStr, sizeof(charsetStr) - 1) == 0) {
+ charset = paramName + sizeof(charsetStr) - 1;
+ charsetEnd = start + curParamEnd;
+ typeHasCharset = true;
+ charsetParamStart = curParamStart - 1;
+ charsetParamEnd = curParamEnd;
+ }
+
+ consumed = curParamEnd;
+ curParamStart = curParamEnd + 1;
+ } while (curParamStart < flatStr.Length());
+ }
+
+ bool charsetNeedsQuotedStringUnescaping = false;
+ if (typeHasCharset) {
+ // Trim LWS leading and trailing whitespace from charset. We include
+ // '(' in the trailing trim set to catch media-type comments, which are
+ // not at all standard, but may occur in rare cases.
+ charset = net_FindCharNotInSet(charset, charsetEnd, HTTP_LWS);
+ if (*charset == '"') {
+ charsetNeedsQuotedStringUnescaping = true;
+ charsetEnd =
+ start + net_FindStringEnd(flatStr, charset - start, *charset);
+ charset++;
+ NS_ASSERTION(charsetEnd >= charset, "Bad charset parsing");
+ } else {
+ charsetEnd = net_FindCharInSet(charset, charsetEnd, HTTP_LWS ";(");
+ }
+ }
+
+ // if the server sent "*/*", it is meaningless, so do not store it.
+ // also, if type is the same as aContentType, then just update the
+ // charset. however, if charset is empty and aContentType hasn't
+ // changed, then don't wipe-out an existing aContentCharset. We
+ // also want to reject a mime-type if it does not include a slash.
+ // some servers give junk after the charset parameter, which may
+ // include a comma, so this check makes us a bit more tolerant.
+
+ if (type != typeEnd && memchr(type, '/', typeEnd - type) != nullptr &&
+ (aStrict ? (net_FindCharNotInSet(start + consumed, end, HTTP_LWS) == end)
+ : (strncmp(type, "*/*", typeEnd - type) != 0))) {
+ // Common case here is that aContentType is empty
+ bool eq = !aContentType.IsEmpty() &&
+ aContentType.Equals(Substring(type, typeEnd),
+ nsCaseInsensitiveCStringComparator);
+ if (!eq) {
+ aContentType.Assign(type, typeEnd - type);
+ ToLowerCase(aContentType);
+ }
+
+ if ((!eq && *aHadCharset) || typeHasCharset) {
+ *aHadCharset = true;
+ if (charsetNeedsQuotedStringUnescaping) {
+ // parameters using the "quoted-string" syntax need
+ // backslash-escapes to be unescaped (see RFC 2616 Section 2.2)
+ aContentCharset.Truncate();
+ for (const char* c = charset; c != charsetEnd; c++) {
+ if (*c == '\\' && c + 1 != charsetEnd) {
+ // eat escape
+ c++;
+ }
+ aContentCharset.Append(*c);
+ }
+ } else {
+ aContentCharset.Assign(charset, charsetEnd - charset);
+ }
+ if (typeHasCharset) {
+ *aCharsetStart = charsetParamStart + aOffset;
+ *aCharsetEnd = charsetParamEnd + aOffset;
+ }
+ }
+ // Only set a new charset position if this is a different type
+ // from the last one we had and it doesn't already have a
+ // charset param. If this is the same type, we probably want
+ // to leave the charset position on its first occurrence.
+ if (!eq && !typeHasCharset) {
+ int32_t charsetStart = int32_t(paramStart);
+ if (charsetStart == kNotFound) charsetStart = flatStr.Length();
+
+ *aCharsetEnd = *aCharsetStart = charsetStart + aOffset;
+ }
+ }
+}
+
+#undef HTTP_LWS
+
+void net_ParseContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType, nsACString& aContentCharset,
+ bool* aHadCharset) {
+ int32_t dummy1, dummy2;
+ net_ParseContentType(aHeaderStr, aContentType, aContentCharset, aHadCharset,
+ &dummy1, &dummy2);
+}
+
+void net_ParseContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType, nsACString& aContentCharset,
+ bool* aHadCharset, int32_t* aCharsetStart,
+ int32_t* aCharsetEnd) {
+ //
+ // Augmented BNF (from RFC 2616 section 3.7):
+ //
+ // header-value = media-type *( LWS "," LWS media-type )
+ // media-type = type "/" subtype *( LWS ";" LWS parameter )
+ // type = token
+ // subtype = token
+ // parameter = attribute "=" value
+ // attribute = token
+ // value = token | quoted-string
+ //
+ //
+ // Examples:
+ //
+ // text/html
+ // text/html, text/html
+ // text/html,text/html; charset=ISO-8859-1
+ // text/html,text/html; charset="ISO-8859-1"
+ // text/html;charset=ISO-8859-1, text/html
+ // text/html;charset='ISO-8859-1', text/html
+ // application/octet-stream
+ //
+
+ *aHadCharset = false;
+ const nsCString& flatStr = PromiseFlatCString(aHeaderStr);
+
+ // iterate over media-types. Note that ',' characters can happen
+ // inside quoted strings, so we need to watch out for that.
+ uint32_t curTypeStart = 0;
+ do {
+ // curTypeStart points to the start of the current media-type. We want
+ // to look for its end.
+ uint32_t curTypeEnd = net_FindMediaDelimiter(flatStr, curTypeStart, ',');
+
+ // At this point curTypeEnd points to the spot where the media-type
+ // starting at curTypeEnd ends. Time to parse that!
+ net_ParseMediaType(
+ Substring(flatStr, curTypeStart, curTypeEnd - curTypeStart),
+ aContentType, aContentCharset, curTypeStart, aHadCharset, aCharsetStart,
+ aCharsetEnd, false);
+
+ // And let's move on to the next media-type
+ curTypeStart = curTypeEnd + 1;
+ } while (curTypeStart < flatStr.Length());
+}
+
+void net_ParseRequestContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType,
+ nsACString& aContentCharset,
+ bool* aHadCharset) {
+ //
+ // Augmented BNF (from RFC 7231 section 3.1.1.1):
+ //
+ // media-type = type "/" subtype *( OWS ";" OWS parameter )
+ // type = token
+ // subtype = token
+ // parameter = token "=" ( token / quoted-string )
+ //
+ // Examples:
+ //
+ // text/html
+ // text/html; charset=ISO-8859-1
+ // text/html; charset="ISO-8859-1"
+ // application/octet-stream
+ //
+
+ aContentType.Truncate();
+ aContentCharset.Truncate();
+ *aHadCharset = false;
+ const nsCString& flatStr = PromiseFlatCString(aHeaderStr);
+
+ // At this point curTypeEnd points to the spot where the media-type
+ // starting at curTypeEnd ends. Time to parse that!
+ nsAutoCString contentType, contentCharset;
+ bool hadCharset = false;
+ int32_t dummy1, dummy2;
+ uint32_t typeEnd = net_FindMediaDelimiter(flatStr, 0, ',');
+ if (typeEnd != flatStr.Length()) {
+ // We have some stuff left at the end, so this is not a valid
+ // request Content-Type header.
+ return;
+ }
+ net_ParseMediaType(flatStr, contentType, contentCharset, 0, &hadCharset,
+ &dummy1, &dummy2, true);
+
+ aContentType = contentType;
+ aContentCharset = contentCharset;
+ *aHadCharset = hadCharset;
+}
+
+bool net_IsValidHostName(const nsACString& host) {
+ const char* end = host.EndReading();
+ // Use explicit whitelists to select which characters we are
+ // willing to send to lower-level DNS logic. This is more
+ // self-documenting, and can also be slightly faster than the
+ // blacklist approach, since DNS names are the common case, and
+ // the commonest characters will tend to be near the start of
+ // the list.
+
+ // Whitelist for DNS names (RFC 1035) with extra characters added
+ // for pragmatic reasons "$+_"
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=355181#c2
+ if (net_FindCharNotInSet(host.BeginReading(), end,
+ "abcdefghijklmnopqrstuvwxyz"
+ ".-0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ$+_") == end)
+ return true;
+
+ // Might be a valid IPv6 link-local address containing a percent sign
+ nsAutoCString strhost(host);
+ PRNetAddr addr;
+ return PR_StringToNetAddr(strhost.get(), &addr) == PR_SUCCESS;
+}
+
+bool net_IsValidIPv4Addr(const 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);
+}
+
+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));
+}
+
+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); });
+}
+
+/* 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) 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.
+ SerializeString(NS_ConvertUTF16toUTF8(mParams[i].mKey), aValue);
+ aValue.Append('=');
+ SerializeString(NS_ConvertUTF16toUTF8(mParams[i].mValue), aValue);
+ }
+}
+
+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..bd850cb01b
--- /dev/null
+++ b/netwerk/base/nsURLHelper.h
@@ -0,0 +1,355 @@
+/* -*- 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"
+
+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& inURL);
+
+/**
+ * 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 aResult the out param to write to if filtering happens
+ */
+nsresult net_FilterAndEscapeURI(const nsACString& aInput, uint32_t aFlags,
+ 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* str, const char* end, const char* set);
+
+/**
+ * returns pointer to first character of |str| NOT in the given set. if all
+ * characters are in the given set, then |end| is returned. if '\0' is not
+ * included in |set|, then stops prematurely if a null byte is encountered,
+ * and returns the address of the null byte.
+ */
+char* net_FindCharNotInSet(const char* str, const char* end, const char* set);
+
+/**
+ * returns pointer to last character of |str| NOT in the given set. if all
+ * characters are in the given set, then |str - 1| is returned.
+ */
+char* net_RFindCharNotInSet(const char* str, const char* end, const char* set);
+
+/**
+ * Parses a content-type header and returns the content type and
+ * charset (if any). aCharset is not modified if no charset is
+ * specified in anywhere in aHeaderStr. In that case (no charset
+ * specified), aHadCharset is set to false. Otherwise, it's set to
+ * true. Note that aContentCharset can be empty even if aHadCharset
+ * is true.
+ *
+ * This parsing is suitable for HTTP request. Use net_ParseContentType
+ * for parsing this header in HTTP responses.
+ */
+void net_ParseRequestContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType,
+ nsACString& aContentCharset,
+ bool* aHadCharset);
+
+/**
+ * Parses a content-type header and returns the content type and
+ * charset (if any). aCharset is not modified if no charset is
+ * specified in anywhere in aHeaderStr. In that case (no charset
+ * specified), aHadCharset is set to false. Otherwise, it's set to
+ * true. Note that aContentCharset can be empty even if aHadCharset
+ * is true.
+ */
+void net_ParseContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType, nsACString& aContentCharset,
+ bool* aHadCharset);
+/**
+ * As above, but also returns the start and end indexes for the charset
+ * parameter in aHeaderStr. These are indices for the entire parameter, NOT
+ * just the value. If there is "effectively" no charset parameter (e.g. if an
+ * earlier type with one is overridden by a later type without one),
+ * *aHadCharset will be true but *aCharsetStart will be set to -1. Note that
+ * it's possible to have aContentCharset empty and *aHadCharset true when
+ * *aCharsetStart is nonnegative; this corresponds to charset="".
+ */
+void net_ParseContentType(const nsACString& aHeaderStr,
+ nsACString& aContentType, nsACString& aContentCharset,
+ bool* aHadCharset, int32_t* aCharsetStart,
+ int32_t* aCharsetEnd);
+
+/* inline versions */
+
+/* remember the 64-bit platforms ;-) */
+#define NET_MAX_ADDRESS ((char*)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);
+
+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.
+ */
+ void Serialize(nsAString& aValue) 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);
+
+ /**
+ * \brief Deletes all parameters with the given name.
+ */
+ void Delete(const nsAString& aName);
+
+ 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..c1eeb36e0b
--- /dev/null
+++ b/netwerk/base/nsURLHelperUnix.cpp
@@ -0,0 +1,105 @@
+/* -*- 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..2ed642230d
--- /dev/null
+++ b/netwerk/base/nsURLParsers.cpp
@@ -0,0 +1,634 @@
+/* -*- 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)) ||
+ (*(colon + 1) == ':')) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+ SET_RESULT(scheme, offset, colon - spec);
+ if (authorityLen || pathLen) {
+ uint32_t schemeLen = colon + 1 - spec;
+ offset += schemeLen;
+ ParseAfterScheme(colon + 1, specLen - schemeLen, authorityPos,
+ authorityLen, pathPos, pathLen);
+ OFFSET_RESULT(authority, offset);
+ OFFSET_RESULT(path, offset);
+ }
+ } else {
+ //
+ // spec = <authority-no-port-or-password>/<path>
+ // spec = <path>
+ //
+ // or
+ //
+ // spec = <authority-no-port-or-password>/<path-with-colon>
+ // spec = <path-with-colon>
+ //
+ // or
+ //
+ // spec = <authority-no-port-or-password>
+ // spec = <path-no-slashes-or-colon>
+ //
+ SET_RESULT(scheme, 0, -1);
+ if (authorityLen || pathLen) {
+ ParseAfterScheme(spec, specLen, authorityPos, authorityLen, pathPos,
+ pathLen);
+ OFFSET_RESULT(authority, offset);
+ OFFSET_RESULT(path, offset);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseAuthority(const char* auth, int32_t authLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) {
+ if (NS_WARN_IF(!auth)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (authLen < 0) authLen = strlen(auth);
+
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ SET_RESULT(hostname, 0, authLen);
+ if (port) *port = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseUserInfo(const char* userinfo, int32_t userinfoLen,
+ uint32_t* usernamePos, int32_t* usernameLen,
+ uint32_t* passwordPos, int32_t* passwordLen) {
+ SET_RESULT(username, 0, -1);
+ SET_RESULT(password, 0, -1);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParseServerInfo(const char* serverinfo, int32_t serverinfoLen,
+ uint32_t* hostnamePos, int32_t* hostnameLen,
+ int32_t* port) {
+ SET_RESULT(hostname, 0, -1);
+ if (port) *port = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseURLParser::ParsePath(const char* path, int32_t pathLen,
+ uint32_t* filepathPos, int32_t* filepathLen,
+ uint32_t* queryPos, int32_t* queryLen,
+ uint32_t* refPos, int32_t* refLen) {
+ if (NS_WARN_IF(!path)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ if (pathLen < 0) pathLen = strlen(path);
+
+ // path = [/]<segment1>/<segment2>/<...>/<segmentN>?<query>#<ref>
+
+ // XXX PL_strnpbrk would be nice, but it's buggy
+
+ // search for first occurrence of either ? or #
+ const char *query_beg = 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..e522dae38b
--- /dev/null
+++ b/netwerk/base/rust-helper/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "netwerk_helper"
+version = "0.0.1"
+authors = ["Jeff Hemphill <jthemphill@mozilla.com>"]
+
+[dependencies]
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] } \ No newline at end of file
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..df6e4af1e4
--- /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(..) => break,
+ };
+
+ 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..3c5f612158
--- /dev/null
+++ b/netwerk/build/components.conf
@@ -0,0 +1,672 @@
+# -*- 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'
+
+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'],
+ },
+ {
+ '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': '{02bf7a2a-39d8-4a23-a50c-2cbb085ab7a5}',
+ 'contract_ids': ['@mozilla.org/network/application-cache-service;1'],
+ 'singleton': True,
+ 'type': 'nsApplicationCacheService',
+ 'headers': ['nsApplicationCacheService.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',
+ },
+ {
+ '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': '{6c84aec9-29a5-4264-8fbc-bee8f922ea67}',
+ 'contract_ids': ['@mozilla.org/network/cache-service;1'],
+ 'legacy_constructor': 'nsCacheServiceConstructor',
+ },
+ {
+ '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'],
+ },
+ {
+ 'cid': '{b0ff4572-dae4-4bef-a092-83c1b88f6be9}',
+ 'contract_ids': ['@mozilla.org/network/dns-service;1'],
+ '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': '{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'],
+ },
+ {
+ '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'],
+ },
+ {
+ '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'],
+ },
+ {
+ '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',
+ },
+ {
+ 'cid': '{25029490-f132-11d2-9588-00805f369f95}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=ftp'],
+ 'singleton': True,
+ 'type': 'nsFtpProtocolHandler',
+ 'headers': ['/netwerk/protocol/ftp/nsFtpProtocolHandler.h'],
+ 'init_method': 'Init',
+ },
+ {
+ '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,
+ },
+ {
+ '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,
+ },
+ {
+ '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',
+ },
+ {
+ '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',
+ },
+ {
+ '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'],
+ },
+ {
+ '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',
+ },
+ {
+ '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'],
+ },
+ {
+ 'cid': '{dc01db59-a513-4c90-824b-085cce06c0aa}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=ws'],
+ 'singleton': True,
+ 'legacy_constructor': 'mozilla::net::WebSocketChannelConstructor',
+ },
+ {
+ 'cid': '{dc01dbbb-a5bb-4cbb-82bb-085cce06c0bb}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=wss'],
+ 'singleton': True,
+ 'legacy_constructor': 'mozilla::net::WebSocketSSLChannelConstructor',
+ },
+ {
+ '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,
+ },
+ {
+ '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'],
+ },
+ {
+ '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'],
+ },
+ {
+ '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': '{14c0e880-623e-11d3-a178-0050041caf44}',
+ 'contract_ids': ['@mozilla.org/streamconv;1?from=text/ftp-dir&to=application/http-index-format'],
+ 'legacy_constructor': 'CreateNewFTPDirListingConv',
+ },
+ {
+ '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': ['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::Create',
+ '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'],
+ },
+ ]
+
+if buildconfig.substs['OS_ARCH'] in ('WINNT', 'Darwin', 'Linux'):
+ Classes += [
+ {
+ 'cid': '{296d0900-f8ef-4df0-9c35-db5862abc58d}',
+ 'contract_ids': ['@mozilla.org/network-info-service;1'],
+ 'type': 'mozilla::net::nsNetworkInfoService',
+ 'headers': ['/netwerk/base/nsNetworkInfoService.h'],
+ 'init_method': 'Init',
+ },
+ ]
+
+
+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)
+ ]
diff --git a/netwerk/build/moz.build b/netwerk/build/moz.build
new file mode 100644
index 0000000000..698eb70c53
--- /dev/null
+++ b/netwerk/build/moz.build
@@ -0,0 +1,51 @@
+# -*- 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 += [
+ "/extensions/auth",
+ "/netwerk/base",
+ "/netwerk/cache",
+ "/netwerk/dns",
+ "/netwerk/mime",
+ "/netwerk/protocol/about",
+ "/netwerk/protocol/data",
+ "/netwerk/protocol/file",
+ "/netwerk/protocol/ftp",
+ "/netwerk/protocol/http",
+ "/netwerk/protocol/res",
+ "/netwerk/protocol/viewsource",
+ "/netwerk/protocol/websocket",
+ "/netwerk/socket",
+ "/netwerk/streamconv",
+ "/netwerk/streamconv/converters",
+]
+
+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..c07d42eb74
--- /dev/null
+++ b/netwerk/build/nsNetCID.h
@@ -0,0 +1,854 @@
+/* -*- 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_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/cache/ 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 \
+ } \
+ }
+
+// service implementing nsIApplicationCacheService.
+#define NS_APPLICATIONCACHESERVICE_CONTRACTID \
+ "@mozilla.org/network/application-cache-service;1"
+#define NS_APPLICATIONCACHESERVICE_CID \
+ { /* 02bf7a2a-39d8-4a23-a50c-2cbb085ab7a5 */ \
+ 0x02bf7a2a, 0x39d8, 0x4a23, { \
+ 0xa5, 0x0c, 0x2c, 0xbb, 0x08, 0x5a, 0xb7, 0xa5 \
+ } \
+ }
+
+/******************************************************************************
+ * 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/ftp/ classes
+ */
+
+#define NS_FTPPROTOCOLHANDLER_CID \
+ { /* 25029490-F132-11d2-9588-00805F369F95 */ \
+ 0x25029490, 0xf132, 0x11d2, { \
+ 0x95, 0x88, 0x0, 0x80, 0x5f, 0x36, 0x9f, 0x95 \
+ } \
+ }
+
+/******************************************************************************
+ * 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"
+
+/**
+ * Must implement nsICryptoHMAC.
+ */
+#define NS_CRYPTO_HMAC_CONTRACTID "@mozilla.org/security/hmac;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"
+
+/**
+ * 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..78dfaa358e
--- /dev/null
+++ b/netwerk/build/nsNetModule.cpp
@@ -0,0 +1,352 @@
+/* -*- 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"
+#include "RedirectChannelRegistrar.h"
+#include "nsAuthGSSAPI.h"
+
+#include "nsNetCID.h"
+
+#if defined(XP_MACOSX) || defined(XP_WIN) || defined(XP_LINUX)
+# define BUILD_NETWORK_INFO_SERVICE 1
+#endif
+
+using namespace mozilla;
+
+typedef nsCategoryCache<nsIContentSniffer> ContentSnifferCache;
+ContentSnifferCache* gNetSniffers = nullptr;
+ContentSnifferCache* gDataSniffers = nullptr;
+
+#define static
+typedef mozilla::net::nsLoadGroup 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 "nsCacheService.h"
+
+nsresult nsCacheServiceConstructor(nsISupports* aOuter, const nsIID& aIID,
+ void** aResult) {
+ return nsCacheService::Create(aOuter, aIID, aResult);
+}
+
+#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(nsISupports* aOuter, REFNSIID aIID, \
+ void** aResult) { \
+ nsresult rv; \
+ \
+ RefPtr<BaseWebSocketChannel> inst; \
+ \
+ *aResult = nullptr; \
+ if (nullptr != aOuter) { \
+ rv = NS_ERROR_NO_AGGREGATION; \
+ return rv; \
+ } \
+ 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 "nsFTPDirListingConv.h"
+nsresult NS_NewFTPDirListingConv(nsFTPDirListingConv** result);
+
+#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);
+
+#define FTP_TO_INDEX "?from=text/ftp-dir&to=application/http-index-format"
+#define INDEX_TO_HTML "?from=application/http-index-format&to=text/html"
+#define MULTI_MIXED_X "?from=multipart/x-mixed-replace&to=*/*"
+#define MULTI_MIXED "?from=multipart/mixed&to=*/*"
+#define MULTI_BYTERANGES "?from=multipart/byteranges&to=*/*"
+#define UNKNOWN_CONTENT "?from=" UNKNOWN_CONTENT_TYPE "&to=*/*"
+#define GZIP_TO_UNCOMPRESSED "?from=gzip&to=uncompressed"
+#define XGZIP_TO_UNCOMPRESSED "?from=x-gzip&to=uncompressed"
+#define BROTLI_TO_UNCOMPRESSED "?from=br&to=uncompressed"
+#define COMPRESS_TO_UNCOMPRESSED "?from=compress&to=uncompressed"
+#define XCOMPRESS_TO_UNCOMPRESSED "?from=x-compress&to=uncompressed"
+#define DEFLATE_TO_UNCOMPRESSED "?from=deflate&to=uncompressed"
+
+static const mozilla::Module::CategoryEntry kNeckoCategories[] = {
+ {NS_ISTREAMCONVERTER_KEY, FTP_TO_INDEX, ""},
+ {NS_ISTREAMCONVERTER_KEY, INDEX_TO_HTML, ""},
+ {NS_ISTREAMCONVERTER_KEY, MULTI_MIXED_X, ""},
+ {NS_ISTREAMCONVERTER_KEY, MULTI_MIXED, ""},
+ {NS_ISTREAMCONVERTER_KEY, MULTI_BYTERANGES, ""},
+ {NS_ISTREAMCONVERTER_KEY, UNKNOWN_CONTENT, ""},
+ {NS_ISTREAMCONVERTER_KEY, GZIP_TO_UNCOMPRESSED, ""},
+ {NS_ISTREAMCONVERTER_KEY, XGZIP_TO_UNCOMPRESSED, ""},
+ {NS_ISTREAMCONVERTER_KEY, BROTLI_TO_UNCOMPRESSED, ""},
+ {NS_ISTREAMCONVERTER_KEY, COMPRESS_TO_UNCOMPRESSED, ""},
+ {NS_ISTREAMCONVERTER_KEY, XCOMPRESS_TO_UNCOMPRESSED, ""},
+ {NS_ISTREAMCONVERTER_KEY, DEFLATE_TO_UNCOMPRESSED, ""},
+ NS_BINARYDETECTOR_CATEGORYENTRY,
+ {nullptr}};
+
+nsresult CreateNewStreamConvServiceFactory(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ if (aOuter) {
+ *aResult = nullptr;
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ 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 CreateNewFTPDirListingConv(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ if (aOuter) {
+ *aResult = nullptr;
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ RefPtr<nsFTPDirListingConv> inst;
+ nsresult rv = NS_NewFTPDirListingConv(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(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ if (aOuter) {
+ *aResult = nullptr;
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ 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(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ if (aOuter) {
+ *aResult = nullptr;
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ 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(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ if (!aResult) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ if (aOuter) {
+ *aResult = nullptr;
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ 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(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aResult = nullptr;
+
+ if (aOuter) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ RefPtr<nsUnknownDecoder> inst = new nsUnknownDecoder();
+ return inst->QueryInterface(aIID, aResult);
+}
+
+nsresult CreateNewBinaryDetectorFactory(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ if (!aResult) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aResult = nullptr;
+
+ if (aOuter) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ 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();
+
+ mozilla::net::RedirectChannelRegistrar::Shutdown();
+
+ mozilla::net::BackgroundChannelRegistrar::Shutdown();
+
+ nsAuthGSSAPI::Shutdown();
+
+ delete gNetSniffers;
+ gNetSniffers = nullptr;
+ delete gDataSniffers;
+ gDataSniffers = nullptr;
+}
+
+extern const mozilla::Module kNeckoModule = {
+ mozilla::Module::kVersion,
+ nullptr,
+ nullptr,
+ kNeckoCategories,
+ nullptr,
+ nullptr,
+ nullptr,
+ mozilla::Module::ALLOW_IN_SOCKET_PROCESS};
diff --git a/netwerk/build/nsNetModule.h b/netwerk/build/nsNetModule.h
new file mode 100644
index 0000000000..3a83f82a3e
--- /dev/null
+++ b/netwerk/build/nsNetModule.h
@@ -0,0 +1,47 @@
+/* -*- 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(nsISupports* aOuter,
+ const nsIID& aIID, void** aResult);
+nsresult CreateNewFTPDirListingConv(nsISupports* aOuter, const nsIID& aIID,
+ void** aResult);
+nsresult CreateNewMultiMixedConvFactory(nsISupports* aOuter, const nsIID& aIID,
+ void** aResult);
+nsresult CreateNewTXTToHTMLConvFactory(nsISupports* aOuter, const nsIID& aIID,
+ void** aResult);
+nsresult CreateNewHTTPCompressConvFactory(nsISupports* aOuter,
+ const nsIID& aIID, void** aResult);
+nsresult CreateNewUnknownDecoderFactory(nsISupports* aOuter, const nsIID& aIID,
+ void** aResult);
+nsresult CreateNewBinaryDetectorFactory(nsISupports* aOuter, const nsIID& aIID,
+ void** aResult);
+nsresult nsLoadGroupConstructor(nsISupports* aOuter, const nsIID& aIID,
+ void** aResult);
+
+nsresult nsCacheServiceConstructor(nsISupports* aOuter, const nsIID& aIID,
+ void** aResult);
+
+extern nsresult net_NewIncrementalDownload(nsISupports*, const nsIID&, void**);
+
+namespace mozilla {
+namespace net {
+nsresult WebSocketChannelConstructor(nsISupports* aOuter, const nsIID& aIID,
+ void** aResult);
+nsresult WebSocketSSLChannelConstructor(nsISupports* aOuter, const nsIID& aIID,
+ void** aResult);
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache/moz.build b/netwerk/cache/moz.build
new file mode 100644
index 0000000000..62a0c06ebe
--- /dev/null
+++ b/netwerk/cache/moz.build
@@ -0,0 +1,50 @@
+# -*- 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 += [
+ "nsICache.idl",
+ "nsICacheEntryDescriptor.idl",
+ "nsICacheListener.idl",
+ "nsICacheService.idl",
+ "nsICacheSession.idl",
+ "nsICacheVisitor.idl",
+]
+
+XPIDL_MODULE = "necko_cache"
+
+EXPORTS += [
+ "nsApplicationCache.h",
+ "nsApplicationCacheService.h",
+ "nsCacheDevice.h",
+ "nsCacheService.h",
+ "nsDeleteDir.h",
+ "nsDiskCacheDeviceSQL.h",
+]
+
+UNIFIED_SOURCES += [
+ "nsApplicationCacheService.cpp",
+ "nsCache.cpp",
+ "nsCacheEntry.cpp",
+ "nsCacheEntryDescriptor.cpp",
+ "nsCacheMetaData.cpp",
+ "nsCacheService.cpp",
+ "nsCacheSession.cpp",
+ "nsCacheUtils.cpp",
+ "nsDeleteDir.cpp",
+ "nsDiskCacheDeviceSQL.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/netwerk/cache/nsApplicationCache.h b/netwerk/cache/nsApplicationCache.h
new file mode 100644
index 0000000000..37e3bc14e4
--- /dev/null
+++ b/netwerk/cache/nsApplicationCache.h
@@ -0,0 +1,33 @@
+/* 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 "nsIApplicationCache.h"
+#include "nsWeakReference.h"
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+
+class nsOfflineCacheDevice;
+
+class nsApplicationCache : public nsIApplicationCache,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAPPLICATIONCACHE
+
+ nsApplicationCache(nsOfflineCacheDevice* device, const nsACString& group,
+ const nsACString& clientID);
+
+ nsApplicationCache();
+
+ void MarkInvalid();
+
+ private:
+ virtual ~nsApplicationCache();
+
+ RefPtr<nsOfflineCacheDevice> mDevice;
+ nsCString mGroup;
+ nsCString mClientID;
+ bool mValid;
+};
diff --git a/netwerk/cache/nsApplicationCacheService.cpp b/netwerk/cache/nsApplicationCacheService.cpp
new file mode 100644
index 0000000000..af931706a3
--- /dev/null
+++ b/netwerk/cache/nsApplicationCacheService.cpp
@@ -0,0 +1,192 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDiskCache.h"
+#include "nsDiskCacheDeviceSQL.h"
+#include "nsCacheService.h"
+#include "nsApplicationCacheService.h"
+#include "nsCRT.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIObserverService.h"
+#include "mozilla/LoadContextInfo.h"
+
+using namespace mozilla;
+
+static NS_DEFINE_CID(kCacheServiceCID, NS_CACHESERVICE_CID);
+
+//-----------------------------------------------------------------------------
+// nsApplicationCacheService
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsApplicationCacheService, nsIApplicationCacheService)
+
+nsApplicationCacheService::nsApplicationCacheService() {
+ nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID);
+ mCacheService = nsCacheService::GlobalInstance();
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::BuildGroupIDForInfo(
+ nsIURI* aManifestURL, nsILoadContextInfo* aLoadContextInfo,
+ nsACString& _result) {
+ nsresult rv;
+
+ nsAutoCString originSuffix;
+ if (aLoadContextInfo) {
+ aLoadContextInfo->OriginAttributesPtr()->CreateSuffix(originSuffix);
+ }
+
+ rv = nsOfflineCacheDevice::BuildApplicationCacheGroupID(
+ aManifestURL, originSuffix, _result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::BuildGroupIDForSuffix(
+ nsIURI* aManifestURL, nsACString const& aOriginSuffix,
+ nsACString& _result) {
+ nsresult rv;
+
+ rv = nsOfflineCacheDevice::BuildApplicationCacheGroupID(
+ aManifestURL, aOriginSuffix, _result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::CreateApplicationCache(const nsACString& group,
+ nsIApplicationCache** out) {
+ if (!mCacheService) return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->CreateApplicationCache(group, out);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::CreateCustomApplicationCache(
+ const nsACString& group, nsIFile* profileDir, int32_t quota,
+ nsIApplicationCache** out) {
+ if (!mCacheService) return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetCustomOfflineDevice(profileDir, quota,
+ getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->CreateApplicationCache(group, out);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::GetApplicationCache(const nsACString& clientID,
+ nsIApplicationCache** out) {
+ if (!mCacheService) return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->GetApplicationCache(clientID, out);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::GetActiveCache(const nsACString& group,
+ nsIApplicationCache** out) {
+ if (!mCacheService) return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->GetActiveCache(group, out);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::DeactivateGroup(const nsACString& group) {
+ if (!mCacheService) return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->DeactivateGroup(group);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::ChooseApplicationCache(
+ const nsACString& key, nsILoadContextInfo* aLoadContextInfo,
+ nsIApplicationCache** out) {
+ if (!mCacheService) return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ if (NS_FAILED(rv)) {
+ // Silently fail and provide no appcache to the caller.
+ return NS_OK;
+ }
+
+ return device->ChooseApplicationCache(key, aLoadContextInfo, out);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::CacheOpportunistically(nsIApplicationCache* cache,
+ const nsACString& key) {
+ if (!mCacheService) return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->CacheOpportunistically(cache, key);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::Evict(nsILoadContextInfo* aInfo) {
+ if (!mCacheService) return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->Evict(aInfo);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::EvictMatchingOriginAttributes(
+ nsAString const& aPattern) {
+ if (!mCacheService) return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozilla::OriginAttributesPattern pattern;
+ if (!pattern.Init(aPattern)) {
+ NS_ERROR(
+ "Could not parse OriginAttributesPattern JSON in "
+ "clear-origin-attributes-data notification");
+ return NS_ERROR_FAILURE;
+ }
+
+ return device->Evict(pattern);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::GetGroups(nsTArray<nsCString>& keys) {
+ if (!mCacheService) return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->GetGroups(keys);
+}
+
+NS_IMETHODIMP
+nsApplicationCacheService::GetGroupsTimeOrdered(nsTArray<nsCString>& keys) {
+ if (!mCacheService) return NS_ERROR_UNEXPECTED;
+
+ RefPtr<nsOfflineCacheDevice> device;
+ nsresult rv = mCacheService->GetOfflineDevice(getter_AddRefs(device));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return device->GetGroupsTimeOrdered(keys);
+}
diff --git a/netwerk/cache/nsApplicationCacheService.h b/netwerk/cache/nsApplicationCacheService.h
new file mode 100644
index 0000000000..d57a913726
--- /dev/null
+++ b/netwerk/cache/nsApplicationCacheService.h
@@ -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/. */
+
+#ifndef _nsApplicationCacheService_h_
+#define _nsApplicationCacheService_h_
+
+#include "nsIApplicationCacheService.h"
+#include "mozilla/Attributes.h"
+
+class nsCacheService;
+
+class nsApplicationCacheService final : public nsIApplicationCacheService {
+ public:
+ nsApplicationCacheService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAPPLICATIONCACHESERVICE
+
+ static void AppClearDataObserverInit();
+
+ private:
+ ~nsApplicationCacheService() = default;
+ RefPtr<nsCacheService> mCacheService;
+};
+
+#endif // _nsApplicationCacheService_h_
diff --git a/netwerk/cache/nsCache.cpp b/netwerk/cache/nsCache.cpp
new file mode 100644
index 0000000000..73b98dee1c
--- /dev/null
+++ b/netwerk/cache/nsCache.cpp
@@ -0,0 +1,70 @@
+/* -*- 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 "nsCache.h"
+#include "nsReadableUtils.h"
+#include "nsDependentSubstring.h"
+#include "nsString.h"
+#include "mozilla/IntegerPrintfMacros.h"
+
+/**
+ * Cache Service Utility Functions
+ */
+
+mozilla::LazyLogModule gCacheLog("cache");
+
+void CacheLogPrintPath(mozilla::LogLevel level, const char* format,
+ nsIFile* item) {
+ MOZ_LOG(gCacheLog, level, (format, item->HumanReadablePath().get()));
+}
+
+uint32_t SecondsFromPRTime(PRTime prTime) {
+ int64_t microSecondsPerSecond = PR_USEC_PER_SEC;
+ return uint32_t(prTime / microSecondsPerSecond);
+}
+
+PRTime PRTimeFromSeconds(uint32_t seconds) {
+ int64_t intermediateResult = seconds;
+ PRTime prTime = intermediateResult * PR_USEC_PER_SEC;
+ return prTime;
+}
+
+nsresult ClientIDFromCacheKey(const nsACString& key, nsACString& result) {
+ nsReadingIterator<char> colon;
+ key.BeginReading(colon);
+
+ nsReadingIterator<char> start;
+ key.BeginReading(start);
+
+ nsReadingIterator<char> end;
+ key.EndReading(end);
+
+ if (FindCharInReadable(':', colon, end)) {
+ result.Assign(Substring(start, colon));
+ return NS_OK;
+ }
+
+ NS_ASSERTION(false, "FindCharInRead failed to find ':'");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult ClientKeyFromCacheKey(const nsCString& key, nsACString& result) {
+ nsReadingIterator<char> start;
+ key.BeginReading(start);
+
+ nsReadingIterator<char> end;
+ key.EndReading(end);
+
+ if (FindCharInReadable(':', start, end)) {
+ ++start; // advance past clientID ':' delimiter
+ result.Assign(Substring(start, end));
+ return NS_OK;
+ }
+
+ NS_ASSERTION(false, "FindCharInRead failed to find ':'");
+ result.Truncate(0);
+ return NS_ERROR_UNEXPECTED;
+}
diff --git a/netwerk/cache/nsCache.h b/netwerk/cache/nsCache.h
new file mode 100644
index 0000000000..859dddb8f2
--- /dev/null
+++ b/netwerk/cache/nsCache.h
@@ -0,0 +1,39 @@
+/* -*- 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/. */
+
+/**
+ * Cache Service Utility Functions
+ */
+
+#ifndef _nsCache_h_
+#define _nsCache_h_
+
+#include "mozilla/Logging.h"
+#include "nsISupports.h"
+#include "nsIFile.h"
+#include "nsAString.h"
+#include "prtime.h"
+#include "nsError.h"
+
+// PR_LOG args = "format string", arg, arg, ...
+extern mozilla::LazyLogModule gCacheLog;
+void CacheLogPrintPath(mozilla::LogLevel level, const char* format,
+ nsIFile* item);
+#define CACHE_LOG_INFO(args) MOZ_LOG(gCacheLog, mozilla::LogLevel::Info, args)
+#define CACHE_LOG_ERROR(args) MOZ_LOG(gCacheLog, mozilla::LogLevel::Error, args)
+#define CACHE_LOG_WARNING(args) \
+ MOZ_LOG(gCacheLog, mozilla::LogLevel::Warning, args)
+#define CACHE_LOG_DEBUG(args) MOZ_LOG(gCacheLog, mozilla::LogLevel::Debug, args)
+#define CACHE_LOG_PATH(level, format, item) \
+ CacheLogPrintPath(level, format, item)
+
+extern uint32_t SecondsFromPRTime(PRTime prTime);
+extern PRTime PRTimeFromSeconds(uint32_t seconds);
+
+extern nsresult ClientIDFromCacheKey(const nsACString& key, nsACString& result);
+extern nsresult ClientKeyFromCacheKey(const nsCString& key, nsACString& result);
+
+#endif // _nsCache_h
diff --git a/netwerk/cache/nsCacheDevice.h b/netwerk/cache/nsCacheDevice.h
new file mode 100644
index 0000000000..fc68bcb8a2
--- /dev/null
+++ b/netwerk/cache/nsCacheDevice.h
@@ -0,0 +1,63 @@
+/* -*- 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 _nsCacheDevice_h_
+#define _nsCacheDevice_h_
+
+#include "nspr.h"
+#include "nsError.h"
+#include "nsICache.h"
+#include "nsStringFwd.h"
+
+class nsIFile;
+class nsCacheEntry;
+class nsICacheVisitor;
+class nsIInputStream;
+class nsIOutputStream;
+
+/******************************************************************************
+ * nsCacheDevice
+ *******************************************************************************/
+class nsCacheDevice {
+ public:
+ MOZ_COUNTED_DEFAULT_CTOR(nsCacheDevice)
+ MOZ_COUNTED_DTOR_VIRTUAL(nsCacheDevice)
+
+ virtual nsresult Init() = 0;
+ virtual nsresult Shutdown() = 0;
+
+ virtual const char* GetDeviceID(void) = 0;
+ virtual nsCacheEntry* FindEntry(nsCString* key, bool* collision) = 0;
+
+ virtual nsresult DeactivateEntry(nsCacheEntry* entry) = 0;
+ virtual nsresult BindEntry(nsCacheEntry* entry) = 0;
+ virtual void DoomEntry(nsCacheEntry* entry) = 0;
+
+ virtual nsresult OpenInputStreamForEntry(nsCacheEntry* entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream** result) = 0;
+
+ virtual nsresult OpenOutputStreamForEntry(nsCacheEntry* entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream** result) = 0;
+
+ virtual nsresult GetFileForEntry(nsCacheEntry* entry, nsIFile** result) = 0;
+
+ virtual nsresult OnDataSizeChange(nsCacheEntry* entry, int32_t deltaSize) = 0;
+
+ virtual nsresult Visit(nsICacheVisitor* visitor) = 0;
+
+ /**
+ * Device must evict entries associated with clientID. If clientID ==
+ * nullptr, all entries must be evicted. Active entries must be doomed,
+ * rather than evicted.
+ */
+ virtual nsresult EvictEntries(const char* clientID) = 0;
+};
+
+#endif // _nsCacheDevice_h_
diff --git a/netwerk/cache/nsCacheEntry.cpp b/netwerk/cache/nsCacheEntry.cpp
new file mode 100644
index 0000000000..c91748ef72
--- /dev/null
+++ b/netwerk/cache/nsCacheEntry.cpp
@@ -0,0 +1,415 @@
+/* -*- 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 "nsCache.h"
+#include "nspr.h"
+#include "nsCacheEntry.h"
+#include "nsCacheEntryDescriptor.h"
+#include "nsCacheMetaData.h"
+#include "nsCacheRequest.h"
+#include "nsThreadUtils.h"
+#include "nsError.h"
+#include "nsCacheService.h"
+#include "nsCacheDevice.h"
+#include "nsHashKeys.h"
+
+using namespace mozilla;
+
+nsCacheEntry::nsCacheEntry(const nsACString& key, bool streamBased,
+ nsCacheStoragePolicy storagePolicy)
+ : mKey(key),
+ mFetchCount(0),
+ mLastFetched(0),
+ mLastModified(0),
+ mLastValidated(0),
+ mExpirationTime(nsICache::NO_EXPIRATION_TIME),
+ mFlags(0),
+ mPredictedDataSize(-1),
+ mDataSize(0),
+ mCacheDevice(nullptr),
+ mCustomDevice(nullptr),
+ mData(nullptr),
+ mRequestQ{},
+ mDescriptorQ{} {
+ MOZ_COUNT_CTOR(nsCacheEntry);
+ PR_INIT_CLIST(this);
+ PR_INIT_CLIST(&mRequestQ);
+ PR_INIT_CLIST(&mDescriptorQ);
+
+ if (streamBased) MarkStreamBased();
+ SetStoragePolicy(storagePolicy);
+
+ MarkPublic();
+}
+
+nsCacheEntry::~nsCacheEntry() {
+ MOZ_COUNT_DTOR(nsCacheEntry);
+
+ if (mData) nsCacheService::ReleaseObject_Locked(mData, mEventTarget);
+}
+
+nsresult nsCacheEntry::Create(const char* key, bool streamBased,
+ nsCacheStoragePolicy storagePolicy,
+ nsCacheDevice* device, nsCacheEntry** result) {
+ nsCacheEntry* entry =
+ new nsCacheEntry(nsCString(key), streamBased, storagePolicy);
+ entry->SetCacheDevice(device);
+ *result = entry;
+ return NS_OK;
+}
+
+void nsCacheEntry::Fetched() {
+ mLastFetched = SecondsFromPRTime(PR_Now());
+ ++mFetchCount;
+ MarkEntryDirty();
+}
+
+const char* nsCacheEntry::GetDeviceID() {
+ if (mCacheDevice) return mCacheDevice->GetDeviceID();
+ return nullptr;
+}
+
+void nsCacheEntry::TouchData() {
+ mLastModified = SecondsFromPRTime(PR_Now());
+ MarkDataDirty();
+}
+
+void nsCacheEntry::SetData(nsISupports* data) {
+ if (mData) {
+ nsCacheService::ReleaseObject_Locked(mData, mEventTarget);
+ mData = nullptr;
+ }
+
+ if (data) {
+ NS_ADDREF(mData = data);
+ mEventTarget = GetCurrentEventTarget();
+ }
+}
+
+void nsCacheEntry::TouchMetaData() {
+ mLastModified = SecondsFromPRTime(PR_Now());
+ MarkMetaDataDirty();
+}
+
+/**
+ * cache entry states
+ * 0 descriptors (new entry)
+ * 0 descriptors (existing, bound entry)
+ * n descriptors (existing, bound entry) valid
+ * n descriptors (existing, bound entry) not valid (wait until valid or
+ * doomed)
+ */
+
+nsresult nsCacheEntry::RequestAccess(nsCacheRequest* request,
+ nsCacheAccessMode* accessGranted) {
+ nsresult rv = NS_OK;
+
+ if (IsDoomed()) return NS_ERROR_CACHE_ENTRY_DOOMED;
+
+ if (!IsInitialized()) {
+ // brand new, unbound entry
+ if (request->IsStreamBased()) MarkStreamBased();
+ MarkInitialized();
+
+ *accessGranted = request->AccessRequested() & nsICache::ACCESS_WRITE;
+ NS_ASSERTION(*accessGranted, "new cache entry for READ-ONLY request");
+ PR_APPEND_LINK(request, &mRequestQ);
+ return rv;
+ }
+
+ if (IsStreamData() != request->IsStreamBased()) {
+ *accessGranted = nsICache::ACCESS_NONE;
+ return request->IsStreamBased() ? NS_ERROR_CACHE_DATA_IS_NOT_STREAM
+ : NS_ERROR_CACHE_DATA_IS_STREAM;
+ }
+
+ if (PR_CLIST_IS_EMPTY(&mDescriptorQ)) {
+ // 1st descriptor for existing bound entry
+ *accessGranted = request->AccessRequested();
+ if (*accessGranted & nsICache::ACCESS_WRITE) {
+ MarkInvalid();
+ } else {
+ MarkValid();
+ }
+ } else {
+ // nth request for existing, bound entry
+ *accessGranted = request->AccessRequested() & ~nsICache::ACCESS_WRITE;
+ if (!IsValid()) rv = NS_ERROR_CACHE_WAIT_FOR_VALIDATION;
+ }
+ PR_APPEND_LINK(request, &mRequestQ);
+
+ return rv;
+}
+
+nsresult nsCacheEntry::CreateDescriptor(nsCacheRequest* request,
+ nsCacheAccessMode accessGranted,
+ nsICacheEntryDescriptor** result) {
+ NS_ENSURE_ARG_POINTER(request && result);
+
+ nsCacheEntryDescriptor* descriptor =
+ new nsCacheEntryDescriptor(this, accessGranted);
+
+ // XXX check request is on q
+ PR_REMOVE_AND_INIT_LINK(request); // remove request regardless of success
+
+ if (descriptor == nullptr) return NS_ERROR_OUT_OF_MEMORY;
+
+ PR_APPEND_LINK(descriptor, &mDescriptorQ);
+
+ CACHE_LOG_DEBUG((" descriptor %p created for request %p on entry %p\n",
+ descriptor, request, this));
+
+ *result = do_AddRef(descriptor).take();
+ return NS_OK;
+}
+
+bool nsCacheEntry::RemoveRequest(nsCacheRequest* request) {
+ // XXX if debug: verify this request belongs to this entry
+ PR_REMOVE_AND_INIT_LINK(request);
+
+ // return true if this entry should stay active
+ return !((PR_CLIST_IS_EMPTY(&mRequestQ)) &&
+ (PR_CLIST_IS_EMPTY(&mDescriptorQ)));
+}
+
+bool nsCacheEntry::RemoveDescriptor(nsCacheEntryDescriptor* descriptor,
+ bool* doomEntry) {
+ NS_ASSERTION(descriptor->CacheEntry() == this, "### Wrong cache entry!!");
+
+ *doomEntry = descriptor->ClearCacheEntry();
+
+ PR_REMOVE_AND_INIT_LINK(descriptor);
+
+ if (!PR_CLIST_IS_EMPTY(&mDescriptorQ))
+ return true; // stay active if we still have open descriptors
+
+ if (PR_CLIST_IS_EMPTY(&mRequestQ))
+ return false; // no descriptors or requests, we can deactivate
+
+ return true; // find next best request to give a descriptor to
+}
+
+void nsCacheEntry::DetachDescriptors() {
+ nsCacheEntryDescriptor* descriptor =
+ (nsCacheEntryDescriptor*)PR_LIST_HEAD(&mDescriptorQ);
+
+ while (descriptor != &mDescriptorQ) {
+ nsCacheEntryDescriptor* nextDescriptor =
+ (nsCacheEntryDescriptor*)PR_NEXT_LINK(descriptor);
+
+ descriptor->ClearCacheEntry();
+ PR_REMOVE_AND_INIT_LINK(descriptor);
+ descriptor = nextDescriptor;
+ }
+}
+
+void nsCacheEntry::GetDescriptors(
+ nsTArray<RefPtr<nsCacheEntryDescriptor> >& outDescriptors) {
+ nsCacheEntryDescriptor* descriptor =
+ (nsCacheEntryDescriptor*)PR_LIST_HEAD(&mDescriptorQ);
+
+ while (descriptor != &mDescriptorQ) {
+ nsCacheEntryDescriptor* nextDescriptor =
+ (nsCacheEntryDescriptor*)PR_NEXT_LINK(descriptor);
+
+ outDescriptors.AppendElement(descriptor);
+ descriptor = nextDescriptor;
+ }
+}
+
+/******************************************************************************
+ * nsCacheEntryInfo - for implementing about:cache
+ *****************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsCacheEntryInfo, nsICacheEntryInfo)
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetClientID(nsACString& aClientID) {
+ if (!mCacheEntry) {
+ aClientID.Truncate();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return ClientIDFromCacheKey(*mCacheEntry->Key(), aClientID);
+}
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetDeviceID(nsACString& aDeviceID) {
+ if (!mCacheEntry) {
+ aDeviceID.Truncate();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aDeviceID.Assign(mCacheEntry->GetDeviceID());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetKey(nsACString& key) {
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return ClientKeyFromCacheKey(*mCacheEntry->Key(), key);
+}
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetFetchCount(int32_t* fetchCount) {
+ NS_ENSURE_ARG_POINTER(fetchCount);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *fetchCount = mCacheEntry->FetchCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetLastFetched(uint32_t* lastFetched) {
+ NS_ENSURE_ARG_POINTER(lastFetched);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *lastFetched = mCacheEntry->LastFetched();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetLastModified(uint32_t* lastModified) {
+ NS_ENSURE_ARG_POINTER(lastModified);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *lastModified = mCacheEntry->LastModified();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetExpirationTime(uint32_t* expirationTime) {
+ NS_ENSURE_ARG_POINTER(expirationTime);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *expirationTime = mCacheEntry->ExpirationTime();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryInfo::GetDataSize(uint32_t* dataSize) {
+ NS_ENSURE_ARG_POINTER(dataSize);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *dataSize = mCacheEntry->DataSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryInfo::IsStreamBased(bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->IsStreamData();
+ return NS_OK;
+}
+
+/******************************************************************************
+ * nsCacheEntryHashTable
+ *****************************************************************************/
+
+const PLDHashTableOps nsCacheEntryHashTable::ops = {HashKey, MatchEntry,
+ MoveEntry, ClearEntry};
+
+nsCacheEntryHashTable::nsCacheEntryHashTable()
+ : table(&ops, sizeof(nsCacheEntryHashTableEntry), kInitialTableLength),
+ initialized(false) {
+ MOZ_COUNT_CTOR(nsCacheEntryHashTable);
+}
+
+nsCacheEntryHashTable::~nsCacheEntryHashTable() {
+ MOZ_COUNT_DTOR(nsCacheEntryHashTable);
+ if (initialized) Shutdown();
+}
+
+void nsCacheEntryHashTable::Init() {
+ table.ClearAndPrepareForLength(kInitialTableLength);
+ initialized = true;
+}
+
+void nsCacheEntryHashTable::Shutdown() {
+ if (initialized) {
+ table.ClearAndPrepareForLength(kInitialTableLength);
+ initialized = false;
+ }
+}
+
+nsCacheEntry* nsCacheEntryHashTable::GetEntry(const nsCString* key) const {
+ NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized");
+ if (!initialized) return nullptr;
+
+ PLDHashEntryHdr* hashEntry = table.Search(key);
+ return hashEntry ? ((nsCacheEntryHashTableEntry*)hashEntry)->cacheEntry
+ : nullptr;
+}
+
+nsresult nsCacheEntryHashTable::AddEntry(nsCacheEntry* cacheEntry) {
+ PLDHashEntryHdr* hashEntry;
+
+ NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized");
+ if (!initialized) return NS_ERROR_NOT_INITIALIZED;
+ if (!cacheEntry) return NS_ERROR_NULL_POINTER;
+
+ hashEntry = table.Add(&(cacheEntry->mKey), fallible);
+
+ if (!hashEntry) return NS_ERROR_FAILURE;
+ NS_ASSERTION(((nsCacheEntryHashTableEntry*)hashEntry)->cacheEntry == nullptr,
+ "### nsCacheEntryHashTable::AddEntry - entry already used");
+ ((nsCacheEntryHashTableEntry*)hashEntry)->cacheEntry = cacheEntry;
+
+ return NS_OK;
+}
+
+void nsCacheEntryHashTable::RemoveEntry(nsCacheEntry* cacheEntry) {
+ NS_ASSERTION(initialized, "nsCacheEntryHashTable not initialized");
+ NS_ASSERTION(cacheEntry, "### cacheEntry == nullptr");
+
+ if (!initialized) return; // NS_ERROR_NOT_INITIALIZED
+
+#if DEBUG
+ // XXX debug code to make sure we have the entry we're trying to remove
+ nsCacheEntry* check = GetEntry(&(cacheEntry->mKey));
+ NS_ASSERTION(check == cacheEntry,
+ "### Attempting to remove unknown cache entry!!!");
+#endif
+ table.Remove(&(cacheEntry->mKey));
+}
+
+PLDHashTable::Iterator nsCacheEntryHashTable::Iter() {
+ return PLDHashTable::Iterator(&table);
+}
+
+/**
+ * hash table operation callback functions
+ */
+
+PLDHashNumber nsCacheEntryHashTable::HashKey(const void* key) {
+ return HashString(*static_cast<const nsCString*>(key));
+}
+
+bool nsCacheEntryHashTable::MatchEntry(const PLDHashEntryHdr* hashEntry,
+ const void* key) {
+ NS_ASSERTION(key != nullptr,
+ "### nsCacheEntryHashTable::MatchEntry : null key");
+ nsCacheEntry* cacheEntry =
+ ((nsCacheEntryHashTableEntry*)hashEntry)->cacheEntry;
+
+ return cacheEntry->mKey.Equals(*(nsCString*)key);
+}
+
+void nsCacheEntryHashTable::MoveEntry(PLDHashTable* /* table */,
+ const PLDHashEntryHdr* from,
+ PLDHashEntryHdr* to) {
+ new (KnownNotNull, to) nsCacheEntryHashTableEntry(
+ std::move(*((nsCacheEntryHashTableEntry*)from)));
+ // No need to destroy `from`.
+}
+
+void nsCacheEntryHashTable::ClearEntry(PLDHashTable* /* table */,
+ PLDHashEntryHdr* hashEntry) {
+ ((nsCacheEntryHashTableEntry*)hashEntry)->cacheEntry = nullptr;
+}
diff --git a/netwerk/cache/nsCacheEntry.h b/netwerk/cache/nsCacheEntry.h
new file mode 100644
index 0000000000..ee2a2af35f
--- /dev/null
+++ b/netwerk/cache/nsCacheEntry.h
@@ -0,0 +1,285 @@
+/* -*- 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 _nsCacheEntry_h_
+#define _nsCacheEntry_h_
+
+#include "nsICache.h"
+#include "nsICacheEntryDescriptor.h"
+#include "nsCacheMetaData.h"
+
+#include "nspr.h"
+#include "PLDHashTable.h"
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsAString.h"
+
+class nsCacheDevice;
+class nsCacheMetaData;
+class nsCacheRequest;
+class nsCacheEntryDescriptor;
+class nsIEventTarget;
+
+/******************************************************************************
+ * nsCacheEntry
+ *******************************************************************************/
+class nsCacheEntry : public PRCList {
+ public:
+ nsCacheEntry(const nsACString& key, bool streamBased,
+ nsCacheStoragePolicy storagePolicy);
+ ~nsCacheEntry();
+
+ static nsresult Create(const char* key, bool streamBased,
+ nsCacheStoragePolicy storagePolicy,
+ nsCacheDevice* device, nsCacheEntry** result);
+
+ nsCString* Key() { return &mKey; }
+
+ int32_t FetchCount() { return mFetchCount; }
+ void SetFetchCount(int32_t count) { mFetchCount = count; }
+ void Fetched();
+
+ uint32_t LastFetched() { return mLastFetched; }
+ void SetLastFetched(uint32_t lastFetched) { mLastFetched = lastFetched; }
+
+ uint32_t LastModified() { return mLastModified; }
+ void SetLastModified(uint32_t lastModified) { mLastModified = lastModified; }
+
+ uint32_t ExpirationTime() { return mExpirationTime; }
+ void SetExpirationTime(uint32_t expires) { mExpirationTime = expires; }
+
+ uint32_t Size() { return mDataSize + mMetaData.Size() + mKey.Length(); }
+
+ nsCacheDevice* CacheDevice() { return mCacheDevice; }
+ void SetCacheDevice(nsCacheDevice* device) { mCacheDevice = device; }
+ void SetCustomCacheDevice(nsCacheDevice* device) { mCustomDevice = device; }
+ nsCacheDevice* CustomCacheDevice() { return mCustomDevice; }
+ const char* GetDeviceID();
+
+ /**
+ * Data accessors
+ */
+ nsISupports* Data() { return mData; }
+ void SetData(nsISupports* data);
+
+ int64_t PredictedDataSize() { return mPredictedDataSize; }
+ void SetPredictedDataSize(int64_t size) { mPredictedDataSize = size; }
+
+ uint32_t DataSize() { return mDataSize; }
+ void SetDataSize(uint32_t size) { mDataSize = size; }
+
+ void TouchData();
+
+ /**
+ * Meta data accessors
+ */
+ const char* GetMetaDataElement(const char* key) {
+ return mMetaData.GetElement(key);
+ }
+ nsresult SetMetaDataElement(const char* key, const char* value) {
+ return mMetaData.SetElement(key, value);
+ }
+ nsresult VisitMetaDataElements(nsICacheMetaDataVisitor* visitor) {
+ return mMetaData.VisitElements(visitor);
+ }
+ nsresult FlattenMetaData(char* buffer, uint32_t bufSize) {
+ return mMetaData.FlattenMetaData(buffer, bufSize);
+ }
+ nsresult UnflattenMetaData(const char* buffer, uint32_t bufSize) {
+ return mMetaData.UnflattenMetaData(buffer, bufSize);
+ }
+ uint32_t MetaDataSize() { return mMetaData.Size(); }
+
+ void TouchMetaData();
+
+ /**
+ * Security Info accessors
+ */
+ nsISupports* SecurityInfo() { return mSecurityInfo; }
+ void SetSecurityInfo(nsISupports* info) { mSecurityInfo = info; }
+
+ // XXX enumerate MetaData method
+
+ enum CacheEntryFlags {
+ eStoragePolicyMask = 0x000000FF,
+ eDoomedMask = 0x00000100,
+ eEntryDirtyMask = 0x00000200,
+ eDataDirtyMask = 0x00000400,
+ eMetaDataDirtyMask = 0x00000800,
+ eStreamDataMask = 0x00001000,
+ eActiveMask = 0x00002000,
+ eInitializedMask = 0x00004000,
+ eValidMask = 0x00008000,
+ eBindingMask = 0x00010000,
+ ePrivateMask = 0x00020000
+ };
+
+ void MarkBinding() { mFlags |= eBindingMask; }
+ void ClearBinding() { mFlags &= ~eBindingMask; }
+ bool IsBinding() { return (mFlags & eBindingMask) != 0; }
+
+ void MarkEntryDirty() { mFlags |= eEntryDirtyMask; }
+ void MarkEntryClean() { mFlags &= ~eEntryDirtyMask; }
+ void MarkDataDirty() { mFlags |= eDataDirtyMask; }
+ void MarkDataClean() { mFlags &= ~eDataDirtyMask; }
+ void MarkMetaDataDirty() { mFlags |= eMetaDataDirtyMask; }
+ void MarkMetaDataClean() { mFlags &= ~eMetaDataDirtyMask; }
+ void MarkStreamData() { mFlags |= eStreamDataMask; }
+ void MarkValid() { mFlags |= eValidMask; }
+ void MarkInvalid() { mFlags &= ~eValidMask; }
+ void MarkPrivate() { mFlags |= ePrivateMask; }
+ void MarkPublic() { mFlags &= ~ePrivateMask; }
+ // void MarkAllowedInMemory() { mFlags |= eAllowedInMemoryMask; }
+ // void MarkAllowedOnDisk() { mFlags |= eAllowedOnDiskMask; }
+
+ bool IsDoomed() { return (mFlags & eDoomedMask) != 0; }
+ bool IsEntryDirty() { return (mFlags & eEntryDirtyMask) != 0; }
+ bool IsDataDirty() { return (mFlags & eDataDirtyMask) != 0; }
+ bool IsMetaDataDirty() { return (mFlags & eMetaDataDirtyMask) != 0; }
+ bool IsStreamData() { return (mFlags & eStreamDataMask) != 0; }
+ bool IsActive() { return (mFlags & eActiveMask) != 0; }
+ bool IsInitialized() { return (mFlags & eInitializedMask) != 0; }
+ bool IsValid() { return (mFlags & eValidMask) != 0; }
+ bool IsInvalid() { return (mFlags & eValidMask) == 0; }
+ bool IsInUse() {
+ return IsBinding() ||
+ !(PR_CLIST_IS_EMPTY(&mRequestQ) && PR_CLIST_IS_EMPTY(&mDescriptorQ));
+ }
+ bool IsNotInUse() { return !IsInUse(); }
+ bool IsPrivate() { return (mFlags & ePrivateMask) != 0; }
+
+ bool IsAllowedInMemory() {
+ return (StoragePolicy() == nsICache::STORE_ANYWHERE) ||
+ (StoragePolicy() == nsICache::STORE_IN_MEMORY);
+ }
+
+ bool IsAllowedOnDisk() {
+ return !IsPrivate() && ((StoragePolicy() == nsICache::STORE_ANYWHERE) ||
+ (StoragePolicy() == nsICache::STORE_ON_DISK));
+ }
+
+ bool IsAllowedOffline() {
+ return (StoragePolicy() == nsICache::STORE_OFFLINE);
+ }
+
+ nsCacheStoragePolicy StoragePolicy() {
+ return (nsCacheStoragePolicy)(mFlags & eStoragePolicyMask);
+ }
+
+ void SetStoragePolicy(nsCacheStoragePolicy policy) {
+ NS_ASSERTION(policy <= 0xFF, "too many bits in nsCacheStoragePolicy");
+ mFlags &= ~eStoragePolicyMask; // clear storage policy bits
+ mFlags |= policy;
+ }
+
+ // methods for nsCacheService
+ nsresult RequestAccess(nsCacheRequest* request,
+ nsCacheAccessMode* accessGranted);
+ nsresult CreateDescriptor(nsCacheRequest* request,
+ nsCacheAccessMode accessGranted,
+ nsICacheEntryDescriptor** result);
+
+ bool RemoveRequest(nsCacheRequest* request);
+ bool RemoveDescriptor(nsCacheEntryDescriptor* descriptor, bool* doomEntry);
+
+ void GetDescriptors(
+ nsTArray<RefPtr<nsCacheEntryDescriptor> >& outDescriptors);
+
+ private:
+ friend class nsCacheEntryHashTable;
+ friend class nsCacheService;
+
+ void DetachDescriptors();
+
+ // internal methods
+ void MarkDoomed() { mFlags |= eDoomedMask; }
+ void MarkStreamBased() { mFlags |= eStreamDataMask; }
+ void MarkInitialized() { mFlags |= eInitializedMask; }
+ void MarkActive() { mFlags |= eActiveMask; }
+ void MarkInactive() { mFlags &= ~eActiveMask; }
+
+ nsCString mKey;
+ uint32_t mFetchCount; // 4
+ uint32_t mLastFetched; // 4
+ uint32_t mLastModified; // 4
+ uint32_t mLastValidated; // 4
+ uint32_t mExpirationTime; // 4
+ uint32_t mFlags; // 4
+ int64_t mPredictedDataSize; // Size given by ContentLength.
+ uint32_t mDataSize; // 4
+ nsCacheDevice* mCacheDevice; // 4
+ nsCacheDevice* mCustomDevice; // 4
+ nsCOMPtr<nsISupports> mSecurityInfo; //
+ nsISupports* mData; // strong ref
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+ nsCacheMetaData mMetaData; // 4
+ PRCList mRequestQ; // 8
+ PRCList mDescriptorQ; // 8
+};
+
+/******************************************************************************
+ * nsCacheEntryInfo
+ *******************************************************************************/
+class nsCacheEntryInfo : public nsICacheEntryInfo {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYINFO
+
+ explicit nsCacheEntryInfo(nsCacheEntry* entry) : mCacheEntry(entry) {}
+
+ void DetachEntry() { mCacheEntry = nullptr; }
+
+ private:
+ nsCacheEntry* mCacheEntry;
+
+ virtual ~nsCacheEntryInfo() = default;
+};
+
+/******************************************************************************
+ * nsCacheEntryHashTable
+ *******************************************************************************/
+
+struct nsCacheEntryHashTableEntry : public PLDHashEntryHdr {
+ nsCacheEntry* cacheEntry;
+};
+
+class nsCacheEntryHashTable {
+ public:
+ nsCacheEntryHashTable();
+ ~nsCacheEntryHashTable();
+
+ void Init();
+ void Shutdown();
+
+ nsCacheEntry* GetEntry(const nsCString* key) const;
+ nsresult AddEntry(nsCacheEntry* entry);
+ void RemoveEntry(nsCacheEntry* entry);
+
+ PLDHashTable::Iterator Iter();
+
+ private:
+ // PLDHashTable operation callbacks
+ static PLDHashNumber HashKey(const void* key);
+
+ static bool MatchEntry(const PLDHashEntryHdr* entry, const void* key);
+
+ static void MoveEntry(PLDHashTable* table, const PLDHashEntryHdr* from,
+ PLDHashEntryHdr* to);
+
+ static void ClearEntry(PLDHashTable* table, PLDHashEntryHdr* entry);
+
+ static void Finalize(PLDHashTable* table);
+
+ // member variables
+ static const PLDHashTableOps ops;
+ PLDHashTable table;
+ bool initialized;
+
+ static const uint32_t kInitialTableLength = 256;
+};
+
+#endif // _nsCacheEntry_h_
diff --git a/netwerk/cache/nsCacheEntryDescriptor.cpp b/netwerk/cache/nsCacheEntryDescriptor.cpp
new file mode 100644
index 0000000000..c879e8b31b
--- /dev/null
+++ b/netwerk/cache/nsCacheEntryDescriptor.cpp
@@ -0,0 +1,1307 @@
+/* -*- 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 "nsICache.h"
+#include "nsCache.h"
+#include "nsCacheService.h"
+#include "nsCacheEntryDescriptor.h"
+#include "nsCacheEntry.h"
+#include "nsReadableUtils.h"
+#include "nsIOutputStream.h"
+#include "nsCRT.h"
+#include "nsThreadUtils.h"
+#include <algorithm>
+#include "mozilla/IntegerPrintfMacros.h"
+
+#define kMinDecompressReadBufLen 1024
+#define kMinCompressWriteBufLen 1024
+
+/******************************************************************************
+ * nsAsyncDoomEvent
+ *****************************************************************************/
+
+class nsAsyncDoomEvent : public mozilla::Runnable {
+ public:
+ nsAsyncDoomEvent(nsCacheEntryDescriptor* descriptor,
+ nsICacheListener* listener)
+ : mozilla::Runnable("nsAsyncDoomEvent") {
+ mDescriptor = descriptor;
+ mListener = listener;
+ mEventTarget = GetCurrentEventTarget();
+ // We addref the listener here and release it in nsNotifyDoomListener
+ // on the callers thread. If posting of nsNotifyDoomListener event fails
+ // we leak the listener which is better than releasing it on a wrong
+ // thread.
+ NS_IF_ADDREF(mListener);
+ }
+
+ NS_IMETHOD Run() override {
+ nsresult status = NS_OK;
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSASYNCDOOMEVENT_RUN));
+
+ if (mDescriptor->mCacheEntry) {
+ status = nsCacheService::gService->DoomEntry_Internal(
+ mDescriptor->mCacheEntry, true);
+ } else if (!mDescriptor->mDoomedOnClose) {
+ status = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ if (mListener) {
+ mEventTarget->Dispatch(new nsNotifyDoomListener(mListener, status),
+ NS_DISPATCH_NORMAL);
+ // posted event will release the reference on the correct thread
+ mListener = nullptr;
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsCacheEntryDescriptor> mDescriptor;
+ nsICacheListener* mListener;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+NS_IMPL_ISUPPORTS(nsCacheEntryDescriptor, nsICacheEntryDescriptor,
+ nsICacheEntryInfo)
+
+nsCacheEntryDescriptor::nsCacheEntryDescriptor(nsCacheEntry* entry,
+ nsCacheAccessMode accessGranted)
+ : mCacheEntry(entry),
+ mAccessGranted(accessGranted),
+ mOutputWrapper(nullptr),
+ mLock("nsCacheEntryDescriptor.mLock"),
+ mAsyncDoomPending(false),
+ mDoomedOnClose(false),
+ mClosingDescriptor(false) {
+ PR_INIT_CLIST(this);
+ // We need to make sure the cache service lives for the entire lifetime
+ // of the descriptor
+ mCacheService = nsCacheService::GlobalInstance();
+}
+
+nsCacheEntryDescriptor::~nsCacheEntryDescriptor() {
+ // No need to close if the cache entry has already been severed. This
+ // helps avoid a shutdown assertion (bug 285519) that is caused when
+ // consumers end up holding onto these objects past xpcom-shutdown. It's
+ // okay for them to do that because the cache service calls our Close
+ // method during xpcom-shutdown, so we don't need to complain about it.
+ if (mCacheEntry) Close();
+
+ NS_ASSERTION(mInputWrappers.IsEmpty(), "We have still some input wrapper!");
+ NS_ASSERTION(!mOutputWrapper, "We have still an output wrapper!");
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetClientID(nsACString& aClientID) {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETCLIENTID));
+ if (!mCacheEntry) {
+ aClientID.Truncate();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return ClientIDFromCacheKey(*(mCacheEntry->Key()), aClientID);
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetDeviceID(nsACString& aDeviceID) {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETDEVICEID));
+ if (!mCacheEntry) {
+ aDeviceID.Truncate();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aDeviceID.Assign(mCacheEntry->GetDeviceID());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetKey(nsACString& result) {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETKEY));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return ClientKeyFromCacheKey(*(mCacheEntry->Key()), result);
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetFetchCount(int32_t* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETFETCHCOUNT));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->FetchCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetLastFetched(uint32_t* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETLASTFETCHED));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->LastFetched();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetLastModified(uint32_t* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETLASTMODIFIED));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->LastModified();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetExpirationTime(uint32_t* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETEXPIRATIONTIME));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->ExpirationTime();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetExpirationTime(uint32_t expirationTime) {
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETEXPIRATIONTIME));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ mCacheEntry->SetExpirationTime(expirationTime);
+ mCacheEntry->MarkEntryDirty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::IsStreamBased(bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_ISSTREAMBASED));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->IsStreamData();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::GetPredictedDataSize(int64_t* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETPREDICTEDDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->PredictedDataSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::SetPredictedDataSize(
+ int64_t predictedSize) {
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETPREDICTEDDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ mCacheEntry->SetPredictedDataSize(predictedSize);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::GetDataSize(uint32_t* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ const char* val = mCacheEntry->GetMetaDataElement("uncompressed-len");
+ if (!val) {
+ *result = mCacheEntry->DataSize();
+ } else {
+ *result = atol(val);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::GetStorageDataSize(uint32_t* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSTORAGEDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->DataSize();
+
+ return NS_OK;
+}
+
+nsresult nsCacheEntryDescriptor::RequestDataSizeChange(int32_t deltaSize) {
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_REQUESTDATASIZECHANGE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv;
+ rv = nsCacheService::OnDataSizeChange(mCacheEntry, deltaSize);
+ if (NS_SUCCEEDED(rv)) {
+ // XXX review for signed/unsigned math errors
+ uint32_t newDataSize = mCacheEntry->DataSize() + deltaSize;
+ mCacheEntry->SetDataSize(newDataSize);
+ mCacheEntry->TouchData();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetDataSize(uint32_t dataSize) {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ // XXX review for signed/unsigned math errors
+ int32_t deltaSize = dataSize - mCacheEntry->DataSize();
+
+ nsresult rv;
+ rv = nsCacheService::OnDataSizeChange(mCacheEntry, deltaSize);
+ // this had better be NS_OK, this call instance is advisory for memory cache
+ // objects
+ if (NS_SUCCEEDED(rv)) {
+ // XXX review for signed/unsigned math errors
+ uint32_t newDataSize = mCacheEntry->DataSize() + deltaSize;
+ mCacheEntry->SetDataSize(newDataSize);
+ mCacheEntry->TouchData();
+ } else {
+ NS_WARNING("failed SetDataSize() on memory cache object!");
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::OpenInputStream(uint32_t offset,
+ nsIInputStream** result) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ RefPtr<nsInputStreamWrapper> cacheInput;
+ {
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_OPENINPUTSTREAM));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ if (!mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_NOT_STREAM;
+
+ // Don't open any new stream when closing descriptor or clearing entries
+ if (mClosingDescriptor || nsCacheService::GetClearingEntries())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // ensure valid permissions
+ if (!(mAccessGranted & nsICache::ACCESS_READ))
+ return NS_ERROR_CACHE_READ_ACCESS_DENIED;
+
+ const char* val;
+ val = mCacheEntry->GetMetaDataElement("uncompressed-len");
+ if (val) {
+ cacheInput = new nsDecompressInputStreamWrapper(this, offset);
+ } else {
+ cacheInput = new nsInputStreamWrapper(this, offset);
+ }
+ if (!cacheInput) return NS_ERROR_OUT_OF_MEMORY;
+
+ mInputWrappers.AppendElement(cacheInput);
+ }
+
+ cacheInput.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::OpenOutputStream(uint32_t offset,
+ nsIOutputStream** result) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ RefPtr<nsOutputStreamWrapper> cacheOutput;
+ {
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_OPENOUTPUTSTREAM));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ if (!mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_NOT_STREAM;
+
+ // Don't open any new stream when closing descriptor or clearing entries
+ if (mClosingDescriptor || nsCacheService::GetClearingEntries())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // ensure valid permissions
+ if (!(mAccessGranted & nsICache::ACCESS_WRITE))
+ return NS_ERROR_CACHE_WRITE_ACCESS_DENIED;
+
+ int32_t compressionLevel = nsCacheService::CacheCompressionLevel();
+ const char* val;
+ val = mCacheEntry->GetMetaDataElement("uncompressed-len");
+ if ((compressionLevel > 0) && val) {
+ cacheOutput = new nsCompressOutputStreamWrapper(this, offset);
+ } else {
+ // clear compression flag when compression disabled - see bug 715198
+ if (val) {
+ mCacheEntry->SetMetaDataElement("uncompressed-len", nullptr);
+ }
+ cacheOutput = new nsOutputStreamWrapper(this, offset);
+ }
+ if (!cacheOutput) return NS_ERROR_OUT_OF_MEMORY;
+
+ mOutputWrapper = cacheOutput;
+ }
+
+ cacheOutput.forget(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetCacheElement(nsISupports** result) {
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETCACHEELEMENT));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ if (mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_STREAM;
+
+ NS_IF_ADDREF(*result = mCacheEntry->Data());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetCacheElement(nsISupports* cacheElement) {
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETCACHEELEMENT));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ if (mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_STREAM;
+
+ return nsCacheService::SetCacheElement(mCacheEntry, cacheElement);
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetAccessGranted(nsCacheAccessMode* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = mAccessGranted;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetStoragePolicy(nsCacheStoragePolicy* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSTORAGEPOLICY));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->StoragePolicy();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetStoragePolicy(nsCacheStoragePolicy policy) {
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETSTORAGEPOLICY));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ // XXX validate policy against session?
+
+ bool storageEnabled = false;
+ storageEnabled = nsCacheService::IsStorageEnabledForPolicy_Locked(policy);
+ if (!storageEnabled) return NS_ERROR_FAILURE;
+
+ // Don't change the storage policy of entries we can't write
+ if (!(mAccessGranted & nsICache::ACCESS_WRITE)) return NS_ERROR_NOT_AVAILABLE;
+
+ // Don't allow a cache entry to move from memory-only to anything else
+ if (mCacheEntry->StoragePolicy() == nsICache::STORE_IN_MEMORY &&
+ policy != nsICache::STORE_IN_MEMORY)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ mCacheEntry->SetStoragePolicy(policy);
+ mCacheEntry->MarkEntryDirty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetFile(nsIFile** result) {
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETFILE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return nsCacheService::GetFileForEntry(mCacheEntry, result);
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetSecurityInfo(nsISupports** result) {
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSECURITYINFO));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->SecurityInfo();
+ NS_IF_ADDREF(*result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetSecurityInfo(nsISupports* securityInfo) {
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETSECURITYINFO));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ mCacheEntry->SetSecurityInfo(securityInfo);
+ mCacheEntry->MarkEntryDirty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::Doom() {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_DOOM));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return nsCacheService::DoomEntry(mCacheEntry);
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::DoomAndFailPendingRequests(nsresult status) {
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_DOOMANDFAILPENDINGREQUESTS));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::AsyncDoom(nsICacheListener* listener) {
+ bool asyncDoomPending;
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ asyncDoomPending = mAsyncDoomPending;
+ mAsyncDoomPending = true;
+ }
+
+ if (asyncDoomPending) {
+ // AsyncDoom was already called. Notify listener if it is non-null,
+ // otherwise just return success.
+ if (listener) {
+ nsresult rv = NS_DispatchToCurrentThread(
+ new nsNotifyDoomListener(listener, NS_ERROR_NOT_AVAILABLE));
+ if (NS_SUCCEEDED(rv)) NS_IF_ADDREF(listener);
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIRunnable> event = new nsAsyncDoomEvent(this, listener);
+ return nsCacheService::DispatchToCacheIOThread(event);
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::MarkValid() {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_MARKVALID));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = nsCacheService::ValidateEntry(mCacheEntry);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::Close() {
+ RefPtr<nsOutputStreamWrapper> outputWrapper;
+ nsTArray<RefPtr<nsInputStreamWrapper> > inputWrappers;
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_CLOSE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ // Make sure no other stream can be opened
+ mClosingDescriptor = true;
+ outputWrapper = mOutputWrapper;
+ for (size_t i = 0; i < mInputWrappers.Length(); i++)
+ inputWrappers.AppendElement(mInputWrappers[i]);
+ }
+
+ // Call Close() on the streams outside the lock since it might need to call
+ // methods that grab the cache service lock, e.g. compressed output stream
+ // when it finalizes the entry
+ if (outputWrapper) {
+ if (NS_FAILED(outputWrapper->Close())) {
+ NS_WARNING("Dooming entry because Close() failed!!!");
+ Doom();
+ }
+ outputWrapper = nullptr;
+ }
+
+ for (uint32_t i = 0; i < inputWrappers.Length(); i++)
+ inputWrappers[i]->Close();
+
+ inputWrappers.Clear();
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_CLOSE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ // XXX perhaps closing descriptors should clear/sever transports
+
+ // tell nsCacheService we're going away
+ nsCacheService::CloseDescriptor(this);
+ NS_ASSERTION(mCacheEntry == nullptr, "mCacheEntry not null");
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetMetaDataElement(const char* key, char** result) {
+ NS_ENSURE_ARG_POINTER(key);
+ *result = nullptr;
+
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETMETADATAELEMENT));
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_AVAILABLE);
+
+ const char* value;
+
+ value = mCacheEntry->GetMetaDataElement(key);
+ if (!value) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = NS_xstrdup(value);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetMetaDataElement(const char* key, const char* value) {
+ NS_ENSURE_ARG_POINTER(key);
+
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETMETADATAELEMENT));
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_AVAILABLE);
+
+ // XXX allow null value, for clearing key?
+
+ nsresult rv = mCacheEntry->SetMetaDataElement(key, value);
+ if (NS_SUCCEEDED(rv)) mCacheEntry->TouchMetaData();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::VisitMetaData(nsICacheMetaDataVisitor* visitor) {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_VISITMETADATA));
+ // XXX check callers, we're calling out of module
+ NS_ENSURE_ARG_POINTER(visitor);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return mCacheEntry->VisitMetaDataElements(visitor);
+}
+
+/******************************************************************************
+ * nsCacheInputStream - a wrapper for nsIInputStream keeps the cache entry
+ * open while referenced.
+ ******************************************************************************/
+
+NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsInputStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsCacheEntryDescriptor::nsInputStreamWrapper::Release() {
+ // Holding a reference to descriptor ensures that cache service won't go
+ // away. Do not grab cache service lock if there is no descriptor.
+ RefPtr<nsCacheEntryDescriptor> desc;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ desc = mDescriptor;
+ }
+
+ if (desc) nsCacheService::Lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_RELEASE));
+
+ nsrefcnt count;
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "nsCacheEntryDescriptor::nsInputStreamWrapper");
+
+ if (0 == count) {
+ // don't use desc here since mDescriptor might be already nulled out
+ if (mDescriptor) {
+ NS_ASSERTION(mDescriptor->mInputWrappers.Contains(this),
+ "Wrapper not found in array!");
+ mDescriptor->mInputWrappers.RemoveElement(this);
+ }
+
+ if (desc) nsCacheService::Unlock();
+
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ if (desc) nsCacheService::Unlock();
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsInputStreamWrapper)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::LazyInit() {
+ // Check if we have the descriptor. If not we can't even grab the cache
+ // lock since it is not ensured that the cache service still exists.
+ if (!mDescriptor) return NS_ERROR_NOT_AVAILABLE;
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_LAZYINIT));
+
+ nsCacheAccessMode mode;
+ nsresult rv = mDescriptor->GetAccessGranted(&mode);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ENSURE_TRUE(mode & nsICache::ACCESS_READ, NS_ERROR_UNEXPECTED);
+
+ nsCacheEntry* cacheEntry = mDescriptor->CacheEntry();
+ if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ rv = nsCacheService::OpenInputStreamForEntry(cacheEntry, mode, mStartOffset,
+ getter_AddRefs(mInput));
+
+ CACHE_LOG_DEBUG(
+ ("nsInputStreamWrapper::LazyInit "
+ "[entry=%p, wrapper=%p, mInput=%p, rv=%d]",
+ mDescriptor, this, mInput.get(), int(rv)));
+
+ if (NS_FAILED(rv)) return rv;
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::EnsureInit() {
+ if (mInitialized) {
+ NS_ASSERTION(mDescriptor, "Bad state");
+ return NS_OK;
+ }
+
+ return LazyInit();
+}
+
+void nsCacheEntryDescriptor::nsInputStreamWrapper::CloseInternal() {
+ mLock.AssertCurrentThreadOwns();
+ if (!mDescriptor) {
+ NS_ASSERTION(!mInitialized, "Bad state");
+ NS_ASSERTION(!mInput, "Bad state");
+ return;
+ }
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_CLOSEINTERNAL));
+
+ if (mDescriptor) {
+ mDescriptor->mInputWrappers.RemoveElement(this);
+ nsCacheService::ReleaseObject_Locked(mDescriptor);
+ mDescriptor = nullptr;
+ }
+ mInitialized = false;
+ mInput = nullptr;
+}
+
+nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::Close() {
+ mozilla::MutexAutoLock lock(mLock);
+
+ return Close_Locked();
+}
+
+nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::Close_Locked() {
+ nsresult rv = EnsureInit();
+ if (NS_SUCCEEDED(rv)) {
+ rv = mInput->Close();
+ } else {
+ NS_ASSERTION(!mInput, "Shouldn't have mInput when EnsureInit() failed");
+ }
+
+ // Call CloseInternal() even when EnsureInit() failed, e.g. in case we are
+ // closing streams with nsCacheService::CloseAllStream()
+ CloseInternal();
+ return rv;
+}
+
+nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::Available(
+ uint64_t* avail) {
+ mozilla::MutexAutoLock lock(mLock);
+
+ nsresult rv = EnsureInit();
+ if (NS_FAILED(rv)) return rv;
+
+ return mInput->Available(avail);
+}
+
+nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::Read(
+ char* buf, uint32_t count, uint32_t* countRead) {
+ mozilla::MutexAutoLock lock(mLock);
+
+ return Read_Locked(buf, count, countRead);
+}
+
+nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::Read_Locked(
+ char* buf, uint32_t count, uint32_t* countRead) {
+ nsresult rv = EnsureInit();
+ if (NS_SUCCEEDED(rv)) rv = mInput->Read(buf, count, countRead);
+
+ CACHE_LOG_DEBUG(
+ ("nsInputStreamWrapper::Read "
+ "[entry=%p, wrapper=%p, mInput=%p, rv=%" PRId32 "]",
+ mDescriptor, this, mInput.get(), static_cast<uint32_t>(rv)));
+
+ return rv;
+}
+
+nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::ReadSegments(
+ nsWriteSegmentFun writer, void* closure, uint32_t count,
+ uint32_t* countRead) {
+ // cache stream not buffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsCacheEntryDescriptor::nsInputStreamWrapper::IsNonBlocking(
+ bool* result) {
+ // cache streams will never return NS_BASE_STREAM_WOULD_BLOCK
+ *result = false;
+ return NS_OK;
+}
+
+/******************************************************************************
+ * nsDecompressInputStreamWrapper - an input stream wrapper that decompresses
+ ******************************************************************************/
+
+NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsDecompressInputStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsCacheEntryDescriptor::nsDecompressInputStreamWrapper::Release() {
+ // Holding a reference to descriptor ensures that cache service won't go
+ // away. Do not grab cache service lock if there is no descriptor.
+ RefPtr<nsCacheEntryDescriptor> desc;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ desc = mDescriptor;
+ }
+
+ if (desc)
+ nsCacheService::Lock(LOCK_TELEM(NSDECOMPRESSINPUTSTREAMWRAPPER_RELEASE));
+
+ nsrefcnt count;
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count,
+ "nsCacheEntryDescriptor::nsDecompressInputStreamWrapper");
+
+ if (0 == count) {
+ // don't use desc here since mDescriptor might be already nulled out
+ if (mDescriptor) {
+ NS_ASSERTION(mDescriptor->mInputWrappers.Contains(this),
+ "Wrapper not found in array!");
+ mDescriptor->mInputWrappers.RemoveElement(this);
+ }
+
+ if (desc) nsCacheService::Unlock();
+
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ if (desc) nsCacheService::Unlock();
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsDecompressInputStreamWrapper)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP nsCacheEntryDescriptor::nsDecompressInputStreamWrapper::Read(
+ char* buf, uint32_t count, uint32_t* countRead) {
+ mozilla::MutexAutoLock lock(mLock);
+
+ int zerr = Z_OK;
+ nsresult rv = NS_OK;
+
+ if (!mStreamInitialized) {
+ rv = InitZstream();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ mZstream.next_out = (Bytef*)buf;
+ mZstream.avail_out = count;
+
+ if (mReadBufferLen < count) {
+ // Allocate a buffer for reading from the input stream. This will
+ // determine the max number of compressed bytes read from the
+ // input stream at one time. Making the buffer size proportional
+ // to the request size is not necessary, but helps minimize the
+ // number of read requests to the input stream.
+ uint32_t newBufLen = std::max(count, (uint32_t)kMinDecompressReadBufLen);
+ mReadBuffer = (unsigned char*)moz_xrealloc(mReadBuffer, newBufLen);
+ mReadBufferLen = newBufLen;
+ if (!mReadBuffer) {
+ mReadBufferLen = 0;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ // read and inflate data until the output buffer is full, or
+ // there is no more data to read
+ while (NS_SUCCEEDED(rv) && zerr == Z_OK && mZstream.avail_out > 0 &&
+ count > 0) {
+ if (mZstream.avail_in == 0) {
+ rv = nsInputStreamWrapper::Read_Locked((char*)mReadBuffer, mReadBufferLen,
+ &mZstream.avail_in);
+ if (NS_FAILED(rv) || !mZstream.avail_in) {
+ break;
+ }
+ mZstream.next_in = mReadBuffer;
+ }
+ zerr = inflate(&mZstream, Z_NO_FLUSH);
+ if (zerr == Z_STREAM_END) {
+ // The compressed data may have been stored in multiple
+ // chunks/streams. To allow for this case, re-initialize
+ // the inflate stream and continue decompressing from
+ // the next byte.
+ Bytef* saveNextIn = mZstream.next_in;
+ unsigned int saveAvailIn = mZstream.avail_in;
+ Bytef* saveNextOut = mZstream.next_out;
+ unsigned int saveAvailOut = mZstream.avail_out;
+ inflateReset(&mZstream);
+ mZstream.next_in = saveNextIn;
+ mZstream.avail_in = saveAvailIn;
+ mZstream.next_out = saveNextOut;
+ mZstream.avail_out = saveAvailOut;
+ zerr = Z_OK;
+ } else if (zerr != Z_OK) {
+ rv = NS_ERROR_INVALID_CONTENT_ENCODING;
+ }
+ }
+ if (NS_SUCCEEDED(rv)) {
+ *countRead = count - mZstream.avail_out;
+ }
+ return rv;
+}
+
+nsresult nsCacheEntryDescriptor::nsDecompressInputStreamWrapper::Close() {
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (!mDescriptor) return NS_ERROR_NOT_AVAILABLE;
+
+ EndZstream();
+ if (mReadBuffer) {
+ free(mReadBuffer);
+ mReadBuffer = nullptr;
+ mReadBufferLen = 0;
+ }
+ return nsInputStreamWrapper::Close_Locked();
+}
+
+nsresult nsCacheEntryDescriptor::nsDecompressInputStreamWrapper::InitZstream() {
+ if (!mDescriptor) return NS_ERROR_NOT_AVAILABLE;
+
+ if (mStreamEnded) return NS_ERROR_FAILURE;
+
+ // Initialize zlib inflate stream
+ mZstream.zalloc = Z_NULL;
+ mZstream.zfree = Z_NULL;
+ mZstream.opaque = Z_NULL;
+ mZstream.next_out = Z_NULL;
+ mZstream.avail_out = 0;
+ mZstream.next_in = Z_NULL;
+ mZstream.avail_in = 0;
+ if (inflateInit(&mZstream) != Z_OK) {
+ return NS_ERROR_FAILURE;
+ }
+ mStreamInitialized = true;
+ return NS_OK;
+}
+
+nsresult nsCacheEntryDescriptor::nsDecompressInputStreamWrapper::EndZstream() {
+ if (mStreamInitialized && !mStreamEnded) {
+ inflateEnd(&mZstream);
+ mStreamInitialized = false;
+ mStreamEnded = true;
+ }
+ return NS_OK;
+}
+
+/******************************************************************************
+ * nsOutputStreamWrapper - a wrapper for nsIOutputstream to track the amount of
+ * data written to a cache entry.
+ * - also keeps the cache entry open while referenced.
+ ******************************************************************************/
+
+NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsOutputStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsCacheEntryDescriptor::nsOutputStreamWrapper::Release() {
+ // Holding a reference to descriptor ensures that cache service won't go
+ // away. Do not grab cache service lock if there is no descriptor.
+ RefPtr<nsCacheEntryDescriptor> desc;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ desc = mDescriptor;
+ }
+
+ if (desc) nsCacheService::Lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_RELEASE));
+
+ nsrefcnt count;
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "nsCacheEntryDescriptor::nsOutputStreamWrapper");
+
+ if (0 == count) {
+ // don't use desc here since mDescriptor might be already nulled out
+ if (mDescriptor) mDescriptor->mOutputWrapper = nullptr;
+
+ if (desc) nsCacheService::Unlock();
+
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ if (desc) nsCacheService::Unlock();
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsOutputStreamWrapper)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+nsresult nsCacheEntryDescriptor::nsOutputStreamWrapper::LazyInit() {
+ // Check if we have the descriptor. If not we can't even grab the cache
+ // lock since it is not ensured that the cache service still exists.
+ if (!mDescriptor) return NS_ERROR_NOT_AVAILABLE;
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_LAZYINIT));
+
+ nsCacheAccessMode mode;
+ nsresult rv = mDescriptor->GetAccessGranted(&mode);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ENSURE_TRUE(mode & nsICache::ACCESS_WRITE, NS_ERROR_UNEXPECTED);
+
+ nsCacheEntry* cacheEntry = mDescriptor->CacheEntry();
+ if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ASSERTION(mOutput == nullptr, "mOutput set in LazyInit");
+
+ nsCOMPtr<nsIOutputStream> stream;
+ rv = nsCacheService::OpenOutputStreamForEntry(cacheEntry, mode, mStartOffset,
+ getter_AddRefs(stream));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCacheDevice* device = cacheEntry->CacheDevice();
+ if (device) {
+ // the entry has been truncated to mStartOffset bytes, inform device
+ int32_t size = cacheEntry->DataSize();
+ rv = device->OnDataSizeChange(cacheEntry, mStartOffset - size);
+ if (NS_SUCCEEDED(rv)) cacheEntry->SetDataSize(mStartOffset);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // If anything above failed, clean up internal state and get out of here
+ // (see bug #654926)...
+ if (NS_FAILED(rv)) {
+ nsCacheService::ReleaseObject_Locked(stream.forget().take());
+ mDescriptor->mOutputWrapper = nullptr;
+ nsCacheService::ReleaseObject_Locked(mDescriptor);
+ mDescriptor = nullptr;
+ mInitialized = false;
+ return rv;
+ }
+
+ mOutput = stream;
+ mInitialized = true;
+ return NS_OK;
+}
+
+nsresult nsCacheEntryDescriptor::nsOutputStreamWrapper::EnsureInit() {
+ if (mInitialized) {
+ NS_ASSERTION(mDescriptor, "Bad state");
+ return NS_OK;
+ }
+
+ return LazyInit();
+}
+
+nsresult nsCacheEntryDescriptor::nsOutputStreamWrapper::OnWrite(
+ uint32_t count) {
+ if (count > INT32_MAX) return NS_ERROR_UNEXPECTED;
+ return mDescriptor->RequestDataSizeChange((int32_t)count);
+}
+
+void nsCacheEntryDescriptor::nsOutputStreamWrapper::CloseInternal() {
+ mLock.AssertCurrentThreadOwns();
+ if (!mDescriptor) {
+ NS_ASSERTION(!mInitialized, "Bad state");
+ NS_ASSERTION(!mOutput, "Bad state");
+ return;
+ }
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_CLOSEINTERNAL));
+
+ if (mDescriptor) {
+ mDescriptor->mOutputWrapper = nullptr;
+ nsCacheService::ReleaseObject_Locked(mDescriptor);
+ mDescriptor = nullptr;
+ }
+ mInitialized = false;
+ mOutput = nullptr;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::nsOutputStreamWrapper::Close() {
+ mozilla::MutexAutoLock lock(mLock);
+
+ return Close_Locked();
+}
+
+nsresult nsCacheEntryDescriptor::nsOutputStreamWrapper::Close_Locked() {
+ nsresult rv = EnsureInit();
+ if (NS_SUCCEEDED(rv)) {
+ rv = mOutput->Close();
+ } else {
+ NS_ASSERTION(!mOutput, "Shouldn't have mOutput when EnsureInit() failed");
+ }
+
+ // Call CloseInternal() even when EnsureInit() failed, e.g. in case we are
+ // closing streams with nsCacheService::CloseAllStream()
+ CloseInternal();
+ return rv;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::nsOutputStreamWrapper::Flush() {
+ mozilla::MutexAutoLock lock(mLock);
+
+ nsresult rv = EnsureInit();
+ if (NS_FAILED(rv)) return rv;
+
+ return mOutput->Flush();
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::nsOutputStreamWrapper::Write(
+ const char* buf, uint32_t count, uint32_t* result) {
+ mozilla::MutexAutoLock lock(mLock);
+ return Write_Locked(buf, count, result);
+}
+
+nsresult nsCacheEntryDescriptor::nsOutputStreamWrapper::Write_Locked(
+ const char* buf, uint32_t count, uint32_t* result) {
+ nsresult rv = EnsureInit();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = OnWrite(count);
+ if (NS_FAILED(rv)) return rv;
+
+ return mOutput->Write(buf, count, result);
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::nsOutputStreamWrapper::WriteFrom(
+ nsIInputStream* inStr, uint32_t count, uint32_t* result) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::nsOutputStreamWrapper::WriteSegments(
+ nsReadSegmentFun reader, void* closure, uint32_t count, uint32_t* result) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::nsOutputStreamWrapper::IsNonBlocking(
+ bool* result) {
+ // cache streams will never return NS_BASE_STREAM_WOULD_BLOCK
+ *result = false;
+ return NS_OK;
+}
+
+/******************************************************************************
+ * nsCompressOutputStreamWrapper - an output stream wrapper that compresses
+ * data before it is written
+ ******************************************************************************/
+
+NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsCompressOutputStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsCacheEntryDescriptor::nsCompressOutputStreamWrapper::Release() {
+ // Holding a reference to descriptor ensures that cache service won't go
+ // away. Do not grab cache service lock if there is no descriptor.
+ RefPtr<nsCacheEntryDescriptor> desc;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ desc = mDescriptor;
+ }
+
+ if (desc)
+ nsCacheService::Lock(LOCK_TELEM(NSCOMPRESSOUTPUTSTREAMWRAPPER_RELEASE));
+
+ nsrefcnt count;
+ MOZ_ASSERT(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count,
+ "nsCacheEntryDescriptor::nsCompressOutputStreamWrapper");
+
+ if (0 == count) {
+ // don't use desc here since mDescriptor might be already nulled out
+ if (mDescriptor) mDescriptor->mOutputWrapper = nullptr;
+
+ if (desc) nsCacheService::Unlock();
+
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ if (desc) nsCacheService::Unlock();
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsCompressOutputStreamWrapper)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP nsCacheEntryDescriptor::nsCompressOutputStreamWrapper::Write(
+ const char* buf, uint32_t count, uint32_t* result) {
+ mozilla::MutexAutoLock lock(mLock);
+
+ int zerr = Z_OK;
+ nsresult rv = NS_OK;
+
+ if (!mStreamInitialized) {
+ rv = InitZstream();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (!mWriteBuffer) {
+ // Once allocated, this buffer is referenced by the zlib stream and
+ // cannot be grown. We use 2x(initial write request) to approximate
+ // a stream buffer size proportional to request buffers.
+ mWriteBufferLen = std::max(count * 2, (uint32_t)kMinCompressWriteBufLen);
+ mWriteBuffer = (unsigned char*)moz_xmalloc(mWriteBufferLen);
+ mZstream.next_out = mWriteBuffer;
+ mZstream.avail_out = mWriteBufferLen;
+ }
+
+ // Compress (deflate) the requested buffer. Keep going
+ // until the entire buffer has been deflated.
+ mZstream.avail_in = count;
+ mZstream.next_in = (Bytef*)buf;
+ while (mZstream.avail_in > 0) {
+ zerr = deflate(&mZstream, Z_NO_FLUSH);
+ if (zerr == Z_STREAM_ERROR) {
+ deflateEnd(&mZstream);
+ mStreamEnded = true;
+ mStreamInitialized = false;
+ return NS_ERROR_FAILURE;
+ }
+ // Note: Z_BUF_ERROR is non-fatal and sometimes expected here.
+
+ // If the compression stream output buffer is filled, write
+ // it out to the underlying stream wrapper.
+ if (mZstream.avail_out == 0) {
+ rv = WriteBuffer();
+ if (NS_FAILED(rv)) {
+ deflateEnd(&mZstream);
+ mStreamEnded = true;
+ mStreamInitialized = false;
+ return rv;
+ }
+ }
+ }
+ *result = count;
+ mUncompressedCount += *result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::nsCompressOutputStreamWrapper::Close() {
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (!mDescriptor) return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult retval = NS_OK;
+ nsresult rv;
+ int zerr = 0;
+
+ if (mStreamInitialized) {
+ // complete compression of any data remaining in the zlib stream
+ do {
+ zerr = deflate(&mZstream, Z_FINISH);
+ rv = WriteBuffer();
+ if (NS_FAILED(rv)) retval = rv;
+ } while (zerr == Z_OK && rv == NS_OK);
+ deflateEnd(&mZstream);
+ mStreamInitialized = false;
+ }
+ // Do not allow to initialize stream after calling Close().
+ mStreamEnded = true;
+
+ if (mDescriptor->CacheEntry()) {
+ nsAutoCString uncompressedLenStr;
+ rv = mDescriptor->GetMetaDataElement("uncompressed-len",
+ getter_Copies(uncompressedLenStr));
+ if (NS_SUCCEEDED(rv)) {
+ int32_t oldCount = uncompressedLenStr.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ mUncompressedCount += oldCount;
+ }
+ }
+ uncompressedLenStr.Adopt(nullptr);
+ uncompressedLenStr.AppendInt(mUncompressedCount);
+ rv = mDescriptor->SetMetaDataElement("uncompressed-len",
+ uncompressedLenStr.get());
+ if (NS_FAILED(rv)) retval = rv;
+ }
+
+ if (mWriteBuffer) {
+ free(mWriteBuffer);
+ mWriteBuffer = nullptr;
+ mWriteBufferLen = 0;
+ }
+
+ rv = nsOutputStreamWrapper::Close_Locked();
+ if (NS_FAILED(rv)) retval = rv;
+
+ return retval;
+}
+
+nsresult nsCacheEntryDescriptor::nsCompressOutputStreamWrapper::InitZstream() {
+ if (!mDescriptor) return NS_ERROR_NOT_AVAILABLE;
+
+ if (mStreamEnded) return NS_ERROR_FAILURE;
+
+ // Determine compression level: Aggressive compression
+ // may impact performance on mobile devices, while a
+ // lower compression level still provides substantial
+ // space savings for many text streams.
+ int32_t compressionLevel = nsCacheService::CacheCompressionLevel();
+
+ // Initialize zlib deflate stream
+ mZstream.zalloc = Z_NULL;
+ mZstream.zfree = Z_NULL;
+ mZstream.opaque = Z_NULL;
+ if (deflateInit2(&mZstream, compressionLevel, Z_DEFLATED, MAX_WBITS, 8,
+ Z_DEFAULT_STRATEGY) != Z_OK) {
+ return NS_ERROR_FAILURE;
+ }
+ mZstream.next_in = Z_NULL;
+ mZstream.avail_in = 0;
+
+ mStreamInitialized = true;
+
+ return NS_OK;
+}
+
+nsresult nsCacheEntryDescriptor::nsCompressOutputStreamWrapper::WriteBuffer() {
+ uint32_t bytesToWrite = mWriteBufferLen - mZstream.avail_out;
+ uint32_t result = 0;
+ nsresult rv = nsCacheEntryDescriptor::nsOutputStreamWrapper::Write_Locked(
+ (const char*)mWriteBuffer, bytesToWrite, &result);
+ mZstream.next_out = mWriteBuffer;
+ mZstream.avail_out = mWriteBufferLen;
+ return rv;
+}
diff --git a/netwerk/cache/nsCacheEntryDescriptor.h b/netwerk/cache/nsCacheEntryDescriptor.h
new file mode 100644
index 0000000000..2274150cff
--- /dev/null
+++ b/netwerk/cache/nsCacheEntryDescriptor.h
@@ -0,0 +1,220 @@
+/* -*- 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 _nsCacheEntryDescriptor_h_
+#define _nsCacheEntryDescriptor_h_
+
+#include "nsICacheEntryDescriptor.h"
+#include "nsCacheEntry.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsCacheService.h"
+#include "zlib.h"
+#include "mozilla/Mutex.h"
+
+/******************************************************************************
+ * nsCacheEntryDescriptor
+ *******************************************************************************/
+class nsCacheEntryDescriptor final : public PRCList,
+ public nsICacheEntryDescriptor {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEENTRYDESCRIPTOR
+ NS_DECL_NSICACHEENTRYINFO
+
+ friend class nsAsyncDoomEvent;
+ friend class nsCacheService;
+
+ nsCacheEntryDescriptor(nsCacheEntry* entry, nsCacheAccessMode mode);
+
+ /**
+ * utility method to attempt changing data size of associated entry
+ */
+ nsresult RequestDataSizeChange(int32_t deltaSize);
+
+ /**
+ * methods callbacks for nsCacheService
+ */
+ nsCacheEntry* CacheEntry(void) { return mCacheEntry; }
+ bool ClearCacheEntry(void) {
+ NS_ASSERTION(mInputWrappers.IsEmpty(), "Bad state");
+ NS_ASSERTION(!mOutputWrapper, "Bad state");
+
+ bool doomEntry = false;
+ bool asyncDoomPending;
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ asyncDoomPending = mAsyncDoomPending;
+ }
+
+ if (asyncDoomPending && mCacheEntry) {
+ doomEntry = true;
+ mDoomedOnClose = true;
+ }
+ mCacheEntry = nullptr;
+
+ return doomEntry;
+ }
+
+ private:
+ virtual ~nsCacheEntryDescriptor();
+
+ /*************************************************************************
+ * input stream wrapper class -
+ *
+ * The input stream wrapper references the descriptor, but the descriptor
+ * doesn't need any references to the stream wrapper.
+ *************************************************************************/
+ class nsInputStreamWrapper : public nsIInputStream {
+ friend class nsCacheEntryDescriptor;
+
+ private:
+ nsCacheEntryDescriptor* mDescriptor;
+ nsCOMPtr<nsIInputStream> mInput;
+ uint32_t mStartOffset;
+ bool mInitialized;
+ mozilla::Mutex mLock;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+ nsInputStreamWrapper(nsCacheEntryDescriptor* desc, uint32_t off)
+ : mDescriptor(desc),
+ mStartOffset(off),
+ mInitialized(false),
+ mLock("nsInputStreamWrapper.mLock") {
+ NS_ADDREF(mDescriptor);
+ }
+
+ private:
+ virtual ~nsInputStreamWrapper() { NS_IF_RELEASE(mDescriptor); }
+
+ nsresult LazyInit();
+ nsresult EnsureInit();
+ nsresult Read_Locked(char* buf, uint32_t count, uint32_t* countRead);
+ nsresult Close_Locked();
+ void CloseInternal();
+ };
+
+ class nsDecompressInputStreamWrapper : public nsInputStreamWrapper {
+ private:
+ unsigned char* mReadBuffer;
+ uint32_t mReadBufferLen;
+ z_stream mZstream;
+ bool mStreamInitialized;
+ bool mStreamEnded;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsDecompressInputStreamWrapper(nsCacheEntryDescriptor* desc, uint32_t off)
+ : nsInputStreamWrapper(desc, off),
+ mReadBuffer(nullptr),
+ mReadBufferLen(0),
+ mZstream{},
+ mStreamInitialized(false),
+ mStreamEnded(false) {}
+ NS_IMETHOD Read(char* buf, uint32_t count, uint32_t* result) override;
+ NS_IMETHOD Close() override;
+
+ private:
+ virtual ~nsDecompressInputStreamWrapper() { Close(); }
+ nsresult InitZstream();
+ nsresult EndZstream();
+ };
+
+ /*************************************************************************
+ * output stream wrapper class -
+ *
+ * The output stream wrapper references the descriptor, but the descriptor
+ * doesn't need any references to the stream wrapper.
+ *************************************************************************/
+ class nsOutputStreamWrapper : public nsIOutputStream {
+ friend class nsCacheEntryDescriptor;
+
+ protected:
+ nsCacheEntryDescriptor* mDescriptor;
+ nsCOMPtr<nsIOutputStream> mOutput;
+ uint32_t mStartOffset;
+ bool mInitialized;
+ mozilla::Mutex mLock;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsOutputStreamWrapper(nsCacheEntryDescriptor* desc, uint32_t off)
+ : mDescriptor(desc),
+ mStartOffset(off),
+ mInitialized(false),
+ mLock("nsOutputStreamWrapper.mLock") {
+ NS_ADDREF(mDescriptor); // owning ref
+ }
+
+ private:
+ virtual ~nsOutputStreamWrapper() {
+ Close();
+
+ NS_ASSERTION(!mOutput, "Bad state");
+ NS_ASSERTION(!mDescriptor, "Bad state");
+ }
+
+ nsresult LazyInit();
+ nsresult EnsureInit();
+ nsresult OnWrite(uint32_t count);
+ nsresult Write_Locked(const char* buf, uint32_t count, uint32_t* result);
+ nsresult Close_Locked();
+ void CloseInternal();
+ };
+
+ class nsCompressOutputStreamWrapper : public nsOutputStreamWrapper {
+ private:
+ unsigned char* mWriteBuffer;
+ uint32_t mWriteBufferLen;
+ z_stream mZstream;
+ bool mStreamInitialized;
+ bool mStreamEnded;
+ uint32_t mUncompressedCount;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsCompressOutputStreamWrapper(nsCacheEntryDescriptor* desc, uint32_t off)
+ : nsOutputStreamWrapper(desc, off),
+ mWriteBuffer(nullptr),
+ mWriteBufferLen(0),
+ mZstream{},
+ mStreamInitialized(false),
+ mStreamEnded(false),
+ mUncompressedCount(0) {}
+ NS_IMETHOD Write(const char* buf, uint32_t count,
+ uint32_t* result) override;
+ NS_IMETHOD Close() override;
+
+ private:
+ virtual ~nsCompressOutputStreamWrapper() { Close(); }
+ nsresult InitZstream();
+ nsresult WriteBuffer();
+ };
+
+ private:
+ /**
+ * nsCacheEntryDescriptor data members
+ */
+
+ nsCOMPtr<nsICacheServiceInternal> mCacheService;
+ nsCacheEntry* mCacheEntry; // we are a child of the entry
+ nsCacheAccessMode mAccessGranted;
+ nsTArray<nsInputStreamWrapper*> mInputWrappers;
+ nsOutputStreamWrapper* mOutputWrapper;
+ mozilla::Mutex mLock;
+ bool mAsyncDoomPending;
+ bool mDoomedOnClose;
+ bool mClosingDescriptor;
+};
+
+#endif // _nsCacheEntryDescriptor_h_
diff --git a/netwerk/cache/nsCacheMetaData.cpp b/netwerk/cache/nsCacheMetaData.cpp
new file mode 100644
index 0000000000..386b569ea0
--- /dev/null
+++ b/netwerk/cache/nsCacheMetaData.cpp
@@ -0,0 +1,151 @@
+/* -*- 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 "nsCacheMetaData.h"
+#include "nsICacheEntryDescriptor.h"
+
+const char* nsCacheMetaData::GetElement(const char* key) {
+ const char* data = mBuffer;
+ const char* limit = mBuffer + mMetaSize;
+
+ while (data < limit) {
+ // Point to the value part
+ const char* value = data + strlen(data) + 1;
+ MOZ_ASSERT(value < limit, "Cache Metadata corrupted");
+ if (strcmp(data, key) == 0) return value;
+
+ // Skip value part
+ data = value + strlen(value) + 1;
+ }
+ MOZ_ASSERT(data == limit, "Metadata corrupted");
+ return nullptr;
+}
+
+nsresult nsCacheMetaData::SetElement(const char* key, const char* value) {
+ const uint32_t keySize = strlen(key) + 1;
+ char* pos = (char*)GetElement(key);
+
+ if (!value) {
+ // No value means remove the key/value pair completely, if existing
+ if (pos) {
+ uint32_t oldValueSize = strlen(pos) + 1;
+ uint32_t offset = pos - mBuffer;
+ uint32_t remainder = mMetaSize - (offset + oldValueSize);
+
+ memmove(pos - keySize, pos + oldValueSize, remainder);
+ mMetaSize -= keySize + oldValueSize;
+ }
+ return NS_OK;
+ }
+
+ const uint32_t valueSize = strlen(value) + 1;
+ uint32_t newSize = mMetaSize + valueSize;
+ if (pos) {
+ const uint32_t oldValueSize = strlen(pos) + 1;
+ const uint32_t offset = pos - mBuffer;
+ const uint32_t remainder = mMetaSize - (offset + oldValueSize);
+
+ // Update the value in place
+ newSize -= oldValueSize;
+ nsresult rv = EnsureBuffer(newSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Move the remainder to the right place
+ pos = mBuffer + offset;
+ memmove(pos + valueSize, pos + oldValueSize, remainder);
+ } else {
+ // allocate new meta data element
+ newSize += keySize;
+ nsresult rv = EnsureBuffer(newSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add after last element
+ pos = mBuffer + mMetaSize;
+ memcpy(pos, key, keySize);
+ pos += keySize;
+ }
+
+ // Update value
+ memcpy(pos, value, valueSize);
+ mMetaSize = newSize;
+
+ return NS_OK;
+}
+
+nsresult nsCacheMetaData::FlattenMetaData(char* buffer, uint32_t bufSize) {
+ if (mMetaSize > bufSize) {
+ NS_ERROR("buffer size too small for meta data.");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!mBuffer) {
+ MOZ_ASSERT(mMetaSize == 0);
+ MOZ_ASSERT(bufSize == 0);
+ return NS_OK;
+ }
+
+ memcpy(buffer, mBuffer, mMetaSize);
+ return NS_OK;
+}
+
+nsresult nsCacheMetaData::UnflattenMetaData(const char* data, uint32_t size) {
+ if (data && size) {
+ // Check if the metadata ends with a zero byte.
+ if (data[size - 1] != '\0') {
+ NS_ERROR("Cache MetaData is not null terminated");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ // 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 < size; i++) {
+ if (data[i] == '\0') odd = !odd;
+ }
+ if (odd) {
+ NS_ERROR("Cache MetaData is malformed");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ nsresult rv = EnsureBuffer(size);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ memcpy(mBuffer, data, size);
+ mMetaSize = size;
+ }
+ return NS_OK;
+}
+
+nsresult nsCacheMetaData::VisitElements(nsICacheMetaDataVisitor* visitor) {
+ const char* data = mBuffer;
+ const char* limit = mBuffer + mMetaSize;
+
+ while (data < limit) {
+ const char* key = data;
+ // Skip key part
+ data += strlen(data) + 1;
+ MOZ_ASSERT(data < limit, "Metadata corrupted");
+ bool keepGoing;
+ nsresult rv = visitor->VisitMetaDataElement(key, data, &keepGoing);
+ if (NS_FAILED(rv) || !keepGoing) return NS_OK;
+
+ // Skip value part
+ data += strlen(data) + 1;
+ }
+ MOZ_ASSERT(data == limit, "Metadata corrupted");
+ return NS_OK;
+}
+
+nsresult nsCacheMetaData::EnsureBuffer(uint32_t bufSize) {
+ if (mBufferSize < bufSize) {
+ char* buf = (char*)realloc(mBuffer, bufSize);
+ if (!buf) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mBuffer = buf;
+ mBufferSize = bufSize;
+ }
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsCacheMetaData.h b/netwerk/cache/nsCacheMetaData.h
new file mode 100644
index 0000000000..4cf84ad5fa
--- /dev/null
+++ b/netwerk/cache/nsCacheMetaData.h
@@ -0,0 +1,45 @@
+/* -*- 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 _nsCacheMetaData_h_
+#define _nsCacheMetaData_h_
+
+#include "nspr.h"
+#include "nscore.h"
+
+class nsICacheMetaDataVisitor;
+
+class nsCacheMetaData {
+ public:
+ nsCacheMetaData() : mBuffer(nullptr), mBufferSize(0), mMetaSize(0) {}
+
+ ~nsCacheMetaData() {
+ mBufferSize = mMetaSize = 0;
+ free(mBuffer);
+ mBuffer = nullptr;
+ }
+
+ const char* GetElement(const char* key);
+
+ nsresult SetElement(const char* key, const char* value);
+
+ uint32_t Size(void) { return mMetaSize; }
+
+ nsresult FlattenMetaData(char* buffer, uint32_t bufSize);
+
+ nsresult UnflattenMetaData(const char* buffer, uint32_t bufSize);
+
+ nsresult VisitElements(nsICacheMetaDataVisitor* visitor);
+
+ private:
+ nsresult EnsureBuffer(uint32_t size);
+
+ char* mBuffer;
+ uint32_t mBufferSize;
+ uint32_t mMetaSize;
+};
+
+#endif // _nsCacheMetaData_h
diff --git a/netwerk/cache/nsCacheRequest.h b/netwerk/cache/nsCacheRequest.h
new file mode 100644
index 0000000000..1d5dcd3106
--- /dev/null
+++ b/netwerk/cache/nsCacheRequest.h
@@ -0,0 +1,146 @@
+/* -*- 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 _nsCacheRequest_h_
+#define _nsCacheRequest_h_
+
+#include "nspr.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/Mutex.h"
+#include "nsCOMPtr.h"
+#include "nsICache.h"
+#include "nsICacheListener.h"
+#include "nsCacheSession.h"
+#include "nsCacheService.h"
+
+class nsCacheRequest : public PRCList {
+ typedef mozilla::CondVar CondVar;
+ typedef mozilla::MutexAutoLock MutexAutoLock;
+ typedef mozilla::Mutex Mutex;
+
+ private:
+ friend class nsCacheService;
+ friend class nsCacheEntry;
+ friend class nsProcessRequestEvent;
+
+ nsCacheRequest(const nsACString& key, nsICacheListener* listener,
+ nsCacheAccessMode accessRequested, bool blockingMode,
+ nsCacheSession* session)
+ : mKey(key),
+ mInfo(0),
+ mListener(listener),
+ mLock("nsCacheRequest.mLock"),
+ mCondVar(mLock, "nsCacheRequest.mCondVar"),
+ mProfileDir(session->ProfileDir()) {
+ MOZ_COUNT_CTOR(nsCacheRequest);
+ PR_INIT_CLIST(this);
+ SetAccessRequested(accessRequested);
+ SetStoragePolicy(session->StoragePolicy());
+ if (session->IsStreamBased()) MarkStreamBased();
+ if (session->WillDoomEntriesIfExpired()) MarkDoomEntriesIfExpired();
+ if (session->IsPrivate()) MarkPrivate();
+ if (blockingMode == nsICache::BLOCKING) MarkBlockingMode();
+ MarkWaitingForValidation();
+ NS_IF_ADDREF(mListener);
+ }
+
+ ~nsCacheRequest() {
+ MOZ_COUNT_DTOR(nsCacheRequest);
+ NS_ASSERTION(PR_CLIST_IS_EMPTY(this), "request still on a list");
+
+ if (mListener)
+ nsCacheService::ReleaseObject_Locked(mListener, mEventTarget);
+ }
+
+ /**
+ * Simple Accessors
+ */
+ enum CacheRequestInfo {
+ eStoragePolicyMask = 0x000000FF,
+ eStreamBasedMask = 0x00000100,
+ ePrivateMask = 0x00000200,
+ eDoomEntriesIfExpiredMask = 0x00001000,
+ eBlockingModeMask = 0x00010000,
+ eWaitingForValidationMask = 0x00100000,
+ eAccessRequestedMask = 0xFF000000
+ };
+
+ void SetAccessRequested(nsCacheAccessMode mode) {
+ NS_ASSERTION(mode <= 0xFF, "too many bits in nsCacheAccessMode");
+ mInfo &= ~eAccessRequestedMask;
+ mInfo |= mode << 24;
+ }
+
+ nsCacheAccessMode AccessRequested() {
+ return (nsCacheAccessMode)((mInfo >> 24) & 0xFF);
+ }
+
+ void MarkStreamBased() { mInfo |= eStreamBasedMask; }
+ bool IsStreamBased() { return (mInfo & eStreamBasedMask) != 0; }
+
+ void MarkDoomEntriesIfExpired() { mInfo |= eDoomEntriesIfExpiredMask; }
+ bool WillDoomEntriesIfExpired() {
+ return (0 != (mInfo & eDoomEntriesIfExpiredMask));
+ }
+
+ void MarkBlockingMode() { mInfo |= eBlockingModeMask; }
+ bool IsBlocking() { return (0 != (mInfo & eBlockingModeMask)); }
+ bool IsNonBlocking() { return !(mInfo & eBlockingModeMask); }
+
+ void SetStoragePolicy(nsCacheStoragePolicy policy) {
+ NS_ASSERTION(policy <= 0xFF, "too many bits in nsCacheStoragePolicy");
+ mInfo &= ~eStoragePolicyMask; // clear storage policy bits
+ mInfo |= policy; // or in new bits
+ }
+
+ nsCacheStoragePolicy StoragePolicy() {
+ return (nsCacheStoragePolicy)(mInfo & eStoragePolicyMask);
+ }
+
+ void MarkPrivate() { mInfo |= ePrivateMask; }
+ void MarkPublic() { mInfo &= ~ePrivateMask; }
+ bool IsPrivate() { return (mInfo & ePrivateMask) != 0; }
+
+ void MarkWaitingForValidation() { mInfo |= eWaitingForValidationMask; }
+ void DoneWaitingForValidation() { mInfo &= ~eWaitingForValidationMask; }
+ bool WaitingForValidation() {
+ return (mInfo & eWaitingForValidationMask) != 0;
+ }
+
+ nsresult WaitForValidation(void) {
+ if (!WaitingForValidation()) { // flag already cleared
+ MarkWaitingForValidation(); // set up for next time
+ return NS_OK; // early exit;
+ }
+ {
+ MutexAutoLock lock(mLock);
+ while (WaitingForValidation()) {
+ mCondVar.Wait();
+ }
+ MarkWaitingForValidation(); // set up for next time
+ }
+ return NS_OK;
+ }
+
+ void WakeUp(void) {
+ DoneWaitingForValidation();
+ MutexAutoLock lock(mLock);
+ mCondVar.Notify();
+ }
+
+ /**
+ * Data members
+ */
+ nsCString mKey;
+ uint32_t mInfo;
+ nsICacheListener* mListener; // strong ref
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+ Mutex mLock;
+ CondVar mCondVar;
+ nsCOMPtr<nsIFile> mProfileDir;
+};
+
+#endif // _nsCacheRequest_h_
diff --git a/netwerk/cache/nsCacheService.cpp b/netwerk/cache/nsCacheService.cpp
new file mode 100644
index 0000000000..f7cb31d010
--- /dev/null
+++ b/netwerk/cache/nsCacheService.cpp
@@ -0,0 +1,1910 @@
+/* -*- 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 "nsCacheService.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/FileUtils.h"
+
+#include "nsCache.h"
+#include "nsCacheRequest.h"
+#include "nsCacheEntry.h"
+#include "nsCacheEntryDescriptor.h"
+#include "nsCacheDevice.h"
+#include "nsICacheVisitor.h"
+#include "nsDiskCacheDeviceSQL.h"
+#include "nsCacheUtils.h"
+#include "../cache2/CacheObserver.h"
+#include "nsIObserverService.h"
+#include "nsIPrefBranch.h"
+#include "nsIFile.h"
+#include "nsIOService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "nsDeleteDir.h"
+#include "nsNetCID.h"
+#include <math.h> // for log()
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_browser.h"
+
+#include "mozilla/net/NeckoCommon.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+/******************************************************************************
+ * nsCacheProfilePrefObserver
+ *****************************************************************************/
+#define OFFLINE_CACHE_DIR_PREF "browser.cache.offline.parent_directory"
+#define OFFLINE_CACHE_CAPACITY_PREF "browser.cache.offline.capacity"
+#define OFFLINE_CACHE_CAPACITY 512000
+
+#define CACHE_COMPRESSION_LEVEL 1
+
+static const char* observerList[] = {
+ "profile-before-change", "profile-do-change",
+ NS_XPCOM_SHUTDOWN_OBSERVER_ID, "last-pb-context-exited",
+ "suspend_process_notification", "resume_process_notification"};
+
+class nsCacheProfilePrefObserver : public nsIObserver {
+ virtual ~nsCacheProfilePrefObserver() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsCacheProfilePrefObserver()
+ : mHaveProfile(false),
+ mOfflineCacheEnabled(false),
+ mOfflineStorageCacheEnabled(false),
+ mOfflineCacheCapacity(0),
+ mCacheCompressionLevel(CACHE_COMPRESSION_LEVEL),
+ mSanitizeOnShutdown(false),
+ mClearCacheOnShutdown(false) {}
+
+ nsresult Install();
+ void Remove();
+ nsresult ReadPrefs(nsIPrefBranch* branch);
+
+ nsIFile* DiskCacheParentDirectory() { return mDiskCacheParentDirectory; }
+
+ bool OfflineCacheEnabled();
+ int32_t OfflineCacheCapacity() { return mOfflineCacheCapacity; }
+ nsIFile* OfflineCacheParentDirectory() {
+ return mOfflineCacheParentDirectory;
+ }
+
+ int32_t CacheCompressionLevel();
+
+ bool SanitizeAtShutdown() {
+ return mSanitizeOnShutdown && mClearCacheOnShutdown;
+ }
+
+ private:
+ bool mHaveProfile;
+
+ nsCOMPtr<nsIFile> mDiskCacheParentDirectory;
+
+ bool mOfflineCacheEnabled;
+ bool mOfflineStorageCacheEnabled;
+ int32_t mOfflineCacheCapacity; // in kilobytes
+ nsCOMPtr<nsIFile> mOfflineCacheParentDirectory;
+
+ int32_t mCacheCompressionLevel;
+
+ bool mSanitizeOnShutdown;
+ bool mClearCacheOnShutdown;
+};
+
+NS_IMPL_ISUPPORTS(nsCacheProfilePrefObserver, nsIObserver)
+
+class nsBlockOnCacheThreadEvent : public Runnable {
+ public:
+ nsBlockOnCacheThreadEvent()
+ : mozilla::Runnable("nsBlockOnCacheThreadEvent") {}
+ NS_IMETHOD Run() override {
+ nsCacheServiceAutoLock autoLock(LOCK_TELEM(NSBLOCKONCACHETHREADEVENT_RUN));
+ CACHE_LOG_DEBUG(("nsBlockOnCacheThreadEvent [%p]\n", this));
+ nsCacheService::gService->mNotified = true;
+ nsCacheService::gService->mCondVar.Notify();
+ return NS_OK;
+ }
+};
+
+nsresult nsCacheProfilePrefObserver::Install() {
+ // install profile-change observer
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) return NS_ERROR_FAILURE;
+
+ nsresult rv, rv2 = NS_OK;
+ for (auto& observer : observerList) {
+ rv = observerService->AddObserver(this, observer, false);
+ if (NS_FAILED(rv)) rv2 = rv;
+ }
+
+ // install preferences observer
+ nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!branch) return NS_ERROR_FAILURE;
+
+ // Determine if we have a profile already
+ // Install() is called *after* the profile-after-change notification
+ // when there is only a single profile, or it is specified on the
+ // commandline at startup.
+ // In that case, we detect the presence of a profile by the existence
+ // of the NS_APP_USER_PROFILE_50_DIR directory.
+
+ nsCOMPtr<nsIFile> directory;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(directory));
+ if (NS_SUCCEEDED(rv)) mHaveProfile = true;
+
+ rv = ReadPrefs(branch);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv2;
+}
+
+void nsCacheProfilePrefObserver::Remove() {
+ // remove Observer Service observers
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ for (auto& observer : observerList) {
+ obs->RemoveObserver(this, observer);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsCacheProfilePrefObserver::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data_unicode) {
+ NS_ConvertUTF16toUTF8 data(data_unicode);
+ CACHE_LOG_INFO(("Observe [topic=%s data=%s]\n", topic, data.get()));
+
+ if (!nsCacheService::IsInitialized()) {
+ if (!strcmp("resume_process_notification", topic)) {
+ // A suspended process has a closed cache, so re-open it here.
+ nsCacheService::GlobalInstance()->Init();
+ }
+ return NS_OK;
+ }
+
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
+ // xpcom going away, shutdown cache service
+ nsCacheService::GlobalInstance()->Shutdown();
+ } else if (!strcmp("profile-before-change", topic)) {
+ // profile before change
+ mHaveProfile = false;
+
+ // XXX shutdown devices
+ nsCacheService::OnProfileShutdown();
+ } else if (!strcmp("suspend_process_notification", topic)) {
+ // A suspended process may never return, so shutdown the cache to reduce
+ // cache corruption.
+ nsCacheService::GlobalInstance()->Shutdown();
+ } else if (!strcmp("profile-do-change", topic)) {
+ // profile after change
+ mHaveProfile = true;
+ nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!branch) {
+ return NS_ERROR_FAILURE;
+ }
+ (void)ReadPrefs(branch);
+ nsCacheService::OnProfileChanged();
+
+ } else if (!strcmp("last-pb-context-exited", topic)) {
+ nsCacheService::LeavePrivateBrowsing();
+ }
+
+ return NS_OK;
+}
+
+static already_AddRefed<nsIFile> GetCacheDirectory(const char* aSubdir,
+ bool aAllowProcDirCache) {
+ nsCOMPtr<nsIFile> directory;
+
+ // try to get the disk cache parent directory
+ Unused << NS_GetSpecialDirectory(NS_APP_CACHE_PARENT_DIR,
+ getter_AddRefs(directory));
+ if (!directory) {
+ // try to get the profile directory (there may not be a profile yet)
+ nsCOMPtr<nsIFile> profDir;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profDir));
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
+ getter_AddRefs(directory));
+ if (!directory)
+ directory = profDir;
+ else if (profDir) {
+ nsCacheService::MoveOrRemoveDiskCache(profDir, directory, aSubdir);
+ }
+ }
+ if (!directory && aAllowProcDirCache) {
+ Unused << NS_GetSpecialDirectory(NS_XPCOM_CURRENT_PROCESS_DIR,
+ getter_AddRefs(directory));
+ }
+ return directory.forget();
+}
+
+nsresult nsCacheProfilePrefObserver::ReadPrefs(nsIPrefBranch* branch) {
+ if (!mDiskCacheParentDirectory) {
+ // use file cache in build tree only if asked, to avoid cache dir litter
+ bool allowProcDirCache = PR_GetEnv("NECKO_DEV_ENABLE_DISK_CACHE");
+ mDiskCacheParentDirectory = GetCacheDirectory("Cache", allowProcDirCache);
+ }
+
+ // read offline cache device prefs
+ mOfflineCacheEnabled = StaticPrefs::browser_cache_offline_enable();
+ mOfflineStorageCacheEnabled =
+ StaticPrefs::browser_cache_offline_storage_enable();
+
+ mOfflineCacheCapacity = OFFLINE_CACHE_CAPACITY;
+ (void)branch->GetIntPref(OFFLINE_CACHE_CAPACITY_PREF, &mOfflineCacheCapacity);
+ mOfflineCacheCapacity = std::max(0, mOfflineCacheCapacity);
+
+ (void)branch->GetComplexValue(OFFLINE_CACHE_DIR_PREF, // ignore error
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(mOfflineCacheParentDirectory));
+
+ if (!mOfflineCacheParentDirectory) {
+#ifdef DEBUG
+ bool allowProcDirCache = true;
+#else
+ bool allowProcDirCache = false;
+#endif
+ mOfflineCacheParentDirectory =
+ GetCacheDirectory("OfflineCache", allowProcDirCache);
+ }
+
+ if (!mDiskCacheParentDirectory || !mOfflineCacheParentDirectory) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mOfflineStorageCacheEnabled) {
+ // Dispatch cleanup task
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction("Delete OfflineCache", []() {
+ nsCOMPtr<nsIFile> dir;
+ nsCacheService::GetAppCacheDirectory(getter_AddRefs(dir));
+ bool exists = false;
+ if (dir && NS_SUCCEEDED(dir->Exists(&exists)) && exists) {
+ // Delay delete by 1 minute to avoid IO thrash on startup.
+ CACHE_LOG_INFO(
+ ("Queuing Delete of AppCacheDirectory in 60 seconds"));
+ nsDeleteDir::DeleteDir(dir, false, 60000);
+ }
+ });
+ Unused << nsCacheService::DispatchToCacheIOThread(runnable);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsCacheService::DispatchToCacheIOThread(nsIRunnable* event) {
+ if (!gService || !gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
+ return gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+nsresult nsCacheService::SyncWithCacheIOThread() {
+ if (!gService || !gService->mCacheIOThread) return NS_ERROR_NOT_AVAILABLE;
+ gService->mLock.AssertCurrentThreadOwns();
+
+ nsCOMPtr<nsIRunnable> event = new nsBlockOnCacheThreadEvent();
+
+ // dispatch event - it will notify the monitor when it's done
+ nsresult rv = gService->mCacheIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed dispatching block-event");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // wait until notified, then return
+ gService->mNotified = false;
+ while (!gService->mNotified) {
+ gService->mCondVar.Wait();
+ }
+
+ return NS_OK;
+}
+
+bool nsCacheProfilePrefObserver::OfflineCacheEnabled() {
+ if ((mOfflineCacheCapacity == 0) || (!mOfflineCacheParentDirectory))
+ return false;
+
+ return mOfflineCacheEnabled && mOfflineStorageCacheEnabled;
+}
+
+int32_t nsCacheProfilePrefObserver::CacheCompressionLevel() {
+ return mCacheCompressionLevel;
+}
+
+/******************************************************************************
+ * nsProcessRequestEvent
+ *****************************************************************************/
+
+class nsProcessRequestEvent : public Runnable {
+ public:
+ explicit nsProcessRequestEvent(nsCacheRequest* aRequest)
+ : mozilla::Runnable("nsProcessRequestEvent") {
+ mRequest = aRequest;
+ }
+
+ NS_IMETHOD Run() override {
+ nsresult rv;
+
+ NS_ASSERTION(mRequest->mListener,
+ "Sync OpenCacheEntry() posted to background thread!");
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSPROCESSREQUESTEVENT_RUN));
+ rv = nsCacheService::gService->ProcessRequest(mRequest, false, nullptr);
+
+ // Don't delete the request if it was queued
+ if (!(mRequest->IsBlocking() && rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION))
+ delete mRequest;
+
+ return NS_OK;
+ }
+
+ protected:
+ virtual ~nsProcessRequestEvent() = default;
+
+ private:
+ nsCacheRequest* mRequest;
+};
+
+/******************************************************************************
+ * nsDoomEvent
+ *****************************************************************************/
+
+class nsDoomEvent : public Runnable {
+ public:
+ nsDoomEvent(nsCacheSession* session, const nsACString& key,
+ nsICacheListener* listener)
+ : mozilla::Runnable("nsDoomEvent") {
+ mKey = *session->ClientID();
+ mKey.Append(':');
+ mKey.Append(key);
+ mStoragePolicy = session->StoragePolicy();
+ mListener = listener;
+ mEventTarget = GetCurrentEventTarget();
+ // We addref the listener here and release it in nsNotifyDoomListener
+ // on the callers thread. If posting of nsNotifyDoomListener event fails
+ // we leak the listener which is better than releasing it on a wrong
+ // thread.
+ NS_IF_ADDREF(mListener);
+ }
+
+ NS_IMETHOD Run() override {
+ nsCacheServiceAutoLock lock;
+
+ bool foundActive = true;
+ nsresult status = NS_ERROR_NOT_AVAILABLE;
+ nsCacheEntry* entry;
+ entry = nsCacheService::gService->mActiveEntries.GetEntry(&mKey);
+ if (!entry) {
+ bool collision = false;
+ foundActive = false;
+ entry = nsCacheService::gService->SearchCacheDevices(
+ &mKey, mStoragePolicy, &collision);
+ }
+
+ if (entry) {
+ status = NS_OK;
+ nsCacheService::gService->DoomEntry_Internal(entry, foundActive);
+ }
+
+ if (mListener) {
+ mEventTarget->Dispatch(new nsNotifyDoomListener(mListener, status),
+ NS_DISPATCH_NORMAL);
+ // posted event will release the reference on the correct thread
+ mListener = nullptr;
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ nsCString mKey;
+ nsCacheStoragePolicy mStoragePolicy;
+ nsICacheListener* mListener;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+/******************************************************************************
+ * nsCacheService
+ *****************************************************************************/
+nsCacheService* nsCacheService::gService = nullptr;
+
+NS_IMPL_ISUPPORTS(nsCacheService, nsICacheService, nsICacheServiceInternal)
+
+nsCacheService::nsCacheService()
+ : mObserver(nullptr),
+ mLock("nsCacheService.mLock"),
+ mCondVar(mLock, "nsCacheService.mCondVar"),
+ mNotified(false),
+ mTimeStampLock("nsCacheService.mTimeStampLock"),
+ mInitialized(false),
+ mClearingEntries(false),
+ mEnableOfflineDevice(false),
+ mOfflineDevice(nullptr),
+ mDoomedEntries{},
+ mTotalEntries(0),
+ mCacheHits(0),
+ mCacheMisses(0),
+ mMaxKeyLength(0),
+ mMaxDataSize(0),
+ mMaxMetaSize(0),
+ mDeactivateFailures(0),
+ mDeactivatedUnboundEntries(0) {
+ NS_ASSERTION(gService == nullptr, "multiple nsCacheService instances!");
+ gService = this;
+
+ // create list of cache devices
+ PR_INIT_CLIST(&mDoomedEntries);
+}
+
+nsCacheService::~nsCacheService() {
+ if (mInitialized) // Shutdown hasn't been called yet.
+ (void)Shutdown();
+
+ if (mObserver) {
+ mObserver->Remove();
+ NS_RELEASE(mObserver);
+ }
+
+ gService = nullptr;
+}
+
+nsresult nsCacheService::Init() {
+ // Thie method must be called on the main thread because mCacheIOThread must
+ // only be modified on the main thread.
+ if (!NS_IsMainThread()) {
+ NS_ERROR("nsCacheService::Init called off the main thread");
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ NS_ASSERTION(!mInitialized, "nsCacheService already initialized.");
+ if (mInitialized) return NS_ERROR_ALREADY_INITIALIZED;
+
+ if (mozilla::net::IsNeckoChild()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv;
+
+ mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewNamedThread("Cache I/O", getter_AddRefs(mCacheIOThread));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't create cache IO thread");
+ }
+
+ rv = nsDeleteDir::Init();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't initialize nsDeleteDir");
+ }
+
+ // initialize hashtable for active cache entries
+ mActiveEntries.Init();
+
+ // create profile/preference observer
+ if (!mObserver) {
+ mObserver = new nsCacheProfilePrefObserver();
+ NS_ADDREF(mObserver);
+ mObserver->Install();
+ }
+
+ mEnableOfflineDevice = mObserver->OfflineCacheEnabled();
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+void nsCacheService::Shutdown() {
+ // This method must be called on the main thread because mCacheIOThread must
+ // only be modified on the main thread.
+ if (!NS_IsMainThread()) {
+ MOZ_CRASH("nsCacheService::Shutdown called off the main thread");
+ }
+
+ nsCOMPtr<nsIThread> cacheIOThread;
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN> totalTimer;
+
+ bool shouldSanitize = false;
+ nsCOMPtr<nsIFile> parentDir;
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
+ NS_ASSERTION(
+ mInitialized,
+ "can't shutdown nsCacheService unless it has been initialized.");
+ if (!mInitialized) return;
+
+ mClearingEntries = true;
+ DoomActiveEntries(nullptr);
+ }
+
+ CloseAllStreams();
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_SHUTDOWN));
+ NS_ASSERTION(mInitialized, "Bad state");
+
+ mInitialized = false;
+
+ // Clear entries
+ ClearDoomList();
+
+ // Make sure to wait for any pending cache-operations before
+ // proceeding with destructive actions (bug #620660)
+ (void)SyncWithCacheIOThread();
+ mActiveEntries.Shutdown();
+
+ // obtain the disk cache directory in case we need to sanitize it
+ parentDir = mObserver->DiskCacheParentDirectory();
+ shouldSanitize = mObserver->SanitizeAtShutdown();
+
+ if (mOfflineDevice) mOfflineDevice->Shutdown();
+
+ NS_IF_RELEASE(mOfflineDevice);
+
+ for (auto iter = mCustomOfflineDevices.Iter(); !iter.Done(); iter.Next()) {
+ iter.Data()->Shutdown();
+ iter.Remove();
+ }
+
+ LogCacheStatistics();
+
+ mClearingEntries = false;
+ mCacheIOThread.swap(cacheIOThread);
+ }
+
+ if (cacheIOThread) nsShutdownThread::BlockingShutdown(cacheIOThread);
+
+ if (shouldSanitize) {
+ nsresult rv = parentDir->AppendNative("Cache"_ns);
+ if (NS_SUCCEEDED(rv)) {
+ bool exists;
+ if (NS_SUCCEEDED(parentDir->Exists(&exists)) && exists)
+ nsDeleteDir::DeleteDir(parentDir, false);
+ }
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_SHUTDOWN_CLEAR_PRIVATE>
+ timer;
+ nsDeleteDir::Shutdown(shouldSanitize);
+ } else {
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR_SHUTDOWN>
+ timer;
+ nsDeleteDir::Shutdown(shouldSanitize);
+ }
+}
+
+nsresult nsCacheService::Create(nsISupports* aOuter, const nsIID& aIID,
+ void** aResult) {
+ nsresult rv;
+
+ if (aOuter != nullptr) return NS_ERROR_NO_AGGREGATION;
+
+ RefPtr<nsCacheService> cacheService = new nsCacheService();
+ rv = cacheService->Init();
+ if (NS_SUCCEEDED(rv)) {
+ rv = cacheService->QueryInterface(aIID, aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCacheService::CreateSession(const char* clientID,
+ nsCacheStoragePolicy storagePolicy,
+ bool streamBased, nsICacheSession** result) {
+ *result = nullptr;
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsCacheService::CreateSessionInternal(
+ const char* clientID, nsCacheStoragePolicy storagePolicy, bool streamBased,
+ nsICacheSession** result) {
+ RefPtr<nsCacheSession> session =
+ new nsCacheSession(clientID, storagePolicy, streamBased);
+ session.forget(result);
+
+ return NS_OK;
+}
+
+nsresult nsCacheService::EvictEntriesForSession(nsCacheSession* session) {
+ NS_ASSERTION(gService, "nsCacheService::gService is null.");
+ return gService->EvictEntriesForClient(session->ClientID()->get(),
+ session->StoragePolicy());
+}
+
+namespace {
+
+class EvictionNotifierRunnable : public Runnable {
+ public:
+ explicit EvictionNotifierRunnable(nsISupports* aSubject)
+ : mozilla::Runnable("EvictionNotifierRunnable"), mSubject(aSubject) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsCOMPtr<nsISupports> mSubject;
+};
+
+NS_IMETHODIMP
+EvictionNotifierRunnable::Run() {
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->NotifyObservers(mSubject, NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID,
+ nullptr);
+ }
+ return NS_OK;
+}
+
+} // namespace
+
+nsresult nsCacheService::EvictEntriesForClient(
+ const char* clientID, nsCacheStoragePolicy storagePolicy) {
+ RefPtr<EvictionNotifierRunnable> r =
+ new EvictionNotifierRunnable(NS_ISUPPORTS_CAST(nsICacheService*, this));
+ NS_DispatchToMainThread(r);
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_EVICTENTRIESFORCLIENT));
+ nsresult res = NS_OK;
+
+ // Only clear the offline cache if it has been specifically asked for.
+ if (storagePolicy == nsICache::STORE_OFFLINE) {
+ if (mEnableOfflineDevice) {
+ nsresult rv = NS_OK;
+ if (!mOfflineDevice) rv = CreateOfflineDevice();
+ if (mOfflineDevice) rv = mOfflineDevice->EvictEntries(clientID);
+ if (NS_FAILED(rv)) res = rv;
+ }
+ }
+
+ return res;
+}
+
+nsresult nsCacheService::IsStorageEnabledForPolicy(
+ nsCacheStoragePolicy storagePolicy, bool* result) {
+ if (gService == nullptr) return NS_ERROR_NOT_AVAILABLE;
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHESERVICE_ISSTORAGEENABLEDFORPOLICY));
+
+ *result = nsCacheService::IsStorageEnabledForPolicy_Locked(storagePolicy);
+ return NS_OK;
+}
+
+nsresult nsCacheService::DoomEntry(nsCacheSession* session,
+ const nsACString& key,
+ nsICacheListener* listener) {
+ CACHE_LOG_DEBUG(("Dooming entry for session %p, key %s\n", session,
+ PromiseFlatCString(key).get()));
+ if (!gService || !gService->mInitialized) return NS_ERROR_NOT_INITIALIZED;
+
+ return DispatchToCacheIOThread(new nsDoomEvent(session, key, listener));
+}
+
+bool nsCacheService::IsStorageEnabledForPolicy_Locked(
+ nsCacheStoragePolicy storagePolicy) {
+ if (gService->mEnableOfflineDevice &&
+ storagePolicy == nsICache::STORE_OFFLINE) {
+ return true;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor* visitor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsCacheService::VisitEntriesInternal(nsICacheVisitor* visitor) {
+ NS_ENSURE_ARG_POINTER(visitor);
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_VISITENTRIES));
+
+ if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
+
+ // XXX record the fact that a visitation is in progress,
+ // XXX i.e. keep list of visitors in progress.
+
+ nsresult rv = NS_OK;
+
+ if (mEnableOfflineDevice) {
+ if (!mOfflineDevice) {
+ rv = CreateOfflineDevice();
+ if (NS_FAILED(rv)) return rv;
+ }
+ rv = mOfflineDevice->Visit(visitor);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // XXX notify any shutdown process that visitation is complete for THIS
+ // visitor.
+ // XXX keep queue of visitors
+
+ return NS_OK;
+}
+
+void nsCacheService::FireClearNetworkCacheStoredAnywhereNotification() {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obsvc = mozilla::services::GetObserverService();
+ if (obsvc) {
+ obsvc->NotifyObservers(nullptr, "network-clear-cache-stored-anywhere",
+ nullptr);
+ }
+}
+
+NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsCacheService::EvictEntriesInternal(
+ nsCacheStoragePolicy storagePolicy) {
+ if (storagePolicy == nsICache::STORE_ANYWHERE) {
+ // if not called on main thread, dispatch the notification to the main
+ // thread to notify observers
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> event = NewRunnableMethod(
+ "nsCacheService::FireClearNetworkCacheStoredAnywhereNotification",
+ this,
+ &nsCacheService::FireClearNetworkCacheStoredAnywhereNotification);
+ NS_DispatchToMainThread(event);
+ } else {
+ // else you're already on main thread - notify observers
+ FireClearNetworkCacheStoredAnywhereNotification();
+ }
+ }
+ return EvictEntriesForClient(nullptr, storagePolicy);
+}
+
+NS_IMETHODIMP nsCacheService::GetCacheIOTarget(
+ nsIEventTarget** aCacheIOTarget) {
+ NS_ENSURE_ARG_POINTER(aCacheIOTarget);
+
+ // Because mCacheIOThread can only be changed on the main thread, it can be
+ // read from the main thread without the lock. This is useful to prevent
+ // blocking the main thread on other cache operations.
+ if (!NS_IsMainThread()) {
+ Lock(LOCK_TELEM(NSCACHESERVICE_GETCACHEIOTARGET));
+ }
+
+ nsresult rv;
+ if (mCacheIOThread) {
+ *aCacheIOTarget = do_AddRef(mCacheIOThread).take();
+ rv = NS_OK;
+ } else {
+ *aCacheIOTarget = nullptr;
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!NS_IsMainThread()) {
+ Unlock();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsCacheService::GetLockHeldTime(double* aLockHeldTime) {
+ MutexAutoLock lock(mTimeStampLock);
+
+ if (mLockAcquiredTimeStamp.IsNull()) {
+ *aLockHeldTime = 0.0;
+ } else {
+ *aLockHeldTime =
+ (TimeStamp::Now() - mLockAcquiredTimeStamp).ToMilliseconds();
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Internal Methods
+ */
+nsresult nsCacheService::GetOfflineDevice(nsOfflineCacheDevice** aDevice) {
+ if (!mOfflineDevice) {
+ nsresult rv = CreateOfflineDevice();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ *aDevice = do_AddRef(mOfflineDevice).take();
+ return NS_OK;
+}
+
+nsresult nsCacheService::GetCustomOfflineDevice(
+ nsIFile* aProfileDir, int32_t aQuota, nsOfflineCacheDevice** aDevice) {
+ nsresult rv;
+
+ nsAutoString profilePath;
+ rv = aProfileDir->GetPath(profilePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mCustomOfflineDevices.Get(profilePath, aDevice)) {
+ rv = CreateCustomOfflineDevice(aProfileDir, aQuota, aDevice);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ (*aDevice)->SetAutoShutdown();
+ mCustomOfflineDevices.Put(profilePath, RefPtr{*aDevice});
+ }
+
+ return NS_OK;
+}
+
+nsresult nsCacheService::CreateOfflineDevice() {
+ CACHE_LOG_INFO(("Creating default offline device"));
+
+ if (mOfflineDevice) return NS_OK;
+ if (!nsCacheService::IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return CreateCustomOfflineDevice(mObserver->OfflineCacheParentDirectory(),
+ mObserver->OfflineCacheCapacity(),
+ &mOfflineDevice);
+}
+
+nsresult nsCacheService::CreateCustomOfflineDevice(
+ nsIFile* aProfileDir, int32_t aQuota, nsOfflineCacheDevice** aDevice) {
+ NS_ENSURE_ARG(aProfileDir);
+
+ if (MOZ_LOG_TEST(gCacheLog, LogLevel::Info)) {
+ CACHE_LOG_INFO(("Creating custom offline device, %s, %d",
+ aProfileDir->HumanReadablePath().get(), aQuota));
+ }
+
+ if (!mInitialized) {
+ NS_WARNING("nsCacheService not initialized");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!mEnableOfflineDevice) return NS_ERROR_NOT_AVAILABLE;
+
+ RefPtr<nsOfflineCacheDevice> device = new nsOfflineCacheDevice();
+
+ // set the preferences
+ device->SetCacheParentDirectory(aProfileDir);
+ device->SetCapacity(aQuota);
+
+ nsresult rv = device->InitWithSqlite(mStorageService);
+ if (NS_FAILED(rv)) {
+ CACHE_LOG_DEBUG(("OfflineDevice->InitWithSqlite() failed (0x%.8" PRIx32
+ ")\n",
+ static_cast<uint32_t>(rv)));
+ CACHE_LOG_DEBUG((" - disabling offline cache for this session.\n"));
+ device = nullptr;
+ }
+
+ device.forget(aDevice);
+ return rv;
+}
+
+nsresult nsCacheService::RemoveCustomOfflineDevice(
+ nsOfflineCacheDevice* aDevice) {
+ nsCOMPtr<nsIFile> profileDir = aDevice->BaseDirectory();
+ if (!profileDir) return NS_ERROR_UNEXPECTED;
+
+ nsAutoString profilePath;
+ nsresult rv = profileDir->GetPath(profilePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCustomOfflineDevices.Remove(profilePath);
+ return NS_OK;
+}
+
+nsresult nsCacheService::CreateRequest(nsCacheSession* session,
+ const nsACString& clientKey,
+ nsCacheAccessMode accessRequested,
+ bool blockingMode,
+ nsICacheListener* listener,
+ nsCacheRequest** request) {
+ NS_ASSERTION(request, "CreateRequest: request is null");
+
+ nsAutoCString key(*session->ClientID());
+ key.Append(':');
+ key.Append(clientKey);
+
+ if (mMaxKeyLength < key.Length()) mMaxKeyLength = key.Length();
+
+ // create request
+ *request =
+ new nsCacheRequest(key, listener, accessRequested, blockingMode, session);
+
+ if (!listener) return NS_OK; // we're sync, we're done.
+
+ // get the request's thread
+ (*request)->mEventTarget = GetCurrentEventTarget();
+
+ return NS_OK;
+}
+
+class nsCacheListenerEvent : public Runnable {
+ public:
+ nsCacheListenerEvent(nsICacheListener* listener,
+ nsICacheEntryDescriptor* descriptor,
+ nsCacheAccessMode accessGranted, nsresult status)
+ : mozilla::Runnable("nsCacheListenerEvent"),
+ mListener(listener) // transfers reference
+ ,
+ mDescriptor(descriptor) // transfers reference (may be null)
+ ,
+ mAccessGranted(accessGranted),
+ mStatus(status) {}
+
+ NS_IMETHOD Run() override {
+ mListener->OnCacheEntryAvailable(mDescriptor, mAccessGranted, mStatus);
+
+ NS_RELEASE(mListener);
+ NS_IF_RELEASE(mDescriptor);
+ return NS_OK;
+ }
+
+ private:
+ // We explicitly leak mListener or mDescriptor if Run is not called
+ // because otherwise we cannot guarantee that they are destroyed on
+ // the right thread.
+
+ nsICacheListener* mListener;
+ nsICacheEntryDescriptor* mDescriptor;
+ nsCacheAccessMode mAccessGranted;
+ nsresult mStatus;
+};
+
+nsresult nsCacheService::NotifyListener(nsCacheRequest* request,
+ nsICacheEntryDescriptor* descriptor,
+ nsCacheAccessMode accessGranted,
+ nsresult status) {
+ NS_ASSERTION(request->mEventTarget, "no thread set in async request!");
+
+ // Swap ownership, and release listener on target thread...
+ nsICacheListener* listener = request->mListener;
+ request->mListener = nullptr;
+
+ nsCOMPtr<nsIRunnable> ev =
+ new nsCacheListenerEvent(listener, descriptor, accessGranted, status);
+ if (!ev) {
+ // Better to leak listener and descriptor if we fail because we don't
+ // want to destroy them inside the cache service lock or on potentially
+ // the wrong thread.
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return request->mEventTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
+}
+
+nsresult nsCacheService::ProcessRequest(nsCacheRequest* request,
+ bool calledFromOpenCacheEntry,
+ nsICacheEntryDescriptor** result) {
+ // !!! must be called with mLock held !!!
+ nsresult rv;
+ nsCacheEntry* entry = nullptr;
+ nsCacheEntry* doomedEntry = nullptr;
+ nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
+ if (result) *result = nullptr;
+
+ while (true) { // Activate entry loop
+ rv = ActivateEntry(request, &entry,
+ &doomedEntry); // get the entry for this request
+ if (NS_FAILED(rv)) break;
+
+ while (true) { // Request Access loop
+ NS_ASSERTION(entry, "no entry in Request Access loop!");
+ // entry->RequestAccess queues request on entry
+ rv = entry->RequestAccess(request, &accessGranted);
+ if (rv != NS_ERROR_CACHE_WAIT_FOR_VALIDATION) break;
+
+ if (request->IsBlocking()) {
+ if (request->mListener) {
+ // async exits - validate, doom, or close will resume
+ return rv;
+ }
+
+ // XXX this is probably wrong...
+ Unlock();
+ rv = request->WaitForValidation();
+ Lock(LOCK_TELEM(NSCACHESERVICE_PROCESSREQUEST));
+ }
+
+ PR_REMOVE_AND_INIT_LINK(request);
+ if (NS_FAILED(rv))
+ break; // non-blocking mode returns WAIT_FOR_VALIDATION error
+ // okay, we're ready to process this request, request access again
+ }
+ if (rv != NS_ERROR_CACHE_ENTRY_DOOMED) break;
+
+ if (entry->IsNotInUse()) {
+ // this request was the last one keeping it around, so get rid of it
+ DeactivateEntry(entry);
+ }
+ // loop back around to look for another entry
+ }
+
+ if (NS_SUCCEEDED(rv) && request->mProfileDir) {
+ // Custom cache directory has been demanded. Preset the cache device.
+ if (entry->StoragePolicy() != nsICache::STORE_OFFLINE) {
+ // Failsafe check: this is implemented only for offline cache atm.
+ rv = NS_ERROR_FAILURE;
+ } else {
+ RefPtr<nsOfflineCacheDevice> customCacheDevice;
+ rv = GetCustomOfflineDevice(request->mProfileDir, -1,
+ getter_AddRefs(customCacheDevice));
+ if (NS_SUCCEEDED(rv)) entry->SetCustomCacheDevice(customCacheDevice);
+ }
+ }
+
+ nsICacheEntryDescriptor* descriptor = nullptr;
+
+ if (NS_SUCCEEDED(rv))
+ rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
+
+ // If doomedEntry is set, ActivatEntry() doomed an existing entry and
+ // created a new one for that cache-key. However, any pending requests
+ // on the doomed entry were not processed and we need to do that here.
+ // This must be done after adding the created entry to list of active
+ // entries (which is done in ActivateEntry()) otherwise the hashkeys crash
+ // (see bug ##561313). It is also important to do this after creating a
+ // descriptor for this request, or some other request may end up being
+ // executed first for the newly created entry.
+ // Finally, it is worth to emphasize that if doomedEntry is set,
+ // ActivateEntry() created a new entry for the request, which will be
+ // initialized by RequestAccess() and they both should have returned NS_OK.
+ if (doomedEntry) {
+ (void)ProcessPendingRequests(doomedEntry);
+ if (doomedEntry->IsNotInUse()) DeactivateEntry(doomedEntry);
+ doomedEntry = nullptr;
+ }
+
+ if (request->mListener) { // Asynchronous
+
+ if (NS_FAILED(rv) && calledFromOpenCacheEntry && request->IsBlocking())
+ return rv; // skip notifying listener, just return rv to caller
+
+ // call listener to report error or descriptor
+ nsresult rv2 = NotifyListener(request, descriptor, accessGranted, rv);
+ if (NS_FAILED(rv2) && NS_SUCCEEDED(rv)) {
+ rv = rv2; // trigger delete request
+ }
+ } else { // Synchronous
+ *result = descriptor;
+ }
+ return rv;
+}
+
+nsresult nsCacheService::OpenCacheEntry(nsCacheSession* session,
+ const nsACString& key,
+ nsCacheAccessMode accessRequested,
+ bool blockingMode,
+ nsICacheListener* listener,
+ nsICacheEntryDescriptor** result) {
+ CACHE_LOG_DEBUG(
+ ("Opening entry for session %p, key %s, mode %d, blocking %d\n", session,
+ PromiseFlatCString(key).get(), accessRequested, blockingMode));
+ if (result) *result = nullptr;
+
+ if (!gService || !gService->mInitialized) return NS_ERROR_NOT_INITIALIZED;
+
+ nsCacheRequest* request = nullptr;
+
+ nsresult rv = gService->CreateRequest(session, key, accessRequested,
+ blockingMode, listener, &request);
+ if (NS_FAILED(rv)) return rv;
+
+ CACHE_LOG_DEBUG(("Created request %p\n", request));
+
+ // Process the request on the background thread if we are on the main thread
+ // and the the request is asynchronous
+ if (NS_IsMainThread() && listener && gService->mCacheIOThread) {
+ nsCOMPtr<nsIRunnable> ev = new nsProcessRequestEvent(request);
+ rv = DispatchToCacheIOThread(ev);
+
+ // delete request if we didn't post the event
+ if (NS_FAILED(rv)) delete request;
+ } else {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_OPENCACHEENTRY));
+ rv = gService->ProcessRequest(request, true, result);
+
+ // delete requests that have completed
+ if (!(listener && blockingMode &&
+ (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)))
+ delete request;
+ }
+
+ return rv;
+}
+
+nsresult nsCacheService::ActivateEntry(nsCacheRequest* request,
+ nsCacheEntry** result,
+ nsCacheEntry** doomedEntry) {
+ CACHE_LOG_DEBUG(("Activate entry for request %p\n", request));
+ if (!mInitialized || mClearingEntries) return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = NS_OK;
+
+ NS_ASSERTION(request != nullptr, "ActivateEntry called with no request");
+ if (result) *result = nullptr;
+ if (doomedEntry) *doomedEntry = nullptr;
+ if ((!request) || (!result) || (!doomedEntry)) return NS_ERROR_NULL_POINTER;
+
+ // check if the request can be satisfied
+ if (!request->IsStreamBased()) return NS_ERROR_FAILURE;
+ if (!IsStorageEnabledForPolicy_Locked(request->StoragePolicy()))
+ return NS_ERROR_FAILURE;
+
+ // search active entries (including those not bound to device)
+ nsCacheEntry* entry = mActiveEntries.GetEntry(&(request->mKey));
+ CACHE_LOG_DEBUG(("Active entry for request %p is %p\n", request, entry));
+
+ if (!entry) {
+ // search cache devices for entry
+ bool collision = false;
+ entry = SearchCacheDevices(&(request->mKey), request->StoragePolicy(),
+ &collision);
+ CACHE_LOG_DEBUG(
+ ("Device search for request %p returned %p\n", request, entry));
+ // When there is a hashkey collision just refuse to cache it...
+ if (collision) return NS_ERROR_CACHE_IN_USE;
+
+ if (entry) entry->MarkInitialized();
+ } else {
+ NS_ASSERTION(entry->IsActive(), "Inactive entry found in mActiveEntries!");
+ }
+
+ if (entry) {
+ ++mCacheHits;
+ entry->Fetched();
+ } else {
+ ++mCacheMisses;
+ }
+
+ if (entry && ((request->AccessRequested() == nsICache::ACCESS_WRITE) ||
+ ((request->StoragePolicy() != nsICache::STORE_OFFLINE) &&
+ (entry->mExpirationTime <= SecondsFromPRTime(PR_Now()) &&
+ request->WillDoomEntriesIfExpired()))))
+
+ {
+ // this is FORCE-WRITE request or the entry has expired
+ // we doom entry without processing pending requests, but store it in
+ // doomedEntry which causes pending requests to be processed below
+ rv = DoomEntry_Internal(entry, false);
+ *doomedEntry = entry;
+ if (NS_FAILED(rv)) {
+ // XXX what to do? Increment FailedDooms counter?
+ }
+ entry = nullptr;
+ }
+
+ if (!entry) {
+ if (!(request->AccessRequested() & nsICache::ACCESS_WRITE)) {
+ // this is a READ-ONLY request
+ rv = NS_ERROR_CACHE_KEY_NOT_FOUND;
+ goto error;
+ }
+
+ entry = new nsCacheEntry(request->mKey, request->IsStreamBased(),
+ request->StoragePolicy());
+ if (!entry) return NS_ERROR_OUT_OF_MEMORY;
+
+ if (request->IsPrivate()) entry->MarkPrivate();
+
+ entry->Fetched();
+ ++mTotalEntries;
+
+ // XXX we could perform an early bind in some cases based on storage policy
+ }
+
+ if (!entry->IsActive()) {
+ rv = mActiveEntries.AddEntry(entry);
+ if (NS_FAILED(rv)) goto error;
+ CACHE_LOG_DEBUG(("Added entry %p to mActiveEntries\n", entry));
+ entry->MarkActive(); // mark entry active, because it's now in
+ // mActiveEntries
+ }
+ *result = entry;
+ return NS_OK;
+
+error:
+ *result = nullptr;
+ delete entry;
+ return rv;
+}
+
+nsCacheEntry* nsCacheService::SearchCacheDevices(nsCString* key,
+ nsCacheStoragePolicy policy,
+ bool* collision) {
+ Telemetry::AutoTimer<Telemetry::CACHE_DEVICE_SEARCH_2> timer;
+ nsCacheEntry* entry = nullptr;
+
+ *collision = false;
+ if (policy == nsICache::STORE_OFFLINE ||
+ (policy == nsICache::STORE_ANYWHERE && gIOService->IsOffline())) {
+ if (mEnableOfflineDevice) {
+ if (!mOfflineDevice) {
+ nsresult rv = CreateOfflineDevice();
+ if (NS_FAILED(rv)) return nullptr;
+ }
+
+ entry = mOfflineDevice->FindEntry(key, collision);
+ }
+ }
+
+ return entry;
+}
+
+nsCacheDevice* nsCacheService::EnsureEntryHasDevice(nsCacheEntry* entry) {
+ nsCacheDevice* device = entry->CacheDevice();
+ // return device if found, possibly null if the entry is doomed i.e prevent
+ // doomed entries to bind to a device (see e.g. bugs #548406 and #596443)
+ if (device || entry->IsDoomed()) return device;
+
+ if (!device && entry->IsStreamData() && entry->IsAllowedOffline() &&
+ mEnableOfflineDevice) {
+ if (!mOfflineDevice) {
+ (void)CreateOfflineDevice(); // ignore the error (check for
+ // mOfflineDevice instead)
+ }
+
+ device = entry->CustomCacheDevice() ? entry->CustomCacheDevice()
+ : mOfflineDevice;
+
+ if (device) {
+ entry->MarkBinding();
+ nsresult rv = device->BindEntry(entry);
+ entry->ClearBinding();
+ if (NS_FAILED(rv)) device = nullptr;
+ }
+ }
+
+ if (device) entry->SetCacheDevice(device);
+ return device;
+}
+
+nsresult nsCacheService::DoomEntry(nsCacheEntry* entry) {
+ return gService->DoomEntry_Internal(entry, true);
+}
+
+nsresult nsCacheService::DoomEntry_Internal(nsCacheEntry* entry,
+ bool doProcessPendingRequests) {
+ if (entry->IsDoomed()) return NS_OK;
+
+ CACHE_LOG_DEBUG(("Dooming entry %p\n", entry));
+ nsresult rv = NS_OK;
+ entry->MarkDoomed();
+
+ NS_ASSERTION(!entry->IsBinding(), "Dooming entry while binding device.");
+ nsCacheDevice* device = entry->CacheDevice();
+ if (device) device->DoomEntry(entry);
+
+ if (entry->IsActive()) {
+ // remove from active entries
+ mActiveEntries.RemoveEntry(entry);
+ CACHE_LOG_DEBUG(("Removed entry %p from mActiveEntries\n", entry));
+ entry->MarkInactive();
+ }
+
+ // put on doom list to wait for descriptors to close
+ NS_ASSERTION(PR_CLIST_IS_EMPTY(entry), "doomed entry still on device list");
+ PR_APPEND_LINK(entry, &mDoomedEntries);
+
+ // handle pending requests only if we're supposed to
+ if (doProcessPendingRequests) {
+ // tell pending requests to get on with their lives...
+ rv = ProcessPendingRequests(entry);
+
+ // All requests have been removed, but there may still be open descriptors
+ if (entry->IsNotInUse()) {
+ DeactivateEntry(entry); // tell device to get rid of it
+ }
+ }
+ return rv;
+}
+
+void nsCacheService::OnProfileShutdown() {
+ if (!gService || !gService->mInitialized) {
+ // The cache service has been shut down, but someone is still holding
+ // a reference to it. Ignore this call.
+ return;
+ }
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
+ gService->mClearingEntries = true;
+ gService->DoomActiveEntries(nullptr);
+ }
+
+ gService->CloseAllStreams();
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILESHUTDOWN));
+ gService->ClearDoomList();
+
+ // Make sure to wait for any pending cache-operations before
+ // proceeding with destructive actions (bug #620660)
+ (void)SyncWithCacheIOThread();
+
+ if (gService->mOfflineDevice && gService->mEnableOfflineDevice) {
+ gService->mOfflineDevice->Shutdown();
+ }
+ for (auto iter = gService->mCustomOfflineDevices.Iter(); !iter.Done();
+ iter.Next()) {
+ iter.Data()->Shutdown();
+ iter.Remove();
+ }
+
+ gService->mEnableOfflineDevice = false;
+
+ gService->mClearingEntries = false;
+}
+
+void nsCacheService::OnProfileChanged() {
+ if (!gService) return;
+
+ CACHE_LOG_DEBUG(("nsCacheService::OnProfileChanged"));
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_ONPROFILECHANGED));
+
+ gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
+
+ if (gService->mOfflineDevice) {
+ gService->mOfflineDevice->SetCacheParentDirectory(
+ gService->mObserver->OfflineCacheParentDirectory());
+ gService->mOfflineDevice->SetCapacity(
+ gService->mObserver->OfflineCacheCapacity());
+
+ // XXX initialization of mOfflineDevice could be made lazily, if
+ // mEnableOfflineDevice is false
+ nsresult rv =
+ gService->mOfflineDevice->InitWithSqlite(gService->mStorageService);
+ if (NS_FAILED(rv)) {
+ NS_ERROR(
+ "nsCacheService::OnProfileChanged: Re-initializing offline device "
+ "failed");
+ gService->mEnableOfflineDevice = false;
+ // XXX delete mOfflineDevice?
+ }
+ }
+}
+
+void nsCacheService::SetOfflineCacheEnabled(bool enabled) {
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHEENABLED));
+ gService->mEnableOfflineDevice = enabled;
+}
+
+void nsCacheService::SetOfflineCacheCapacity(int32_t capacity) {
+ if (!gService) return;
+ nsCacheServiceAutoLock lock(
+ LOCK_TELEM(NSCACHESERVICE_SETOFFLINECACHECAPACITY));
+
+ if (gService->mOfflineDevice) {
+ gService->mOfflineDevice->SetCapacity(capacity);
+ }
+
+ gService->mEnableOfflineDevice = gService->mObserver->OfflineCacheEnabled();
+}
+
+/******************************************************************************
+ * static methods for nsCacheEntryDescriptor
+ *****************************************************************************/
+void nsCacheService::CloseDescriptor(nsCacheEntryDescriptor* descriptor) {
+ // ask entry to remove descriptor
+ nsCacheEntry* entry = descriptor->CacheEntry();
+ bool doomEntry;
+ bool stillActive = entry->RemoveDescriptor(descriptor, &doomEntry);
+
+ if (!entry->IsValid()) {
+ gService->ProcessPendingRequests(entry);
+ }
+
+ if (doomEntry) {
+ gService->DoomEntry_Internal(entry, true);
+ return;
+ }
+
+ if (!stillActive) {
+ gService->DeactivateEntry(entry);
+ }
+}
+
+nsresult nsCacheService::GetFileForEntry(nsCacheEntry* entry,
+ nsIFile** result) {
+ nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ return device->GetFileForEntry(entry, result);
+}
+
+nsresult nsCacheService::OpenInputStreamForEntry(nsCacheEntry* entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream** result) {
+ nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ return device->OpenInputStreamForEntry(entry, mode, offset, result);
+}
+
+nsresult nsCacheService::OpenOutputStreamForEntry(nsCacheEntry* entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream** result) {
+ nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ return device->OpenOutputStreamForEntry(entry, mode, offset, result);
+}
+
+nsresult nsCacheService::OnDataSizeChange(nsCacheEntry* entry,
+ int32_t deltaSize) {
+ nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ return device->OnDataSizeChange(entry, deltaSize);
+}
+
+void nsCacheService::LockAcquired() {
+ MutexAutoLock lock(mTimeStampLock);
+ mLockAcquiredTimeStamp = TimeStamp::Now();
+}
+
+void nsCacheService::LockReleased() {
+ MutexAutoLock lock(mTimeStampLock);
+ mLockAcquiredTimeStamp = TimeStamp();
+}
+
+void nsCacheService::Lock() {
+ gService->mLock.Lock();
+ gService->LockAcquired();
+}
+
+void nsCacheService::Lock(mozilla::Telemetry::HistogramID mainThreadLockerID) {
+ mozilla::Telemetry::HistogramID lockerID;
+ mozilla::Telemetry::HistogramID generalID;
+
+ if (NS_IsMainThread()) {
+ lockerID = mainThreadLockerID;
+ generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_2;
+ } else {
+ lockerID = mozilla::Telemetry::HistogramCount;
+ generalID = mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_2;
+ }
+
+ TimeStamp start(TimeStamp::Now());
+
+ nsCacheService::Lock();
+
+ TimeStamp stop(TimeStamp::Now());
+
+ // Telemetry isn't thread safe on its own, but this is OK because we're
+ // protecting it with the cache lock.
+ if (lockerID != mozilla::Telemetry::HistogramCount) {
+ mozilla::Telemetry::AccumulateTimeDelta(lockerID, start, stop);
+ }
+ mozilla::Telemetry::AccumulateTimeDelta(generalID, start, stop);
+}
+
+void nsCacheService::Unlock() {
+ gService->mLock.AssertCurrentThreadOwns();
+
+ nsTArray<nsISupports*> doomed = std::move(gService->mDoomedObjects);
+
+ gService->LockReleased();
+ gService->mLock.Unlock();
+
+ for (uint32_t i = 0; i < doomed.Length(); ++i) doomed[i]->Release();
+}
+
+void nsCacheService::ReleaseObject_Locked(nsISupports* obj,
+ nsIEventTarget* target) {
+ gService->mLock.AssertCurrentThreadOwns();
+
+ bool isCur;
+ if (!target || (NS_SUCCEEDED(target->IsOnCurrentThread(&isCur)) && isCur)) {
+ gService->mDoomedObjects.AppendElement(obj);
+ } else {
+ NS_ProxyRelease("nsCacheService::ReleaseObject_Locked::obj", target,
+ dont_AddRef(obj));
+ }
+}
+
+nsresult nsCacheService::SetCacheElement(nsCacheEntry* entry,
+ nsISupports* element) {
+ entry->SetData(element);
+ entry->TouchData();
+ return NS_OK;
+}
+
+nsresult nsCacheService::ValidateEntry(nsCacheEntry* entry) {
+ nsCacheDevice* device = gService->EnsureEntryHasDevice(entry);
+ if (!device) return NS_ERROR_UNEXPECTED;
+
+ entry->MarkValid();
+ nsresult rv = gService->ProcessPendingRequests(entry);
+ NS_ASSERTION(rv == NS_OK, "ProcessPendingRequests failed.");
+ // XXX what else should be done?
+
+ return rv;
+}
+
+int32_t nsCacheService::CacheCompressionLevel() {
+ int32_t level = gService->mObserver->CacheCompressionLevel();
+ return level;
+}
+
+void nsCacheService::DeactivateEntry(nsCacheEntry* entry) {
+ CACHE_LOG_DEBUG(("Deactivating entry %p\n", entry));
+ nsresult rv = NS_OK;
+ NS_ASSERTION(entry->IsNotInUse(), "### deactivating an entry while in use!");
+ nsCacheDevice* device = nullptr;
+
+ if (mMaxDataSize < entry->DataSize()) mMaxDataSize = entry->DataSize();
+ if (mMaxMetaSize < entry->MetaDataSize())
+ mMaxMetaSize = entry->MetaDataSize();
+
+ if (entry->IsDoomed()) {
+ // remove from Doomed list
+ PR_REMOVE_AND_INIT_LINK(entry);
+ } else if (entry->IsActive()) {
+ // remove from active entries
+ mActiveEntries.RemoveEntry(entry);
+ CACHE_LOG_DEBUG(
+ ("Removed deactivated entry %p from mActiveEntries\n", entry));
+ entry->MarkInactive();
+
+ // bind entry if necessary to store meta-data
+ device = EnsureEntryHasDevice(entry);
+ if (!device) {
+ CACHE_LOG_DEBUG(
+ ("DeactivateEntry: unable to bind active "
+ "entry %p\n",
+ entry));
+ NS_WARNING("DeactivateEntry: unable to bind active entry\n");
+ return;
+ }
+ } else {
+ // if mInitialized == false,
+ // then we're shutting down and this state is okay.
+ NS_ASSERTION(!mInitialized, "DeactivateEntry: bad cache entry state.");
+ }
+
+ device = entry->CacheDevice();
+ if (device) {
+ rv = device->DeactivateEntry(entry);
+ if (NS_FAILED(rv)) {
+ // increment deactivate failure count
+ ++mDeactivateFailures;
+ }
+ } else {
+ // increment deactivating unbound entry statistic
+ ++mDeactivatedUnboundEntries;
+ delete entry; // because no one else will
+ }
+}
+
+nsresult nsCacheService::ProcessPendingRequests(nsCacheEntry* entry) {
+ nsresult rv = NS_OK;
+ nsCacheRequest* request = (nsCacheRequest*)PR_LIST_HEAD(&entry->mRequestQ);
+ nsCacheRequest* nextRequest;
+ bool newWriter = false;
+
+ CACHE_LOG_DEBUG((
+ "ProcessPendingRequests for %sinitialized %s %salid entry %p\n",
+ (entry->IsInitialized() ? "" : "Un"), (entry->IsDoomed() ? "DOOMED" : ""),
+ (entry->IsValid() ? "V" : "Inv"), entry));
+
+ if (request == &entry->mRequestQ) return NS_OK; // no queued requests
+
+ if (!entry->IsDoomed() && entry->IsInvalid()) {
+ // 1st descriptor closed w/o MarkValid()
+ NS_ASSERTION(PR_CLIST_IS_EMPTY(&entry->mDescriptorQ),
+ "shouldn't be here with open descriptors");
+
+#if DEBUG
+ // verify no ACCESS_WRITE requests(shouldn't have any of these)
+ while (request != &entry->mRequestQ) {
+ NS_ASSERTION(request->AccessRequested() != nsICache::ACCESS_WRITE,
+ "ACCESS_WRITE request should have been given a new entry");
+ request = (nsCacheRequest*)PR_NEXT_LINK(request);
+ }
+ request = (nsCacheRequest*)PR_LIST_HEAD(&entry->mRequestQ);
+#endif
+ // find first request with ACCESS_READ_WRITE (if any) and promote it to 1st
+ // writer
+ while (request != &entry->mRequestQ) {
+ if (request->AccessRequested() == nsICache::ACCESS_READ_WRITE) {
+ newWriter = true;
+ CACHE_LOG_DEBUG((" promoting request %p to 1st writer\n", request));
+ break;
+ }
+
+ request = (nsCacheRequest*)PR_NEXT_LINK(request);
+ }
+
+ if (request == &entry->mRequestQ) // no requests asked for
+ // ACCESS_READ_WRITE, back to top
+ request = (nsCacheRequest*)PR_LIST_HEAD(&entry->mRequestQ);
+
+ // XXX what should we do if there are only READ requests in queue?
+ // XXX serialize their accesses, give them only read access, but force them
+ // to check validate flag?
+ // XXX or do readers simply presume the entry is valid
+ // See fix for bug #467392 below
+ }
+
+ nsCacheAccessMode accessGranted = nsICache::ACCESS_NONE;
+
+ while (request != &entry->mRequestQ) {
+ nextRequest = (nsCacheRequest*)PR_NEXT_LINK(request);
+ CACHE_LOG_DEBUG((" %sync request %p for %p\n",
+ (request->mListener ? "As" : "S"), request, entry));
+
+ if (request->mListener) {
+ // Async request
+ PR_REMOVE_AND_INIT_LINK(request);
+
+ if (entry->IsDoomed()) {
+ rv = ProcessRequest(request, false, nullptr);
+ if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION)
+ rv = NS_OK;
+ else
+ delete request;
+
+ if (NS_FAILED(rv)) {
+ // XXX what to do?
+ }
+ } else if (entry->IsValid() || newWriter) {
+ rv = entry->RequestAccess(request, &accessGranted);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "if entry is valid, RequestAccess must succeed.");
+ // XXX if (newWriter) {
+ // NS_ASSERTION( accessGranted ==
+ // request->AccessRequested(), "why not?");
+ // }
+
+ // entry->CreateDescriptor dequeues request, and queues descriptor
+ nsICacheEntryDescriptor* descriptor = nullptr;
+ rv = entry->CreateDescriptor(request, accessGranted, &descriptor);
+
+ // post call to listener to report error or descriptor
+ rv = NotifyListener(request, descriptor, accessGranted, rv);
+ delete request;
+ if (NS_FAILED(rv)) {
+ // XXX what to do?
+ }
+
+ } else {
+ // read-only request to an invalid entry - need to wait for
+ // the entry to become valid so we post an event to process
+ // the request again later (bug #467392)
+ nsCOMPtr<nsIRunnable> ev = new nsProcessRequestEvent(request);
+ rv = DispatchToCacheIOThread(ev);
+ if (NS_FAILED(rv)) {
+ delete request; // avoid leak
+ }
+ }
+ } else {
+ // Synchronous request
+ request->WakeUp();
+ }
+ if (newWriter) break; // process remaining requests after validation
+ request = nextRequest;
+ }
+
+ return NS_OK;
+}
+
+bool nsCacheService::IsDoomListEmpty() {
+ nsCacheEntry* entry = (nsCacheEntry*)PR_LIST_HEAD(&mDoomedEntries);
+ return &mDoomedEntries == entry;
+}
+
+void nsCacheService::ClearDoomList() {
+ nsCacheEntry* entry = (nsCacheEntry*)PR_LIST_HEAD(&mDoomedEntries);
+
+ while (entry != &mDoomedEntries) {
+ nsCacheEntry* next = (nsCacheEntry*)PR_NEXT_LINK(entry);
+
+ entry->DetachDescriptors();
+ DeactivateEntry(entry);
+ entry = next;
+ }
+}
+
+void nsCacheService::DoomActiveEntries(DoomCheckFn check) {
+ AutoTArray<nsCacheEntry*, 8> array;
+
+ for (auto iter = mActiveEntries.Iter(); !iter.Done(); iter.Next()) {
+ nsCacheEntry* entry =
+ static_cast<nsCacheEntryHashTableEntry*>(iter.Get())->cacheEntry;
+
+ if (check && !check(entry)) {
+ continue;
+ }
+
+ array.AppendElement(entry);
+
+ // entry is being removed from the active entry list
+ entry->MarkInactive();
+ iter.Remove();
+ }
+
+ uint32_t count = array.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ DoomEntry_Internal(array[i], true);
+ }
+}
+
+void nsCacheService::CloseAllStreams() {
+ nsTArray<RefPtr<nsCacheEntryDescriptor::nsInputStreamWrapper> > inputs;
+ nsTArray<RefPtr<nsCacheEntryDescriptor::nsOutputStreamWrapper> > outputs;
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHESERVICE_CLOSEALLSTREAMS));
+
+ nsTArray<nsCacheEntry*> entries;
+
+#if DEBUG
+ // make sure there is no active entry
+ for (auto iter = mActiveEntries.Iter(); !iter.Done(); iter.Next()) {
+ auto entry = static_cast<nsCacheEntryHashTableEntry*>(iter.Get());
+ entries.AppendElement(entry->cacheEntry);
+ }
+ NS_ASSERTION(entries.IsEmpty(), "Bad state");
+#endif
+
+ // Get doomed entries
+ nsCacheEntry* entry = (nsCacheEntry*)PR_LIST_HEAD(&mDoomedEntries);
+ while (entry != &mDoomedEntries) {
+ nsCacheEntry* next = (nsCacheEntry*)PR_NEXT_LINK(entry);
+ entries.AppendElement(entry);
+ entry = next;
+ }
+
+ // Iterate through all entries and collect input and output streams
+ for (size_t i = 0; i < entries.Length(); i++) {
+ entry = entries.ElementAt(i);
+
+ nsTArray<RefPtr<nsCacheEntryDescriptor> > descs;
+ entry->GetDescriptors(descs);
+
+ for (uint32_t j = 0; j < descs.Length(); j++) {
+ if (descs[j]->mOutputWrapper)
+ outputs.AppendElement(descs[j]->mOutputWrapper);
+
+ for (size_t k = 0; k < descs[j]->mInputWrappers.Length(); k++)
+ inputs.AppendElement(descs[j]->mInputWrappers[k]);
+ }
+ }
+ }
+
+ uint32_t i;
+ for (i = 0; i < inputs.Length(); i++) inputs[i]->Close();
+
+ for (i = 0; i < outputs.Length(); i++) outputs[i]->Close();
+}
+
+bool nsCacheService::GetClearingEntries() {
+ AssertOwnsLock();
+ return gService->mClearingEntries;
+}
+
+// static
+void nsCacheService::GetCacheBaseDirectoty(nsIFile** result) {
+ *result = nullptr;
+ if (!gService || !gService->mObserver) return;
+
+ nsCOMPtr<nsIFile> directory = gService->mObserver->DiskCacheParentDirectory();
+ if (!directory) return;
+
+ directory->Clone(result);
+}
+
+// static
+void nsCacheService::GetDiskCacheDirectory(nsIFile** result) {
+ nsCOMPtr<nsIFile> directory;
+ GetCacheBaseDirectoty(getter_AddRefs(directory));
+ if (!directory) return;
+
+ nsresult rv = directory->AppendNative("Cache"_ns);
+ if (NS_FAILED(rv)) return;
+
+ directory.forget(result);
+}
+
+// static
+void nsCacheService::GetAppCacheDirectory(nsIFile** result) {
+ nsCOMPtr<nsIFile> directory;
+ GetCacheBaseDirectoty(getter_AddRefs(directory));
+ if (!directory) return;
+
+ nsresult rv = directory->AppendNative("OfflineCache"_ns);
+ if (NS_FAILED(rv)) return;
+
+ directory.forget(result);
+}
+
+void nsCacheService::LogCacheStatistics() {
+ uint32_t hitPercentage = 0;
+ double sum = (double)(mCacheHits + mCacheMisses);
+ if (sum != 0) {
+ hitPercentage = (uint32_t)((((double)mCacheHits) / sum) * 100);
+ }
+ CACHE_LOG_INFO(("\nCache Service Statistics:\n\n"));
+ CACHE_LOG_INFO((" TotalEntries = %d\n", mTotalEntries));
+ CACHE_LOG_INFO((" Cache Hits = %d\n", mCacheHits));
+ CACHE_LOG_INFO((" Cache Misses = %d\n", mCacheMisses));
+ CACHE_LOG_INFO((" Cache Hit %% = %d%%\n", hitPercentage));
+ CACHE_LOG_INFO((" Max Key Length = %d\n", mMaxKeyLength));
+ CACHE_LOG_INFO((" Max Meta Size = %d\n", mMaxMetaSize));
+ CACHE_LOG_INFO((" Max Data Size = %d\n", mMaxDataSize));
+ CACHE_LOG_INFO(("\n"));
+ CACHE_LOG_INFO(
+ (" Deactivate Failures = %d\n", mDeactivateFailures));
+ CACHE_LOG_INFO(
+ (" Deactivated Unbound Entries = %d\n", mDeactivatedUnboundEntries));
+}
+
+void nsCacheService::MoveOrRemoveDiskCache(nsIFile* aOldCacheDir,
+ nsIFile* aNewCacheDir,
+ const char* aCacheSubdir) {
+ bool same;
+ if (NS_FAILED(aOldCacheDir->Equals(aNewCacheDir, &same)) || same) return;
+
+ nsCOMPtr<nsIFile> aOldCacheSubdir;
+ aOldCacheDir->Clone(getter_AddRefs(aOldCacheSubdir));
+
+ nsresult rv = aOldCacheSubdir->AppendNative(nsDependentCString(aCacheSubdir));
+ if (NS_FAILED(rv)) return;
+
+ bool exists;
+ if (NS_FAILED(aOldCacheSubdir->Exists(&exists)) || !exists) return;
+
+ nsCOMPtr<nsIFile> aNewCacheSubdir;
+ aNewCacheDir->Clone(getter_AddRefs(aNewCacheSubdir));
+
+ rv = aNewCacheSubdir->AppendNative(nsDependentCString(aCacheSubdir));
+ if (NS_FAILED(rv)) return;
+
+ PathString newPath = aNewCacheSubdir->NativePath();
+
+ if (NS_SUCCEEDED(aNewCacheSubdir->Exists(&exists)) && !exists) {
+ // New cache directory does not exist, try to move the old one here
+ // rename needs an empty target directory
+
+ // Make sure the parent of the target sub-dir exists
+ rv = aNewCacheDir->Create(nsIFile::DIRECTORY_TYPE, 0777);
+ if (NS_SUCCEEDED(rv) || NS_ERROR_FILE_ALREADY_EXISTS == rv) {
+ PathString oldPath = aOldCacheSubdir->NativePath();
+#ifdef XP_WIN
+ if (MoveFileW(oldPath.get(), newPath.get()))
+#else
+ if (rename(oldPath.get(), newPath.get()) == 0)
+#endif
+ {
+ return;
+ }
+ }
+ }
+
+ // Delay delete by 1 minute to avoid IO thrash on startup.
+ nsDeleteDir::DeleteDir(aOldCacheSubdir, false, 60000);
+}
+
+static bool IsEntryPrivate(nsCacheEntry* entry) { return entry->IsPrivate(); }
+
+void nsCacheService::LeavePrivateBrowsing() {
+ nsCacheServiceAutoLock lock;
+
+ gService->DoomActiveEntries(IsEntryPrivate);
+}
diff --git a/netwerk/cache/nsCacheService.h b/netwerk/cache/nsCacheService.h
new file mode 100644
index 0000000000..2f9ca28af0
--- /dev/null
+++ b/netwerk/cache/nsCacheService.h
@@ -0,0 +1,331 @@
+/* -*- 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 _nsCacheService_h_
+#define _nsCacheService_h_
+
+#include "nsICacheService.h"
+#include "nsCacheSession.h"
+#include "nsCacheDevice.h"
+#include "nsCacheEntry.h"
+#include "nsThreadUtils.h"
+#include "nsICacheListener.h"
+
+#include "prthread.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/CondVar.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Telemetry.h"
+
+class nsCacheRequest;
+class nsCacheProfilePrefObserver;
+class nsOfflineCacheDevice;
+class nsCacheServiceAutoLock;
+class nsITimer;
+class mozIStorageService;
+
+/******************************************************************************
+ * nsNotifyDoomListener
+ *****************************************************************************/
+
+class nsNotifyDoomListener : public mozilla::Runnable {
+ public:
+ nsNotifyDoomListener(nsICacheListener* listener, nsresult status)
+ : mozilla::Runnable("nsNotifyDoomListener"),
+ mListener(listener) // transfers reference
+ ,
+ mStatus(status) {}
+
+ NS_IMETHOD Run() override {
+ mListener->OnCacheEntryDoomed(mStatus);
+ NS_RELEASE(mListener);
+ return NS_OK;
+ }
+
+ private:
+ nsICacheListener* mListener;
+ nsresult mStatus;
+};
+
+/******************************************************************************
+ * nsCacheService
+ ******************************************************************************/
+
+class nsCacheService final : public nsICacheServiceInternal {
+ virtual ~nsCacheService();
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHESERVICE
+ NS_DECL_NSICACHESERVICEINTERNAL
+
+ nsCacheService();
+
+ // Define a Create method to be used with a factory:
+ static nsresult Create(nsISupports* outer, const nsIID& iid, void** result);
+
+ /**
+ * Methods called by nsCacheSession
+ */
+ static nsresult OpenCacheEntry(nsCacheSession* session, const nsACString& key,
+ nsCacheAccessMode accessRequested,
+ bool blockingMode, nsICacheListener* listener,
+ nsICacheEntryDescriptor** result);
+
+ static nsresult EvictEntriesForSession(nsCacheSession* session);
+
+ static nsresult IsStorageEnabledForPolicy(nsCacheStoragePolicy storagePolicy,
+ bool* result);
+
+ static nsresult DoomEntry(nsCacheSession* session, const nsACString& key,
+ nsICacheListener* listener);
+
+ /**
+ * Methods called by nsCacheEntryDescriptor
+ */
+
+ static void CloseDescriptor(nsCacheEntryDescriptor* descriptor);
+
+ static nsresult GetFileForEntry(nsCacheEntry* entry, nsIFile** result);
+
+ static nsresult OpenInputStreamForEntry(nsCacheEntry* entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream** result);
+
+ static nsresult OpenOutputStreamForEntry(nsCacheEntry* entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream** result);
+
+ static nsresult OnDataSizeChange(nsCacheEntry* entry, int32_t deltaSize);
+
+ static nsresult SetCacheElement(nsCacheEntry* entry, nsISupports* element);
+
+ static nsresult ValidateEntry(nsCacheEntry* entry);
+
+ static int32_t CacheCompressionLevel();
+
+ static bool GetClearingEntries();
+
+ static void GetCacheBaseDirectoty(nsIFile** result);
+ static void GetDiskCacheDirectory(nsIFile** result);
+ static void GetAppCacheDirectory(nsIFile** result);
+
+ /**
+ * Methods called by any cache classes
+ */
+
+ static nsCacheService* GlobalInstance() { return gService; }
+
+ static nsresult DoomEntry(nsCacheEntry* entry);
+
+ static bool IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy policy);
+
+ /**
+ * Methods called by nsApplicationCacheService
+ */
+
+ nsresult GetOfflineDevice(nsOfflineCacheDevice** aDevice);
+
+ /**
+ * Creates an offline cache device that works over a specific profile
+ * directory. A tool to preload offline cache for profiles different from the
+ * current application's profile directory.
+ */
+ nsresult GetCustomOfflineDevice(nsIFile* aProfileDir, int32_t aQuota,
+ nsOfflineCacheDevice** aDevice);
+
+ // This method may be called to release an object while the cache service
+ // lock is being held. If a non-null target is specified and the target
+ // does not correspond to the current thread, then the release will be
+ // proxied to the specified target. Otherwise, the object will be added to
+ // the list of objects to be released when the cache service is unlocked.
+ static void ReleaseObject_Locked(nsISupports* object,
+ nsIEventTarget* target = nullptr);
+
+ static nsresult DispatchToCacheIOThread(nsIRunnable* event);
+
+ // Calling this method will block the calling thread until all pending
+ // events on the cache-io thread has finished. The calling thread must
+ // hold the cache-lock
+ static nsresult SyncWithCacheIOThread();
+
+ /**
+ * Methods called by nsCacheProfilePrefObserver
+ */
+ static void OnProfileShutdown();
+ static void OnProfileChanged();
+
+ static void SetOfflineCacheEnabled(bool enabled);
+ // Sets the offline cache capacity (in kilobytes)
+ static void SetOfflineCacheCapacity(int32_t capacity);
+
+ static void MoveOrRemoveDiskCache(nsIFile* aOldCacheDir,
+ nsIFile* aNewCacheDir,
+ const char* aCacheSubdir);
+
+ nsresult Init();
+ void Shutdown();
+
+ static bool IsInitialized() {
+ if (!gService) {
+ return false;
+ }
+ return gService->mInitialized;
+ }
+
+ static void AssertOwnsLock() { gService->mLock.AssertCurrentThreadOwns(); }
+
+ static void LeavePrivateBrowsing();
+ bool IsDoomListEmpty();
+
+ typedef bool (*DoomCheckFn)(nsCacheEntry* entry);
+
+ // Accessors to the disabled functionality
+ nsresult CreateSessionInternal(const char* clientID,
+ nsCacheStoragePolicy storagePolicy,
+ bool streamBased, nsICacheSession** result);
+ nsresult VisitEntriesInternal(nsICacheVisitor* visitor);
+ nsresult EvictEntriesInternal(nsCacheStoragePolicy storagePolicy);
+
+ private:
+ friend class nsCacheServiceAutoLock;
+ friend class nsOfflineCacheDevice;
+ friend class nsProcessRequestEvent;
+ friend class nsBlockOnCacheThreadEvent;
+ friend class nsDoomEvent;
+ friend class nsAsyncDoomEvent;
+ friend class nsCacheEntryDescriptor;
+
+ /**
+ * Internal Methods
+ */
+
+ static void Lock();
+ static void Lock(::mozilla::Telemetry::HistogramID mainThreadLockerID);
+ static void Unlock();
+ void LockAcquired();
+ void LockReleased();
+
+ nsresult CreateDiskDevice();
+ nsresult CreateOfflineDevice();
+ nsresult CreateCustomOfflineDevice(nsIFile* aProfileDir, int32_t aQuota,
+ nsOfflineCacheDevice** aDevice);
+ nsresult CreateMemoryDevice();
+
+ nsresult RemoveCustomOfflineDevice(nsOfflineCacheDevice* aDevice);
+
+ nsresult CreateRequest(nsCacheSession* session, const nsACString& clientKey,
+ nsCacheAccessMode accessRequested, bool blockingMode,
+ nsICacheListener* listener, nsCacheRequest** request);
+
+ nsresult DoomEntry_Internal(nsCacheEntry* entry,
+ bool doProcessPendingRequests);
+
+ nsresult EvictEntriesForClient(const char* clientID,
+ nsCacheStoragePolicy storagePolicy);
+
+ // Notifies request listener asynchronously on the request's thread, and
+ // releases the descriptor on the request's thread. If this method fails,
+ // the descriptor is not released.
+ nsresult NotifyListener(nsCacheRequest* request,
+ nsICacheEntryDescriptor* descriptor,
+ nsCacheAccessMode accessGranted, nsresult error);
+
+ nsresult ActivateEntry(nsCacheRequest* request, nsCacheEntry** entry,
+ nsCacheEntry** doomedEntry);
+
+ nsCacheDevice* EnsureEntryHasDevice(nsCacheEntry* entry);
+
+ nsCacheEntry* SearchCacheDevices(nsCString* key, nsCacheStoragePolicy policy,
+ bool* collision);
+
+ void DeactivateEntry(nsCacheEntry* entry);
+
+ nsresult ProcessRequest(nsCacheRequest* request,
+ bool calledFromOpenCacheEntry,
+ nsICacheEntryDescriptor** result);
+
+ nsresult ProcessPendingRequests(nsCacheEntry* entry);
+
+ void ClearDoomList(void);
+ void DoomActiveEntries(DoomCheckFn check);
+ void CloseAllStreams();
+ void FireClearNetworkCacheStoredAnywhereNotification();
+
+ void LogCacheStatistics();
+
+ /**
+ * Data Members
+ */
+
+ static nsCacheService* gService; // there can be only one...
+
+ nsCOMPtr<mozIStorageService> mStorageService;
+
+ nsCacheProfilePrefObserver* mObserver;
+
+ mozilla::Mutex mLock;
+ mozilla::CondVar mCondVar;
+ bool mNotified;
+
+ mozilla::Mutex mTimeStampLock;
+ mozilla::TimeStamp mLockAcquiredTimeStamp;
+
+ nsCOMPtr<nsIThread> mCacheIOThread;
+
+ nsTArray<nsISupports*> mDoomedObjects;
+
+ bool mInitialized;
+ bool mClearingEntries;
+
+ bool mEnableOfflineDevice;
+
+ nsOfflineCacheDevice* mOfflineDevice;
+
+ nsRefPtrHashtable<nsStringHashKey, nsOfflineCacheDevice>
+ mCustomOfflineDevices;
+
+ nsCacheEntryHashTable mActiveEntries;
+ PRCList mDoomedEntries;
+
+ // stats
+
+ uint32_t mTotalEntries;
+ uint32_t mCacheHits;
+ uint32_t mCacheMisses;
+ uint32_t mMaxKeyLength;
+ uint32_t mMaxDataSize;
+ uint32_t mMaxMetaSize;
+
+ // Unexpected error totals
+ uint32_t mDeactivateFailures;
+ uint32_t mDeactivatedUnboundEntries;
+};
+
+/******************************************************************************
+ * nsCacheServiceAutoLock
+ ******************************************************************************/
+
+#define LOCK_TELEM(x) \
+ (::mozilla::Telemetry::CACHE_SERVICE_LOCK_WAIT_MAINTHREAD_##x)
+
+// Instantiate this class to acquire the cache service lock for a particular
+// execution scope.
+class nsCacheServiceAutoLock {
+ public:
+ nsCacheServiceAutoLock() { nsCacheService::Lock(); }
+ explicit nsCacheServiceAutoLock(
+ mozilla::Telemetry::HistogramID mainThreadLockerID) {
+ nsCacheService::Lock(mainThreadLockerID);
+ }
+ ~nsCacheServiceAutoLock() { nsCacheService::Unlock(); }
+};
+
+#endif // _nsCacheService_h_
diff --git a/netwerk/cache/nsCacheSession.cpp b/netwerk/cache/nsCacheSession.cpp
new file mode 100644
index 0000000000..3e4a1f92da
--- /dev/null
+++ b/netwerk/cache/nsCacheSession.cpp
@@ -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/. */
+
+#include "nsCacheSession.h"
+#include "nsCacheService.h"
+#include "nsCRT.h"
+#include "nsThreadUtils.h"
+
+NS_IMPL_ISUPPORTS(nsCacheSession, nsICacheSession)
+
+nsCacheSession::nsCacheSession(const char* clientID,
+ nsCacheStoragePolicy storagePolicy,
+ bool streamBased)
+ : mClientID(clientID), mInfo(0) {
+ SetStoragePolicy(storagePolicy);
+
+ if (streamBased)
+ MarkStreamBased();
+ else
+ SetStoragePolicy(nsICache::STORE_IN_MEMORY);
+
+ MarkPublic();
+
+ MarkDoomEntriesIfExpired();
+}
+
+nsCacheSession::~nsCacheSession() {
+ /* destructor code */
+ // notify service we are going away?
+}
+
+NS_IMETHODIMP nsCacheSession::GetDoomEntriesIfExpired(bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = WillDoomEntriesIfExpired();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheSession::SetProfileDirectory(nsIFile* profileDir) {
+ if (StoragePolicy() != nsICache::STORE_OFFLINE && profileDir) {
+ // Profile directory override is currently implemented only for
+ // offline cache. This is an early failure to prevent the request
+ // being processed before it would fail later because of inability
+ // to assign a cache base dir.
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mProfileDir = profileDir;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheSession::GetProfileDirectory(nsIFile** profileDir) {
+ *profileDir = do_AddRef(mProfileDir).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheSession::SetDoomEntriesIfExpired(
+ bool doomEntriesIfExpired) {
+ if (doomEntriesIfExpired)
+ MarkDoomEntriesIfExpired();
+ else
+ ClearDoomEntriesIfExpired();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheSession::OpenCacheEntry(const nsACString& key,
+ nsCacheAccessMode accessRequested,
+ bool blockingMode,
+ nsICacheEntryDescriptor** result) {
+ nsresult rv;
+
+ if (NS_IsMainThread())
+ rv = NS_ERROR_NOT_AVAILABLE;
+ else
+ rv =
+ nsCacheService::OpenCacheEntry(this, key, accessRequested, blockingMode,
+ nullptr, // no listener
+ result);
+ return rv;
+}
+
+NS_IMETHODIMP nsCacheSession::AsyncOpenCacheEntry(
+ const nsACString& key, nsCacheAccessMode accessRequested,
+ nsICacheListener* listener, bool noWait) {
+ nsresult rv;
+ rv = nsCacheService::OpenCacheEntry(this, key, accessRequested, !noWait,
+ listener,
+ nullptr); // no result
+
+ if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) rv = NS_OK;
+ return rv;
+}
+
+NS_IMETHODIMP nsCacheSession::EvictEntries() {
+ return nsCacheService::EvictEntriesForSession(this);
+}
+
+NS_IMETHODIMP nsCacheSession::IsStorageEnabled(bool* result) {
+ return nsCacheService::IsStorageEnabledForPolicy(StoragePolicy(), result);
+}
+
+NS_IMETHODIMP nsCacheSession::DoomEntry(const nsACString& key,
+ nsICacheListener* listener) {
+ return nsCacheService::DoomEntry(this, key, listener);
+}
+
+NS_IMETHODIMP nsCacheSession::GetIsPrivate(bool* aPrivate) {
+ *aPrivate = IsPrivate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheSession::SetIsPrivate(bool aPrivate) {
+ if (aPrivate)
+ MarkPrivate();
+ else
+ MarkPublic();
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsCacheSession.h b/netwerk/cache/nsCacheSession.h
new file mode 100644
index 0000000000..59bb748231
--- /dev/null
+++ b/netwerk/cache/nsCacheSession.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsCacheSession_h_
+#define _nsCacheSession_h_
+
+#include "nspr.h"
+#include "nsError.h"
+#include "nsCOMPtr.h"
+#include "nsICacheSession.h"
+#include "nsIFile.h"
+#include "nsString.h"
+
+class nsCacheSession : public nsICacheSession {
+ virtual ~nsCacheSession();
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHESESSION
+
+ nsCacheSession(const char* clientID, nsCacheStoragePolicy storagePolicy,
+ bool streamBased);
+
+ nsCString* ClientID() { return &mClientID; }
+
+ enum SessionInfo {
+ eStoragePolicyMask = 0x000000FF,
+ eStreamBasedMask = 0x00000100,
+ eDoomEntriesIfExpiredMask = 0x00001000,
+ ePrivateMask = 0x00010000
+ };
+
+ void MarkStreamBased() { mInfo |= eStreamBasedMask; }
+ void ClearStreamBased() { mInfo &= ~eStreamBasedMask; }
+ bool IsStreamBased() { return (mInfo & eStreamBasedMask) != 0; }
+
+ void MarkDoomEntriesIfExpired() { mInfo |= eDoomEntriesIfExpiredMask; }
+ void ClearDoomEntriesIfExpired() { mInfo &= ~eDoomEntriesIfExpiredMask; }
+ bool WillDoomEntriesIfExpired() {
+ return (0 != (mInfo & eDoomEntriesIfExpiredMask));
+ }
+
+ void MarkPrivate() { mInfo |= ePrivateMask; }
+ void MarkPublic() { mInfo &= ~ePrivateMask; }
+ bool IsPrivate() { return (mInfo & ePrivateMask) != 0; }
+ nsCacheStoragePolicy StoragePolicy() {
+ return (nsCacheStoragePolicy)(mInfo & eStoragePolicyMask);
+ }
+
+ void SetStoragePolicy(nsCacheStoragePolicy policy) {
+ NS_ASSERTION(policy <= 0xFF, "too many bits in nsCacheStoragePolicy");
+ mInfo &= ~eStoragePolicyMask; // clear storage policy bits
+ mInfo |= policy;
+ }
+
+ nsIFile* ProfileDir() { return mProfileDir; }
+
+ private:
+ nsCString mClientID;
+ uint32_t mInfo;
+ nsCOMPtr<nsIFile> mProfileDir;
+};
+
+#endif // _nsCacheSession_h_
diff --git a/netwerk/cache/nsCacheUtils.cpp b/netwerk/cache/nsCacheUtils.cpp
new file mode 100644
index 0000000000..88bcabce12
--- /dev/null
+++ b/netwerk/cache/nsCacheUtils.cpp
@@ -0,0 +1,78 @@
+/* -*- 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 "nsCache.h"
+#include "nsCacheUtils.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+class nsDestroyThreadEvent : public Runnable {
+ public:
+ explicit nsDestroyThreadEvent(nsIThread* thread)
+ : mozilla::Runnable("nsDestroyThreadEvent"), mThread(thread) {}
+ NS_IMETHOD Run() override {
+ mThread->Shutdown();
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+nsShutdownThread::nsShutdownThread(nsIThread* aThread)
+ : mozilla::Runnable("nsShutdownThread"),
+ mMonitor("nsShutdownThread.mMonitor"),
+ mShuttingDown(false),
+ mThread(aThread) {}
+
+nsresult nsShutdownThread::Shutdown(nsIThread* aThread) {
+ nsresult rv;
+ RefPtr<nsDestroyThreadEvent> ev = new nsDestroyThreadEvent(aThread);
+ rv = NS_DispatchToMainThread(ev);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Dispatching event in nsShutdownThread::Shutdown failed!");
+ }
+ return rv;
+}
+
+nsresult nsShutdownThread::BlockingShutdown(nsIThread* aThread) {
+ nsresult rv;
+
+ RefPtr<nsShutdownThread> st = new nsShutdownThread(aThread);
+ nsCOMPtr<nsIThread> workerThread;
+
+ rv = NS_NewNamedThread("thread shutdown", getter_AddRefs(workerThread));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't create nsShutDownThread worker thread!");
+ return rv;
+ }
+
+ {
+ MonitorAutoLock mon(st->mMonitor);
+ rv = workerThread->Dispatch(st, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "Dispatching event in nsShutdownThread::BlockingShutdown failed!");
+ } else {
+ st->mShuttingDown = true;
+ while (st->mShuttingDown) {
+ mon.Wait();
+ }
+ }
+ }
+
+ return Shutdown(workerThread);
+}
+
+NS_IMETHODIMP
+nsShutdownThread::Run() {
+ MonitorAutoLock mon(mMonitor);
+ mThread->Shutdown();
+ mShuttingDown = false;
+ mon.Notify();
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsCacheUtils.h b/netwerk/cache/nsCacheUtils.h
new file mode 100644
index 0000000000..71f3056f89
--- /dev/null
+++ b/netwerk/cache/nsCacheUtils.h
@@ -0,0 +1,44 @@
+/* -*- 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 _nsCacheUtils_h_
+#define _nsCacheUtils_h_
+
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Monitor.h"
+
+class nsIThread;
+
+/**
+ * A class with utility methods for shutting down nsIThreads easily.
+ */
+class nsShutdownThread : public mozilla::Runnable {
+ public:
+ explicit nsShutdownThread(nsIThread* aThread);
+ ~nsShutdownThread() = default;
+
+ NS_IMETHOD Run() override;
+
+ /**
+ * Shutdown ensures that aThread->Shutdown() is called on a main thread
+ */
+ static nsresult Shutdown(nsIThread* aThread);
+
+ /**
+ * BlockingShutdown ensures that by the time it returns, aThread->Shutdown()
+ * has been called and no pending events have been processed on the current
+ * thread.
+ */
+ static nsresult BlockingShutdown(nsIThread* aThread);
+
+ private:
+ mozilla::Monitor mMonitor;
+ bool mShuttingDown;
+ nsCOMPtr<nsIThread> mThread;
+};
+
+#endif
diff --git a/netwerk/cache/nsDeleteDir.cpp b/netwerk/cache/nsDeleteDir.cpp
new file mode 100644
index 0000000000..f99896b721
--- /dev/null
+++ b/netwerk/cache/nsDeleteDir.cpp
@@ -0,0 +1,362 @@
+/* -*- 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 "nsDeleteDir.h"
+#include "nsIFile.h"
+#include "nsString.h"
+#include "mozilla/Telemetry.h"
+#include "nsITimer.h"
+#include "nsThreadUtils.h"
+#include "nsISupportsPriority.h"
+#include "nsCacheUtils.h"
+#include "prtime.h"
+#include <time.h>
+
+using namespace mozilla;
+
+class nsBlockOnBackgroundThreadEvent : public Runnable {
+ public:
+ nsBlockOnBackgroundThreadEvent()
+ : mozilla::Runnable("nsBlockOnBackgroundThreadEvent") {}
+ NS_IMETHOD Run() override {
+ MutexAutoLock lock(nsDeleteDir::gInstance->mLock);
+ nsDeleteDir::gInstance->mNotified = true;
+ nsDeleteDir::gInstance->mCondVar.Notify();
+ return NS_OK;
+ }
+};
+
+nsDeleteDir* nsDeleteDir::gInstance = nullptr;
+
+nsDeleteDir::nsDeleteDir()
+ : mLock("nsDeleteDir.mLock"),
+ mCondVar(mLock, "nsDeleteDir.mCondVar"),
+ mNotified(false),
+ mShutdownPending(false),
+ mStopDeleting(false) {
+ NS_ASSERTION(gInstance == nullptr, "multiple nsCacheService instances!");
+}
+
+nsDeleteDir::~nsDeleteDir() { gInstance = nullptr; }
+
+nsresult nsDeleteDir::Init() {
+ if (gInstance) return NS_ERROR_ALREADY_INITIALIZED;
+
+ gInstance = new nsDeleteDir();
+ return NS_OK;
+}
+
+nsresult nsDeleteDir::Shutdown(bool finishDeleting) {
+ if (!gInstance) return NS_ERROR_NOT_INITIALIZED;
+
+ nsCOMArray<nsIFile> dirsToRemove;
+ nsCOMPtr<nsISerialEventTarget> eventTarget;
+ {
+ MutexAutoLock lock(gInstance->mLock);
+ NS_ASSERTION(!gInstance->mShutdownPending,
+ "Unexpected state in nsDeleteDir::Shutdown()");
+ gInstance->mShutdownPending = true;
+
+ if (!finishDeleting) gInstance->mStopDeleting = true;
+
+ // remove all pending timers
+ for (int32_t i = gInstance->mTimers.Count(); i > 0; i--) {
+ nsCOMPtr<nsITimer> timer = gInstance->mTimers[i - 1];
+ gInstance->mTimers.RemoveObjectAt(i - 1);
+
+ nsCOMArray<nsIFile>* arg;
+ timer->GetClosure((reinterpret_cast<void**>(&arg)));
+ timer->Cancel();
+
+ if (finishDeleting) dirsToRemove.AppendObjects(*arg);
+
+ // delete argument passed to the timer
+ delete arg;
+ }
+
+ eventTarget.swap(gInstance->mBackgroundET);
+ if (eventTarget) {
+ // dispatch event and wait for it to run and notify us, so we know thread
+ // has completed all work and can be shutdown
+ nsCOMPtr<nsIRunnable> event = new nsBlockOnBackgroundThreadEvent();
+ nsresult rv = eventTarget->Dispatch(event, NS_DISPATCH_EVENT_MAY_BLOCK);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed dispatching block-event");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ gInstance->mNotified = false;
+ while (!gInstance->mNotified) {
+ gInstance->mCondVar.Wait();
+ }
+ }
+ }
+
+ delete gInstance;
+
+ for (int32_t i = 0; i < dirsToRemove.Count(); i++)
+ dirsToRemove[i]->Remove(true);
+
+ return NS_OK;
+}
+
+nsresult nsDeleteDir::InitThread() {
+ if (mBackgroundET) return NS_OK;
+
+ nsresult rv = NS_CreateBackgroundTaskQueue("Cache Deleter",
+ getter_AddRefs(mBackgroundET));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Can't create background task queue");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void nsDeleteDir::DestroyThread() {
+ if (!mBackgroundET) return;
+
+ if (mTimers.Count())
+ // more work to do, so don't delete thread.
+ return;
+
+ mBackgroundET = nullptr;
+}
+
+void nsDeleteDir::TimerCallback(nsITimer* aTimer, void* arg) {
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR> timer;
+ {
+ MutexAutoLock lock(gInstance->mLock);
+
+ int32_t idx = gInstance->mTimers.IndexOf(aTimer);
+ if (idx == -1) {
+ // Timer was canceled and removed during shutdown.
+ return;
+ }
+
+ gInstance->mTimers.RemoveObjectAt(idx);
+ }
+
+ UniquePtr<nsCOMArray<nsIFile>> dirList;
+ dirList.reset(static_cast<nsCOMArray<nsIFile>*>(arg));
+
+ bool shuttingDown = false;
+
+ // Intentional extra braces to control variable sope.
+ {
+ // Low IO priority can only be set when running in the context of the
+ // current thread. So this shouldn't be moved to where we set the priority
+ // of the Cache deleter thread using the nsThread's NSPR priority constants.
+ nsAutoLowPriorityIO autoLowPriority;
+ for (int32_t i = 0; i < dirList->Count() && !shuttingDown; i++) {
+ gInstance->RemoveDir((*dirList)[i], &shuttingDown);
+ }
+ }
+
+ {
+ MutexAutoLock lock(gInstance->mLock);
+ gInstance->DestroyThread();
+ }
+}
+
+nsresult nsDeleteDir::DeleteDir(nsIFile* dirIn, bool moveToTrash,
+ uint32_t delay) {
+ Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_TRASHRENAME> timer;
+
+ if (!gInstance) return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> trash, dir;
+
+ // Need to make a clone of this since we don't want to modify the input
+ // file object.
+ rv = dirIn->Clone(getter_AddRefs(dir));
+ if (NS_FAILED(rv)) return rv;
+
+ if (moveToTrash) {
+ rv = GetTrashDir(dir, &trash);
+ if (NS_FAILED(rv)) return rv;
+ nsAutoCString origLeaf;
+ rv = trash->GetNativeLeafName(origLeaf);
+ if (NS_FAILED(rv)) return rv;
+
+ // Append random number to the trash directory and check if it exists.
+ srand(static_cast<unsigned>(PR_Now()));
+ nsAutoCString leaf;
+ for (int32_t i = 0; i < 10; i++) {
+ leaf = origLeaf;
+ leaf.AppendInt(rand());
+ rv = trash->SetNativeLeafName(leaf);
+ if (NS_FAILED(rv)) return rv;
+
+ bool exists;
+ if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) {
+ break;
+ }
+
+ leaf.Truncate();
+ }
+
+ // Fail if we didn't find unused trash directory within the limit
+ if (!leaf.Length()) return NS_ERROR_FAILURE;
+
+#if defined(MOZ_WIDGET_ANDROID)
+ nsCOMPtr<nsIFile> parent;
+ rv = trash->GetParent(getter_AddRefs(parent));
+ if (NS_FAILED(rv)) return rv;
+ rv = dir->MoveToNative(parent, leaf);
+#else
+ // Important: must rename directory w/o changing parent directory: else on
+ // NTFS we'll wait (with cache lock) while nsIFile's ACL reset walks file
+ // tree: was hanging GUI for *minutes* on large cache dirs.
+ rv = dir->MoveToNative(nullptr, leaf);
+#endif
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ // we want to pass a clone of the original off to the worker thread.
+ trash.swap(dir);
+ }
+
+ UniquePtr<nsCOMArray<nsIFile>> arg(new nsCOMArray<nsIFile>);
+ arg->AppendObject(trash);
+
+ rv = gInstance->PostTimer(arg.get(), delay);
+ if (NS_FAILED(rv)) return rv;
+
+ Unused << arg.release();
+ return NS_OK;
+}
+
+nsresult nsDeleteDir::GetTrashDir(nsIFile* target, nsCOMPtr<nsIFile>* result) {
+ nsresult rv;
+#if defined(MOZ_WIDGET_ANDROID)
+ // Try to use the app cache folder for cache trash on Android
+ char* cachePath = getenv("CACHE_DIRECTORY");
+ if (cachePath) {
+ rv = NS_NewNativeLocalFile(nsDependentCString(cachePath), true,
+ getter_AddRefs(*result));
+ if (NS_FAILED(rv)) return rv;
+
+ // Add a sub folder with the cache folder name
+ nsAutoCString leaf;
+ rv = target->GetNativeLeafName(leaf);
+ (*result)->AppendNative(leaf);
+ } else
+#endif
+ {
+ rv = target->Clone(getter_AddRefs(*result));
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString leaf;
+ rv = (*result)->GetNativeLeafName(leaf);
+ if (NS_FAILED(rv)) return rv;
+ leaf.AppendLiteral(".Trash");
+
+ return (*result)->SetNativeLeafName(leaf);
+}
+
+nsresult nsDeleteDir::RemoveOldTrashes(nsIFile* cacheDir) {
+ if (!gInstance) return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> trash;
+ rv = GetTrashDir(cacheDir, &trash);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoString trashName;
+ rv = trash->GetLeafName(trashName);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> parent;
+#if defined(MOZ_WIDGET_ANDROID)
+ rv = trash->GetParent(getter_AddRefs(parent));
+#else
+ rv = cacheDir->GetParent(getter_AddRefs(parent));
+#endif
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIDirectoryEnumerator> iter;
+ rv = parent->GetDirectoryEntries(getter_AddRefs(iter));
+ if (NS_FAILED(rv)) return rv;
+
+ UniquePtr<nsCOMArray<nsIFile>> dirList;
+
+ nsCOMPtr<nsIFile> file;
+ while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(file))) && file) {
+ nsAutoString leafName;
+ rv = file->GetLeafName(leafName);
+ if (NS_FAILED(rv)) continue;
+
+ // match all names that begin with the trash name (i.e. "Cache.Trash")
+ if (Substring(leafName, 0, trashName.Length()).Equals(trashName)) {
+ if (!dirList) dirList = MakeUnique<nsCOMArray<nsIFile>>();
+ dirList->AppendObject(file);
+ }
+ }
+
+ if (dirList) {
+ rv = gInstance->PostTimer(dirList.get(), 90000);
+ if (NS_FAILED(rv)) return rv;
+
+ Unused << dirList.release();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDeleteDir::PostTimer(void* arg, uint32_t delay) {
+ nsresult rv;
+
+ MutexAutoLock lock(mLock);
+
+ rv = InitThread();
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsITimer> timer;
+ rv = NS_NewTimerWithFuncCallback(getter_AddRefs(timer), TimerCallback, arg,
+ delay, nsITimer::TYPE_ONE_SHOT,
+ "nsDeleteDir::PostTimer", mBackgroundET);
+ if (NS_FAILED(rv)) return rv;
+
+ mTimers.AppendObject(timer);
+ return NS_OK;
+}
+
+nsresult nsDeleteDir::RemoveDir(nsIFile* file, bool* stopDeleting) {
+ nsresult rv;
+ bool isLink;
+
+ rv = file->IsSymlink(&isLink);
+ if (NS_FAILED(rv) || isLink) return NS_ERROR_UNEXPECTED;
+
+ bool isDir;
+ rv = file->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) return rv;
+
+ if (isDir) {
+ nsCOMPtr<nsIDirectoryEnumerator> iter;
+ rv = file->GetDirectoryEntries(getter_AddRefs(iter));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> file2;
+ while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(file2))) && file2) {
+ RemoveDir(file2, stopDeleting);
+ // No check for errors to remove as much as possible
+
+ if (*stopDeleting) return NS_OK;
+ }
+ }
+
+ file->Remove(false);
+ // No check for errors to remove as much as possible
+
+ MutexAutoLock lock(mLock);
+ if (mStopDeleting) *stopDeleting = true;
+
+ return NS_OK;
+}
diff --git a/netwerk/cache/nsDeleteDir.h b/netwerk/cache/nsDeleteDir.h
new file mode 100644
index 0000000000..cf6de26dde
--- /dev/null
+++ b/netwerk/cache/nsDeleteDir.h
@@ -0,0 +1,79 @@
+/* -*- 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 nsDeleteDir_h__
+#define nsDeleteDir_h__
+
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/CondVar.h"
+
+class nsIFile;
+class nsISerialEventTarget;
+class nsITimer;
+
+class nsDeleteDir {
+ public:
+ nsDeleteDir();
+ ~nsDeleteDir();
+
+ static nsresult Init();
+ static nsresult Shutdown(bool finishDeleting);
+
+ /**
+ * This routine attempts to delete a directory that may contain some files
+ * that are still in use. This latter point is only an issue on Windows and
+ * a few other systems.
+ *
+ * If the moveToTrash parameter is true we first rename the given directory
+ * "foo.Trash123" (where "foo" is the original directory name, and "123" is
+ * a random number, in order to support multiple concurrent deletes). The
+ * directory is then deleted, file-by-file, on a background thread.
+ *
+ * If the moveToTrash parameter is false, then the given directory is deleted
+ * directly.
+ *
+ * If 'delay' is non-zero, the directory will not be deleted until the
+ * specified number of milliseconds have passed. (The directory is still
+ * renamed immediately if 'moveToTrash' is passed, so upon return it is safe
+ * to create a directory with the same name).
+ */
+ static nsresult DeleteDir(nsIFile* dir, bool moveToTrash, uint32_t delay = 0);
+
+ /**
+ * Returns the trash directory corresponding to the given directory.
+ */
+ static nsresult GetTrashDir(nsIFile* dir, nsCOMPtr<nsIFile>* result);
+
+ /**
+ * Remove all trashes left from previous run. This function does nothing when
+ * called second and more times.
+ */
+ static nsresult RemoveOldTrashes(nsIFile* cacheDir);
+
+ static void TimerCallback(nsITimer* aTimer, void* arg);
+
+ private:
+ friend class nsBlockOnBackgroundThreadEvent;
+ friend class nsDestroyThreadEvent;
+
+ nsresult InitThread();
+ void DestroyThread();
+ nsresult PostTimer(void* arg, uint32_t delay);
+ nsresult RemoveDir(nsIFile* file, bool* stopDeleting);
+
+ static nsDeleteDir* gInstance;
+ mozilla::Mutex mLock;
+ mozilla::CondVar mCondVar;
+ bool mNotified;
+ nsCOMArray<nsITimer> mTimers;
+ nsCOMPtr<nsISerialEventTarget> mBackgroundET;
+ bool mShutdownPending;
+ bool mStopDeleting;
+};
+
+#endif // nsDeleteDir_h__
diff --git a/netwerk/cache/nsDiskCache.h b/netwerk/cache/nsDiskCache.h
new file mode 100644
index 0000000000..e01860b7bf
--- /dev/null
+++ b/netwerk/cache/nsDiskCache.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 _nsDiskCache_h_
+#define _nsDiskCache_h_
+
+#include "nsCacheEntry.h"
+
+#ifdef XP_WIN
+# include <winsock.h> // for htonl/ntohl
+#endif
+
+class nsDiskCache {
+ public:
+ enum {
+ kCurrentVersion =
+ 0x00010013 // format = 16 bits major version/16 bits minor version
+ };
+
+ enum { kData, kMetaData };
+
+ // Stores the reason why the cache is corrupt.
+ // Note: I'm only listing the enum values explicitly for easy mapping when
+ // looking at telemetry data.
+ enum CorruptCacheInfo {
+ kNotCorrupt = 0,
+ kInvalidArgPointer = 1,
+ kUnexpectedError = 2,
+ kOpenCacheMapError = 3,
+ kBlockFilesShouldNotExist = 4,
+ kOutOfMemory = 5,
+ kCreateCacheSubdirectories = 6,
+ kBlockFilesShouldExist = 7,
+ kHeaderSizeNotRead = 8,
+ kHeaderIsDirty = 9,
+ kVersionMismatch = 10,
+ kRecordsIncomplete = 11,
+ kHeaderIncomplete = 12,
+ kNotEnoughToRead = 13,
+ kEntryCountIncorrect = 14,
+ kCouldNotGetBlockFileForIndex = 15,
+ kCouldNotCreateBlockFile = 16,
+ kBlockFileSizeError = 17,
+ kBlockFileBitMapWriteError = 18,
+ kBlockFileSizeLessThanBitMap = 19,
+ kBlockFileBitMapReadError = 20,
+ kBlockFileEstimatedSizeError = 21,
+ kFlushHeaderError = 22,
+ kCacheCleanFilePathError = 23,
+ kCacheCleanOpenFileError = 24,
+ kCacheCleanTimerError = 25
+ };
+
+ // Parameter initval initializes internal state of hash function. Hash values
+ // are different for the same text when different initval is used. It can be
+ // any random number.
+ //
+ // It can be used for generating 64-bit hash value:
+ // (uint64_t(Hash(key, initval1)) << 32) | Hash(key, initval2)
+ //
+ // It can be also used to hash multiple strings:
+ // h = Hash(string1, 0);
+ // h = Hash(string2, h);
+ // ...
+ static PLDHashNumber Hash(const char* key, PLDHashNumber initval = 0);
+ static nsresult Truncate(PRFileDesc* fd, uint32_t newEOF);
+};
+
+#endif // _nsDiskCache_h_
diff --git a/netwerk/cache/nsDiskCacheDeviceSQL.cpp b/netwerk/cache/nsDiskCacheDeviceSQL.cpp
new file mode 100644
index 0000000000..b2523a4d3f
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.cpp
@@ -0,0 +1,2608 @@
+/* -*- Mode: C++; 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 <inttypes.h>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/ThreadLocal.h"
+
+#include "nsCache.h"
+#include "nsDiskCache.h"
+#include "nsDiskCacheDeviceSQL.h"
+#include "nsCacheService.h"
+#include "nsApplicationCache.h"
+#include "../cache2/CacheHashUtils.h"
+
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIURI.h"
+#include "nsEscape.h"
+#include "nsIPrefBranch.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "nsCRT.h"
+#include "nsArrayUtils.h"
+#include "nsIArray.h"
+#include "nsIVariant.h"
+#include "nsILoadContextInfo.h"
+#include "nsThreadUtils.h"
+#include "nsISerializable.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsSerializationHelper.h"
+#include "nsMemory.h"
+
+#include "mozIStorageService.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageFunction.h"
+#include "mozStorageHelper.h"
+
+#include "nsICacheVisitor.h"
+#include "nsISeekableStream.h"
+
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/storage.h"
+#include "nsVariant.h"
+#include "mozilla/BasePrincipal.h"
+
+using namespace mozilla;
+using namespace mozilla::storage;
+using mozilla::OriginAttributes;
+
+static const char OFFLINE_CACHE_DEVICE_ID[] = {"offline"};
+
+#define LOG(args) CACHE_LOG_DEBUG(args)
+
+static uint32_t gNextTemporaryClientID = 0;
+
+/*****************************************************************************
+ * helpers
+ */
+
+static nsresult EnsureDir(nsIFile* dir) {
+ bool exists;
+ nsresult rv = dir->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ return rv;
+}
+
+static bool DecomposeCacheEntryKey(const nsCString* fullKey, const char** cid,
+ const char** key, nsCString& buf) {
+ buf = *fullKey;
+
+ int32_t colon = buf.FindChar(':');
+ if (colon == kNotFound) {
+ NS_ERROR("Invalid key");
+ return false;
+ }
+ buf.SetCharAt('\0', colon);
+
+ *cid = buf.get();
+ *key = buf.get() + colon + 1;
+
+ return true;
+}
+
+class AutoResetStatement {
+ public:
+ explicit AutoResetStatement(mozIStorageStatement* s) : mStatement(s) {}
+ ~AutoResetStatement() { mStatement->Reset(); }
+ mozIStorageStatement* operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN {
+ return mStatement;
+ }
+
+ private:
+ mozIStorageStatement* mStatement;
+};
+
+class EvictionObserver {
+ public:
+ EvictionObserver(mozIStorageConnection* db,
+ nsOfflineCacheEvictionFunction* evictionFunction)
+ : mDB(db), mEvictionFunction(evictionFunction) {
+ mEvictionFunction->Init();
+ mDB->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE TEMP TRIGGER cache_on_delete BEFORE DELETE"
+ " ON moz_cache FOR EACH ROW BEGIN SELECT"
+ " cache_eviction_observer("
+ " OLD.ClientID, OLD.key, OLD.generation);"
+ " END;"));
+ }
+
+ ~EvictionObserver() {
+ mDB->ExecuteSimpleSQL("DROP TRIGGER cache_on_delete;"_ns);
+ mEvictionFunction->Reset();
+ }
+
+ void Apply() { return mEvictionFunction->Apply(); }
+
+ private:
+ mozIStorageConnection* mDB;
+ RefPtr<nsOfflineCacheEvictionFunction> mEvictionFunction;
+};
+
+#define DCACHE_HASH_MAX INT64_MAX
+#define DCACHE_HASH_BITS 64
+
+/**
+ * nsOfflineCache::Hash(const char * key)
+ *
+ * This algorithm of this method implies nsOfflineCacheRecords will be stored
+ * in a certain order on disk. If the algorithm changes, existing cache
+ * map files may become invalid, and therefore the kCurrentVersion needs
+ * to be revised.
+ */
+static uint64_t DCacheHash(const char* key) {
+ // initval 0x7416f295 was chosen randomly
+ return (uint64_t(CacheHash::Hash(key, strlen(key), 0)) << 32) |
+ CacheHash::Hash(key, strlen(key), 0x7416f295);
+}
+
+/******************************************************************************
+ * nsOfflineCacheEvictionFunction
+ */
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheEvictionFunction, mozIStorageFunction)
+
+// helper function for directly exposing the same data file binding
+// path algorithm used in nsOfflineCacheBinding::Create
+static nsresult GetCacheDataFile(nsIFile* cacheDir, const char* key,
+ int generation, nsCOMPtr<nsIFile>& file) {
+ cacheDir->Clone(getter_AddRefs(file));
+ if (!file) return NS_ERROR_OUT_OF_MEMORY;
+
+ uint64_t hash = DCacheHash(key);
+
+ uint32_t dir1 = (uint32_t)(hash & 0x0F);
+ uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4);
+
+ hash >>= 8;
+
+ file->AppendNative(nsPrintfCString("%X", dir1));
+ file->AppendNative(nsPrintfCString("%X", dir2));
+
+ char leaf[64];
+ SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
+ return file->AppendNative(nsDependentCString(leaf));
+}
+
+namespace appcachedetail {
+
+typedef nsCOMArray<nsIFile> FileArray;
+static MOZ_THREAD_LOCAL(FileArray*) tlsEvictionItems;
+
+} // namespace appcachedetail
+
+NS_IMETHODIMP
+nsOfflineCacheEvictionFunction::OnFunctionCall(mozIStorageValueArray* values,
+ nsIVariant** _retval) {
+ LOG(("nsOfflineCacheEvictionFunction::OnFunctionCall\n"));
+
+ *_retval = nullptr;
+
+ uint32_t numEntries;
+ nsresult rv = values->GetNumEntries(&numEntries);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(numEntries == 3, "unexpected number of arguments");
+
+ uint32_t valueLen;
+ const char* clientID = values->AsSharedUTF8String(0, &valueLen);
+ const char* key = values->AsSharedUTF8String(1, &valueLen);
+ nsAutoCString fullKey(clientID);
+ fullKey.Append(':');
+ fullKey.Append(key);
+ int generation = values->AsInt32(2);
+
+ // If the key is currently locked, refuse to delete this row.
+ if (mDevice->IsLocked(fullKey)) {
+ // This code thought it was performing the equivalent of invoking the SQL
+ // "RAISE(IGNORE)" function. It was not. Please see bug 1470961 and any
+ // follow-ups to understand the plan for correcting this bug.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = GetCacheDataFile(mDevice->CacheDirectory(), key, generation, file);
+ if (NS_FAILED(rv)) {
+ LOG(("GetCacheDataFile [key=%s generation=%d] failed [rv=%" PRIx32 "]!\n",
+ key, generation, static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ appcachedetail::FileArray* items = appcachedetail::tlsEvictionItems.get();
+ MOZ_ASSERT(items);
+ if (items) {
+ items->AppendObject(file);
+ }
+
+ return NS_OK;
+}
+
+nsOfflineCacheEvictionFunction::nsOfflineCacheEvictionFunction(
+ nsOfflineCacheDevice* device)
+ : mDevice(device) {
+ mTLSInited = appcachedetail::tlsEvictionItems.init();
+}
+
+void nsOfflineCacheEvictionFunction::Init() {
+ if (mTLSInited) {
+ appcachedetail::tlsEvictionItems.set(new appcachedetail::FileArray());
+ }
+}
+
+void nsOfflineCacheEvictionFunction::Reset() {
+ if (!mTLSInited) {
+ return;
+ }
+
+ appcachedetail::FileArray* items = appcachedetail::tlsEvictionItems.get();
+ if (!items) {
+ return;
+ }
+
+ appcachedetail::tlsEvictionItems.set(nullptr);
+ delete items;
+}
+
+void nsOfflineCacheEvictionFunction::Apply() {
+ LOG(("nsOfflineCacheEvictionFunction::Apply\n"));
+
+ if (!mTLSInited) {
+ return;
+ }
+
+ appcachedetail::FileArray* pitems = appcachedetail::tlsEvictionItems.get();
+ if (!pitems) {
+ return;
+ }
+
+ appcachedetail::FileArray items = std::move(*pitems);
+
+ for (int32_t i = 0; i < items.Count(); i++) {
+ if (MOZ_LOG_TEST(gCacheLog, LogLevel::Debug)) {
+ LOG((" removing %s\n", items[i]->HumanReadablePath().get()));
+ }
+
+ items[i]->Remove(false);
+ }
+}
+
+class nsOfflineCacheDiscardCache : public Runnable {
+ public:
+ nsOfflineCacheDiscardCache(nsOfflineCacheDevice* device, nsCString& group,
+ nsCString& clientID)
+ : mozilla::Runnable("nsOfflineCacheDiscardCache"),
+ mDevice(device),
+ mGroup(group),
+ mClientID(clientID) {}
+
+ NS_IMETHOD Run() override {
+ if (mDevice->IsActiveCache(mGroup, mClientID)) {
+ mDevice->DeactivateGroup(mGroup);
+ }
+
+ return mDevice->EvictEntries(mClientID.get());
+ }
+
+ private:
+ RefPtr<nsOfflineCacheDevice> mDevice;
+ nsCString mGroup;
+ nsCString mClientID;
+};
+
+/******************************************************************************
+ * nsOfflineCacheDeviceInfo
+ */
+
+class nsOfflineCacheDeviceInfo final : public nsICacheDeviceInfo {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEDEVICEINFO
+
+ explicit nsOfflineCacheDeviceInfo(nsOfflineCacheDevice* device)
+ : mDevice(device) {}
+
+ private:
+ ~nsOfflineCacheDeviceInfo() = default;
+
+ nsOfflineCacheDevice* mDevice;
+};
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheDeviceInfo, nsICacheDeviceInfo)
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetDescription(nsACString& aDescription) {
+ aDescription.AssignLiteral("Offline cache device");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetUsageReport(nsACString& aUsageReport) {
+ nsAutoCString buffer;
+ buffer.AssignLiteral(
+ " <tr>\n"
+ " <th>Cache Directory:</th>\n"
+ " <td>");
+ nsIFile* cacheDir = mDevice->CacheDirectory();
+ if (!cacheDir) return NS_OK;
+
+ nsAutoString path;
+ nsresult rv = cacheDir->GetPath(path);
+ if (NS_SUCCEEDED(rv))
+ AppendUTF16toUTF8(path, buffer);
+ else
+ buffer.AppendLiteral("directory unavailable");
+
+ buffer.AppendLiteral(
+ "</td>\n"
+ " </tr>\n");
+
+ aUsageReport.Assign(buffer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetEntryCount(uint32_t* aEntryCount) {
+ *aEntryCount = mDevice->EntryCount();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetTotalSize(uint32_t* aTotalSize) {
+ *aTotalSize = mDevice->CacheSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheDeviceInfo::GetMaximumSize(uint32_t* aMaximumSize) {
+ *aMaximumSize = mDevice->CacheCapacity();
+ return NS_OK;
+}
+
+/******************************************************************************
+ * nsOfflineCacheBinding
+ */
+
+class nsOfflineCacheBinding final : public nsISupports {
+ ~nsOfflineCacheBinding() = default;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ static nsOfflineCacheBinding* Create(nsIFile* cacheDir, const nsCString* key,
+ int generation);
+
+ enum { FLAG_NEW_ENTRY = 1 };
+
+ nsCOMPtr<nsIFile> mDataFile;
+ int mGeneration;
+ int mFlags;
+
+ bool IsNewEntry() { return mFlags & FLAG_NEW_ENTRY; }
+ void MarkNewEntry() { mFlags |= FLAG_NEW_ENTRY; }
+ void ClearNewEntry() { mFlags &= ~FLAG_NEW_ENTRY; }
+};
+
+NS_IMPL_ISUPPORTS0(nsOfflineCacheBinding)
+
+nsOfflineCacheBinding* nsOfflineCacheBinding::Create(nsIFile* cacheDir,
+ const nsCString* fullKey,
+ int generation) {
+ nsCOMPtr<nsIFile> file;
+ cacheDir->Clone(getter_AddRefs(file));
+ if (!file) return nullptr;
+
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf)) return nullptr;
+
+ uint64_t hash = DCacheHash(key);
+
+ uint32_t dir1 = (uint32_t)(hash & 0x0F);
+ uint32_t dir2 = (uint32_t)((hash & 0xF0) >> 4);
+
+ hash >>= 8;
+
+ // XXX we might want to create these directories up-front
+
+ file->AppendNative(nsPrintfCString("%X", dir1));
+ Unused << file->Create(nsIFile::DIRECTORY_TYPE, 00700);
+
+ file->AppendNative(nsPrintfCString("%X", dir2));
+ Unused << file->Create(nsIFile::DIRECTORY_TYPE, 00700);
+
+ nsresult rv;
+ char leaf[64];
+
+ if (generation == -1) {
+ file->AppendNative("placeholder"_ns);
+
+ for (generation = 0;; ++generation) {
+ SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
+
+ rv = file->SetNativeLeafName(nsDependentCString(leaf));
+ if (NS_FAILED(rv)) return nullptr;
+ rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) return nullptr;
+ if (NS_SUCCEEDED(rv)) break;
+ }
+ } else {
+ SprintfLiteral(leaf, "%014" PRIX64 "-%X", hash, generation);
+ rv = file->AppendNative(nsDependentCString(leaf));
+ if (NS_FAILED(rv)) return nullptr;
+ }
+
+ nsOfflineCacheBinding* binding = new nsOfflineCacheBinding;
+ if (!binding) return nullptr;
+
+ binding->mDataFile.swap(file);
+ binding->mGeneration = generation;
+ binding->mFlags = 0;
+ return binding;
+}
+
+/******************************************************************************
+ * nsOfflineCacheRecord
+ */
+
+struct nsOfflineCacheRecord {
+ const char* clientID;
+ const char* key;
+ const uint8_t* metaData;
+ uint32_t metaDataLen;
+ int32_t generation;
+ int32_t dataSize;
+ int32_t fetchCount;
+ int64_t lastFetched;
+ int64_t lastModified;
+ int64_t expirationTime;
+};
+
+static nsCacheEntry* CreateCacheEntry(nsOfflineCacheDevice* device,
+ const nsCString* fullKey,
+ const nsOfflineCacheRecord& rec) {
+ nsCacheEntry* entry;
+
+ if (device->IsLocked(*fullKey)) {
+ return nullptr;
+ }
+
+ nsresult rv = nsCacheEntry::Create(fullKey->get(), // XXX enable sharing
+ nsICache::STREAM_BASED,
+ nsICache::STORE_OFFLINE, device, &entry);
+ if (NS_FAILED(rv)) return nullptr;
+
+ entry->SetFetchCount((uint32_t)rec.fetchCount);
+ entry->SetLastFetched(SecondsFromPRTime(rec.lastFetched));
+ entry->SetLastModified(SecondsFromPRTime(rec.lastModified));
+ entry->SetExpirationTime(SecondsFromPRTime(rec.expirationTime));
+ entry->SetDataSize((uint32_t)rec.dataSize);
+
+ entry->UnflattenMetaData((const char*)rec.metaData, rec.metaDataLen);
+
+ // Restore security info, if present
+ const char* info = entry->GetMetaDataElement("security-info");
+ if (info) {
+ nsCOMPtr<nsISupports> infoObj;
+ rv =
+ NS_DeserializeObject(nsDependentCString(info), getter_AddRefs(infoObj));
+ if (NS_FAILED(rv)) {
+ delete entry;
+ return nullptr;
+ }
+ entry->SetSecurityInfo(infoObj);
+ }
+
+ // create a binding object for this entry
+ nsOfflineCacheBinding* binding = nsOfflineCacheBinding::Create(
+ device->CacheDirectory(), fullKey, rec.generation);
+ if (!binding) {
+ delete entry;
+ return nullptr;
+ }
+ entry->SetData(binding);
+
+ return entry;
+}
+
+/******************************************************************************
+ * nsOfflineCacheEntryInfo
+ */
+
+class nsOfflineCacheEntryInfo final : public nsICacheEntryInfo {
+ ~nsOfflineCacheEntryInfo() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEENTRYINFO
+
+ nsOfflineCacheRecord* mRec;
+};
+
+NS_IMPL_ISUPPORTS(nsOfflineCacheEntryInfo, nsICacheEntryInfo)
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetClientID(nsACString& aClientID) {
+ aClientID.Assign(mRec->clientID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetDeviceID(nsACString& aDeviceID) {
+ aDeviceID.Assign(OFFLINE_CACHE_DEVICE_ID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetKey(nsACString& clientKey) {
+ clientKey.Assign(mRec->key);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetFetchCount(int32_t* aFetchCount) {
+ *aFetchCount = mRec->fetchCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetLastFetched(uint32_t* aLastFetched) {
+ *aLastFetched = SecondsFromPRTime(mRec->lastFetched);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetLastModified(uint32_t* aLastModified) {
+ *aLastModified = SecondsFromPRTime(mRec->lastModified);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetExpirationTime(uint32_t* aExpirationTime) {
+ *aExpirationTime = SecondsFromPRTime(mRec->expirationTime);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::IsStreamBased(bool* aStreamBased) {
+ *aStreamBased = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineCacheEntryInfo::GetDataSize(uint32_t* aDataSize) {
+ *aDataSize = mRec->dataSize;
+ return NS_OK;
+}
+
+/******************************************************************************
+ * nsApplicationCacheNamespace
+ */
+
+NS_IMPL_ISUPPORTS(nsApplicationCacheNamespace, nsIApplicationCacheNamespace)
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::Init(uint32_t itemType,
+ const nsACString& namespaceSpec,
+ const nsACString& data) {
+ mItemType = itemType;
+ mNamespaceSpec = namespaceSpec;
+ mData = data;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::GetItemType(uint32_t* out) {
+ *out = mItemType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::GetNamespaceSpec(nsACString& out) {
+ out = mNamespaceSpec;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCacheNamespace::GetData(nsACString& out) {
+ out = mData;
+ return NS_OK;
+}
+
+/******************************************************************************
+ * nsApplicationCache
+ */
+
+NS_IMPL_ISUPPORTS(nsApplicationCache, nsIApplicationCache,
+ nsISupportsWeakReference)
+
+nsApplicationCache::nsApplicationCache() : mDevice(nullptr), mValid(true) {}
+
+nsApplicationCache::nsApplicationCache(nsOfflineCacheDevice* device,
+ const nsACString& group,
+ const nsACString& clientID)
+ : mDevice(device), mGroup(group), mClientID(clientID), mValid(true) {}
+
+nsApplicationCache::~nsApplicationCache() {
+ if (!mDevice) return;
+
+ {
+ MutexAutoLock lock(mDevice->mLock);
+ mDevice->mCaches.Remove(mClientID);
+ }
+
+ // If this isn't an active cache anymore, it can be destroyed.
+ if (mValid && !mDevice->IsActiveCache(mGroup, mClientID)) Discard();
+}
+
+void nsApplicationCache::MarkInvalid() { mValid = false; }
+
+NS_IMETHODIMP
+nsApplicationCache::InitAsHandle(const nsACString& groupId,
+ const nsACString& clientId) {
+ NS_ENSURE_FALSE(mDevice, NS_ERROR_ALREADY_INITIALIZED);
+ NS_ENSURE_TRUE(mGroup.IsEmpty(), NS_ERROR_ALREADY_INITIALIZED);
+
+ mGroup = groupId;
+ mClientID = clientId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetManifestURI(nsIURI** out) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), mGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_GetURIWithNewRef(uri, ""_ns, out);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetGroupID(nsACString& out) {
+ out = mGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetClientID(nsACString& out) {
+ out = mClientID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetProfileDirectory(nsIFile** out) {
+ *out = do_AddRef(mDevice->BaseDirectory()).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetActive(bool* out) {
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ *out = mDevice->IsActiveCache(mGroup, mClientID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::Activate() {
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ mDevice->ActivateCache(mGroup, mClientID);
+
+ if (mDevice->AutoShutdown(this)) mDevice = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::Discard() {
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ mValid = false;
+
+ nsCOMPtr<nsIRunnable> ev =
+ new nsOfflineCacheDiscardCache(mDevice, mGroup, mClientID);
+ nsresult rv = nsCacheService::DispatchToCacheIOThread(ev);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::MarkEntry(const nsACString& key, uint32_t typeBits) {
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->MarkEntry(mClientID, key, typeBits);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::UnmarkEntry(const nsACString& key, uint32_t typeBits) {
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->UnmarkEntry(mClientID, key, typeBits);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetTypes(const nsACString& key, uint32_t* typeBits) {
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->GetTypes(mClientID, key, typeBits);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GatherEntries(uint32_t typeBits,
+ nsTArray<nsCString>& keys) {
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->GatherEntries(mClientID, typeBits, keys);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::AddNamespaces(nsIArray* namespaces) {
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ if (!namespaces) return NS_OK;
+
+ mozStorageTransaction transaction(mDevice->mDB, false);
+
+ uint32_t length;
+ nsresult rv = namespaces->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < length; i++) {
+ nsCOMPtr<nsIApplicationCacheNamespace> ns =
+ do_QueryElementAt(namespaces, i);
+ if (ns) {
+ rv = mDevice->AddNamespace(mClientID, ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ rv = transaction.Commit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetMatchingNamespace(const nsACString& key,
+ nsIApplicationCacheNamespace** out)
+
+{
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->GetMatchingNamespace(mClientID, key, out);
+}
+
+NS_IMETHODIMP
+nsApplicationCache::GetUsage(uint32_t* usage) {
+ NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
+ NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
+
+ return mDevice->GetUsage(mClientID, usage);
+}
+
+/******************************************************************************
+ * nsCloseDBEvent
+ *****************************************************************************/
+
+class nsCloseDBEvent : public Runnable {
+ public:
+ explicit nsCloseDBEvent(mozIStorageConnection* aDB)
+ : mozilla::Runnable("nsCloseDBEvent") {
+ mDB = aDB;
+ }
+
+ NS_IMETHOD Run() override {
+ mDB->Close();
+ return NS_OK;
+ }
+
+ protected:
+ virtual ~nsCloseDBEvent() = default;
+
+ private:
+ nsCOMPtr<mozIStorageConnection> mDB;
+};
+
+/******************************************************************************
+ * nsOfflineCacheDevice
+ */
+
+NS_IMPL_ISUPPORTS0(nsOfflineCacheDevice)
+
+nsOfflineCacheDevice::nsOfflineCacheDevice()
+ : mDB(nullptr),
+ mCacheCapacity(0),
+ mDeltaCounter(0),
+ mAutoShutdown(false),
+ mLock("nsOfflineCacheDevice.lock"),
+ mActiveCaches(4),
+ mLockedEntries(32) {}
+
+/* static */
+bool nsOfflineCacheDevice::GetStrictFileOriginPolicy() {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ bool retval;
+ if (prefs && NS_SUCCEEDED(prefs->GetBoolPref(
+ "security.fileuri.strict_origin_policy", &retval)))
+ return retval;
+
+ // As default value use true (be more strict)
+ return true;
+}
+
+uint32_t nsOfflineCacheDevice::CacheSize() {
+ NS_ENSURE_TRUE(Initialized(), 0);
+
+ AutoResetStatement statement(mStatement_CacheSize);
+
+ bool hasRows;
+ nsresult rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
+
+ return (uint32_t)statement->AsInt32(0);
+}
+
+uint32_t nsOfflineCacheDevice::EntryCount() {
+ NS_ENSURE_TRUE(Initialized(), 0);
+
+ AutoResetStatement statement(mStatement_EntryCount);
+
+ bool hasRows;
+ nsresult rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
+
+ return (uint32_t)statement->AsInt32(0);
+}
+
+nsresult nsOfflineCacheDevice::UpdateEntry(nsCacheEntry* entry) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+
+ if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
+ return NS_ERROR_UNEXPECTED;
+
+ // Store security info, if it is serializable
+ nsCOMPtr<nsISupports> infoObj = entry->SecurityInfo();
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(infoObj);
+ if (infoObj && !serializable) return NS_ERROR_UNEXPECTED;
+
+ if (serializable) {
+ nsCString info;
+ nsresult rv = NS_SerializeToString(serializable, info);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = entry->SetMetaDataElement("security-info", info.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCString metaDataBuf;
+ uint32_t mdSize = entry->MetaDataSize();
+ if (!metaDataBuf.SetLength(mdSize, fallible)) return NS_ERROR_OUT_OF_MEMORY;
+ char* md = metaDataBuf.BeginWriting();
+ entry->FlattenMetaData(md, mdSize);
+
+ nsOfflineCacheRecord rec;
+ rec.metaData = (const uint8_t*)md;
+ rec.metaDataLen = mdSize;
+ rec.dataSize = entry->DataSize();
+ rec.fetchCount = entry->FetchCount();
+ rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
+ rec.lastModified = PRTimeFromSeconds(entry->LastModified());
+ rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
+
+ AutoResetStatement statement(mStatement_UpdateEntry);
+
+ nsresult rv;
+ rv = statement->BindBlobByIndex(0, rec.metaData, rec.metaDataLen);
+ nsresult tmp = statement->BindInt32ByIndex(1, rec.dataSize);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt32ByIndex(2, rec.fetchCount);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(3, rec.lastFetched);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(4, rec.lastModified);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(5, rec.expirationTime);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindUTF8StringByIndex(6, nsDependentCString(cid));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindUTF8StringByIndex(7, nsDependentCString(key));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(!hasRows, "UPDATE should not result in output");
+ return rv;
+}
+
+nsresult nsOfflineCacheDevice::UpdateEntrySize(nsCacheEntry* entry,
+ uint32_t newSize) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
+ return NS_ERROR_UNEXPECTED;
+
+ AutoResetStatement statement(mStatement_UpdateEntrySize);
+
+ nsresult rv = statement->BindInt32ByIndex(0, newSize);
+ nsresult tmp = statement->BindUTF8StringByIndex(1, nsDependentCString(cid));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindUTF8StringByIndex(2, nsDependentCString(key));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(!hasRows, "UPDATE should not result in output");
+ return rv;
+}
+
+nsresult nsOfflineCacheDevice::DeleteEntry(nsCacheEntry* entry,
+ bool deleteData) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ if (deleteData) {
+ nsresult rv = DeleteData(entry);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
+ return NS_ERROR_UNEXPECTED;
+
+ AutoResetStatement statement(mStatement_DeleteEntry);
+
+ nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
+ nsresult rv2 = statement->BindUTF8StringByIndex(1, nsDependentCString(key));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_SUCCESS(rv2, rv2);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(!hasRows, "DELETE should not result in output");
+ return rv;
+}
+
+nsresult nsOfflineCacheDevice::DeleteData(nsCacheEntry* entry) {
+ nsOfflineCacheBinding* binding = (nsOfflineCacheBinding*)entry->Data();
+ NS_ENSURE_STATE(binding);
+
+ return binding->mDataFile->Remove(false);
+}
+
+/**
+ * nsCacheDevice implementation
+ */
+
+// This struct is local to nsOfflineCacheDevice::Init, but ISO C++98 doesn't
+// allow a template (mozilla::ArrayLength) to be instantiated based on a local
+// type. Boo-urns!
+struct StatementSql {
+ nsCOMPtr<mozIStorageStatement>& statement;
+ const char* sql;
+ StatementSql(nsCOMPtr<mozIStorageStatement>& aStatement, const char* aSql)
+ : statement(aStatement), sql(aSql) {}
+};
+
+nsresult nsOfflineCacheDevice::Init() {
+ MOZ_ASSERT(false, "Need to be initialized with sqlite");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsOfflineCacheDevice::InitWithSqlite(mozIStorageService* ss) {
+ NS_ENSURE_TRUE(!mDB, NS_ERROR_ALREADY_INITIALIZED);
+
+ // SetCacheParentDirectory must have been called
+ NS_ENSURE_TRUE(mCacheDirectory, NS_ERROR_UNEXPECTED);
+
+ // make sure the cache directory exists
+ nsresult rv = EnsureDir(mCacheDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // build path to index file
+ nsCOMPtr<nsIFile> indexFile;
+ rv = mCacheDirectory->Clone(getter_AddRefs(indexFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = indexFile->AppendNative("index.sqlite"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(ss,
+ "nsOfflineCacheDevice::InitWithSqlite called before "
+ "nsCacheService::Init() ?");
+ NS_ENSURE_TRUE(ss, NS_ERROR_UNEXPECTED);
+
+ rv = ss->OpenDatabase(indexFile, getter_AddRefs(mDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInitEventTarget = GetCurrentEventTarget();
+
+ mDB->ExecuteSimpleSQL("PRAGMA synchronous = OFF;"_ns);
+
+ // XXX ... other initialization steps
+
+ // XXX in the future we may wish to verify the schema for moz_cache
+ // perhaps using "PRAGMA table_info" ?
+
+ // build the table
+ //
+ // "Generation" is the data file generation number.
+ //
+ rv = mDB->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE TABLE IF NOT EXISTS moz_cache (\n"
+ " ClientID TEXT,\n"
+ " Key TEXT,\n"
+ " MetaData BLOB,\n"
+ " Generation INTEGER,\n"
+ " DataSize INTEGER,\n"
+ " FetchCount INTEGER,\n"
+ " LastFetched INTEGER,\n"
+ " LastModified INTEGER,\n"
+ " ExpirationTime INTEGER,\n"
+ " ItemType INTEGER DEFAULT 0\n"
+ ");\n"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Databases from 1.9.0 don't have the ItemType column. Add the column
+ // here, but don't worry about failures (the column probably already exists)
+ mDB->ExecuteSimpleSQL(
+ "ALTER TABLE moz_cache ADD ItemType INTEGER DEFAULT 0"_ns);
+
+ // Create the table for storing cache groups. All actions on
+ // moz_cache_groups use the GroupID, so use it as the primary key.
+ rv = mDB->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE TABLE IF NOT EXISTS moz_cache_groups (\n"
+ " GroupID TEXT PRIMARY KEY,\n"
+ " ActiveClientID TEXT\n"
+ ");\n"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDB->ExecuteSimpleSQL(
+ nsLiteralCString("ALTER TABLE moz_cache_groups "
+ "ADD ActivateTimeStamp INTEGER DEFAULT 0"));
+
+ // ClientID: clientID joining moz_cache and moz_cache_namespaces
+ // tables.
+ // Data: Data associated with this namespace (e.g. a fallback URI
+ // for fallback entries).
+ // ItemType: the type of namespace.
+ rv =
+ mDB->ExecuteSimpleSQL(nsLiteralCString("CREATE TABLE IF NOT EXISTS"
+ " moz_cache_namespaces (\n"
+ " ClientID TEXT,\n"
+ " NameSpace TEXT,\n"
+ " Data TEXT,\n"
+ " ItemType INTEGER\n"
+ ");\n"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Databases from 1.9.0 have a moz_cache_index that should be dropped
+ rv = mDB->ExecuteSimpleSQL("DROP INDEX IF EXISTS moz_cache_index"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Key/ClientID pairs should be unique in the database. All queries
+ // against moz_cache use the Key (which is also the most unique), so
+ // use it as the primary key for this index.
+ rv = mDB->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE UNIQUE INDEX IF NOT EXISTS "
+ " moz_cache_key_clientid_index"
+ " ON moz_cache (Key, ClientID);"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Used for ClientID lookups and to keep ClientID/NameSpace pairs unique.
+ rv = mDB->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE UNIQUE INDEX IF NOT EXISTS"
+ " moz_cache_namespaces_clientid_index"
+ " ON moz_cache_namespaces (ClientID, NameSpace);"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Used for namespace lookups.
+ rv = mDB->ExecuteSimpleSQL(
+ nsLiteralCString("CREATE INDEX IF NOT EXISTS"
+ " moz_cache_namespaces_namespace_index"
+ " ON moz_cache_namespaces (NameSpace);"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mEvictionFunction = new nsOfflineCacheEvictionFunction(this);
+ if (!mEvictionFunction) return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = mDB->CreateFunction("cache_eviction_observer"_ns, 3, mEvictionFunction);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create all (most) of our statements up front
+ StatementSql prepared[] = {
+ // clang-format off
+ StatementSql ( mStatement_CacheSize, "SELECT Sum(DataSize) from moz_cache;" ),
+ StatementSql ( mStatement_ApplicationCacheSize, "SELECT Sum(DataSize) from moz_cache WHERE ClientID = ?;" ),
+ StatementSql ( mStatement_EntryCount, "SELECT count(*) from moz_cache;" ),
+ StatementSql ( mStatement_UpdateEntry, "UPDATE moz_cache SET MetaData = ?, DataSize = ?, FetchCount = ?, LastFetched = ?, LastModified = ?, ExpirationTime = ? WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_UpdateEntrySize, "UPDATE moz_cache SET DataSize = ? WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_DeleteEntry, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_FindEntry, "SELECT MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime, ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_BindEntry, "INSERT INTO moz_cache (ClientID, Key, MetaData, Generation, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime) VALUES(?,?,?,?,?,?,?,?,?);" ),
+
+ StatementSql ( mStatement_MarkEntry, "UPDATE moz_cache SET ItemType = (ItemType | ?) WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_UnmarkEntry, "UPDATE moz_cache SET ItemType = (ItemType & ~?) WHERE ClientID = ? AND Key = ?;" ),
+ StatementSql ( mStatement_GetTypes, "SELECT ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;"),
+ StatementSql ( mStatement_CleanupUnmarked, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ? AND ItemType = 0;" ),
+ StatementSql ( mStatement_GatherEntries, "SELECT Key FROM moz_cache WHERE ClientID = ? AND (ItemType & ?) > 0;" ),
+
+ StatementSql ( mStatement_ActivateClient, "INSERT OR REPLACE INTO moz_cache_groups (GroupID, ActiveClientID, ActivateTimeStamp) VALUES (?, ?, ?);" ),
+ StatementSql ( mStatement_DeactivateGroup, "DELETE FROM moz_cache_groups WHERE GroupID = ?;" ),
+ StatementSql ( mStatement_FindClient, "/* do not warn (bug 1293375) */ SELECT ClientID, ItemType FROM moz_cache WHERE Key = ? ORDER BY LastFetched DESC, LastModified DESC;" ),
+
+ // Search for namespaces that match the URI. Use the <= operator
+ // to ensure that we use the index on moz_cache_namespaces.
+ StatementSql ( mStatement_FindClientByNamespace, "/* do not warn (bug 1293375) */ SELECT ns.ClientID, ns.ItemType FROM"
+ " moz_cache_namespaces AS ns JOIN moz_cache_groups AS groups"
+ " ON ns.ClientID = groups.ActiveClientID"
+ " WHERE ns.NameSpace <= ?1 AND ?1 GLOB ns.NameSpace || '*'"
+ " ORDER BY ns.NameSpace DESC, groups.ActivateTimeStamp DESC;"),
+ StatementSql ( mStatement_FindNamespaceEntry, "SELECT NameSpace, Data, ItemType FROM moz_cache_namespaces"
+ " WHERE ClientID = ?1"
+ " AND NameSpace <= ?2 AND ?2 GLOB NameSpace || '*'"
+ " ORDER BY NameSpace DESC;"),
+ StatementSql ( mStatement_InsertNamespaceEntry, "INSERT INTO moz_cache_namespaces (ClientID, NameSpace, Data, ItemType) VALUES(?, ?, ?, ?);"),
+ StatementSql ( mStatement_EnumerateApps, "SELECT GroupID, ActiveClientID FROM moz_cache_groups WHERE GroupID LIKE ?1;"),
+ StatementSql ( mStatement_EnumerateGroups, "SELECT GroupID, ActiveClientID FROM moz_cache_groups;"),
+ StatementSql ( mStatement_EnumerateGroupsTimeOrder, "SELECT GroupID, ActiveClientID FROM moz_cache_groups ORDER BY ActivateTimeStamp;")
+ // clang-format on
+ };
+ for (uint32_t i = 0; NS_SUCCEEDED(rv) && i < ArrayLength(prepared); ++i) {
+ LOG(("Creating statement: %s\n", prepared[i].sql));
+
+ rv = mDB->CreateStatement(nsDependentCString(prepared[i].sql),
+ getter_AddRefs(prepared[i].statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = InitActiveCaches();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+namespace {
+
+nsresult GetGroupForCache(const nsACString& clientID, nsCString& group) {
+ group.Assign(clientID);
+ group.Truncate(group.FindChar('|'));
+ NS_UnescapeURL(group);
+
+ return NS_OK;
+}
+
+} // namespace
+
+// static
+nsresult nsOfflineCacheDevice::BuildApplicationCacheGroupID(
+ nsIURI* aManifestURL, nsACString const& aOriginSuffix,
+ nsACString& _result) {
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv =
+ NS_GetURIWithNewRef(aManifestURL, ""_ns, getter_AddRefs(newURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString manifestSpec;
+ rv = newURI->GetAsciiSpec(manifestSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ _result.Assign(manifestSpec);
+ _result.Append('#');
+ _result.Append(aOriginSuffix);
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::InitActiveCaches() {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ MutexAutoLock lock(mLock);
+
+ AutoResetStatement statement(mStatement_EnumerateGroups);
+
+ bool hasRows;
+ nsresult rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows) {
+ nsAutoCString group;
+ statement->GetUTF8String(0, group);
+ nsCString clientID;
+ statement->GetUTF8String(1, clientID);
+
+ mActiveCaches.PutEntry(clientID);
+ mActiveCachesByGroup.Put(group, new nsCString(clientID));
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::Shutdown() {
+ NS_ENSURE_TRUE(mDB, NS_ERROR_NOT_INITIALIZED);
+
+ {
+ MutexAutoLock lock(mLock);
+ for (auto iter = mCaches.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIApplicationCache> obj = do_QueryReferent(iter.UserData());
+ if (obj) {
+ auto appCache = static_cast<nsApplicationCache*>(obj.get());
+ appCache->MarkInvalid();
+ }
+ }
+ }
+
+ {
+ EvictionObserver evictionObserver(mDB, mEvictionFunction);
+
+ // Delete all rows whose clientID is not an active clientID.
+ nsresult rv = mDB->ExecuteSimpleSQL(nsLiteralCString(
+ "DELETE FROM moz_cache WHERE rowid IN"
+ " (SELECT moz_cache.rowid FROM"
+ " moz_cache LEFT OUTER JOIN moz_cache_groups ON"
+ " (moz_cache.ClientID = moz_cache_groups.ActiveClientID)"
+ " WHERE moz_cache_groups.GroupID ISNULL)"));
+
+ if (NS_FAILED(rv))
+ NS_WARNING("Failed to clean up unused application caches.");
+ else
+ evictionObserver.Apply();
+
+ // Delete all namespaces whose clientID is not an active clientID.
+ rv = mDB->ExecuteSimpleSQL(nsLiteralCString(
+ "DELETE FROM moz_cache_namespaces WHERE rowid IN"
+ " (SELECT moz_cache_namespaces.rowid FROM"
+ " moz_cache_namespaces LEFT OUTER JOIN moz_cache_groups ON"
+ " (moz_cache_namespaces.ClientID = "
+ "moz_cache_groups.ActiveClientID)"
+ " WHERE moz_cache_groups.GroupID ISNULL)"));
+
+ if (NS_FAILED(rv)) NS_WARNING("Failed to clean up namespaces.");
+
+ mEvictionFunction = nullptr;
+
+ mStatement_CacheSize = nullptr;
+ mStatement_ApplicationCacheSize = nullptr;
+ mStatement_EntryCount = nullptr;
+ mStatement_UpdateEntry = nullptr;
+ mStatement_UpdateEntrySize = nullptr;
+ mStatement_DeleteEntry = nullptr;
+ mStatement_FindEntry = nullptr;
+ mStatement_BindEntry = nullptr;
+ mStatement_ClearDomain = nullptr;
+ mStatement_MarkEntry = nullptr;
+ mStatement_UnmarkEntry = nullptr;
+ mStatement_GetTypes = nullptr;
+ mStatement_FindNamespaceEntry = nullptr;
+ mStatement_InsertNamespaceEntry = nullptr;
+ mStatement_CleanupUnmarked = nullptr;
+ mStatement_GatherEntries = nullptr;
+ mStatement_ActivateClient = nullptr;
+ mStatement_DeactivateGroup = nullptr;
+ mStatement_FindClient = nullptr;
+ mStatement_FindClientByNamespace = nullptr;
+ mStatement_EnumerateApps = nullptr;
+ mStatement_EnumerateGroups = nullptr;
+ mStatement_EnumerateGroupsTimeOrder = nullptr;
+ }
+
+ // Close Database on the correct thread
+ bool isOnCurrentThread = true;
+ if (mInitEventTarget)
+ isOnCurrentThread = mInitEventTarget->IsOnCurrentThread();
+
+ if (!isOnCurrentThread) {
+ nsCOMPtr<nsIRunnable> ev = new nsCloseDBEvent(mDB);
+
+ if (ev) {
+ mInitEventTarget->Dispatch(ev, NS_DISPATCH_NORMAL);
+ }
+ } else {
+ mDB->Close();
+ }
+
+ mDB = nullptr;
+ mInitEventTarget = nullptr;
+
+ return NS_OK;
+}
+
+const char* nsOfflineCacheDevice::GetDeviceID() {
+ return OFFLINE_CACHE_DEVICE_ID;
+}
+
+nsCacheEntry* nsOfflineCacheDevice::FindEntry(nsCString* fullKey,
+ bool* collision) {
+ NS_ENSURE_TRUE(Initialized(), nullptr);
+
+ mozilla::Telemetry::AutoTimer<mozilla::Telemetry::CACHE_OFFLINE_SEARCH_2>
+ timer;
+ LOG(("nsOfflineCacheDevice::FindEntry [key=%s]\n", fullKey->get()));
+
+ // SELECT * FROM moz_cache WHERE key = ?
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf)) return nullptr;
+
+ AutoResetStatement statement(mStatement_FindEntry);
+
+ nsresult rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
+ nsresult rv2 = statement->BindUTF8StringByIndex(1, nsDependentCString(key));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ NS_ENSURE_SUCCESS(rv2, nullptr);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ if (NS_FAILED(rv) || !hasRows) return nullptr; // entry not found
+
+ nsOfflineCacheRecord rec;
+ statement->GetSharedBlob(0, &rec.metaDataLen, (const uint8_t**)&rec.metaData);
+ rec.generation = statement->AsInt32(1);
+ rec.dataSize = statement->AsInt32(2);
+ rec.fetchCount = statement->AsInt32(3);
+ rec.lastFetched = statement->AsInt64(4);
+ rec.lastModified = statement->AsInt64(5);
+ rec.expirationTime = statement->AsInt64(6);
+
+ LOG(("entry: [%u %d %d %d %" PRId64 " %" PRId64 " %" PRId64 "]\n",
+ rec.metaDataLen, rec.generation, rec.dataSize, rec.fetchCount,
+ rec.lastFetched, rec.lastModified, rec.expirationTime));
+
+ nsCacheEntry* entry = CreateCacheEntry(this, fullKey, rec);
+
+ if (entry) {
+ // make sure that the data file exists
+ nsOfflineCacheBinding* binding = (nsOfflineCacheBinding*)entry->Data();
+ bool isFile;
+ rv = binding->mDataFile->IsFile(&isFile);
+ if (NS_FAILED(rv) || !isFile) {
+ DeleteEntry(entry, false);
+ delete entry;
+ return nullptr;
+ }
+
+ // lock the entry
+ Lock(*fullKey);
+ }
+
+ return entry;
+}
+
+nsresult nsOfflineCacheDevice::DeactivateEntry(nsCacheEntry* entry) {
+ LOG(("nsOfflineCacheDevice::DeactivateEntry [key=%s]\n",
+ entry->Key()->get()));
+
+ // This method is called to inform us that the nsCacheEntry object is going
+ // away. We should persist anything that needs to be persisted, or if the
+ // entry is doomed, we can go ahead and clear its storage.
+
+ if (entry->IsDoomed()) {
+ // remove corresponding row and file if they exist
+
+ // the row should have been removed in DoomEntry... we could assert that
+ // that happened. otherwise, all we have to do here is delete the file
+ // on disk.
+ DeleteData(entry);
+ } else if (((nsOfflineCacheBinding*)entry->Data())->IsNewEntry()) {
+ // UPDATE the database row
+
+ // Only new entries are updated, since offline cache is updated in
+ // transactions. New entries are those who is returned from
+ // BindEntry().
+
+ LOG(("nsOfflineCacheDevice::DeactivateEntry updating new entry\n"));
+ UpdateEntry(entry);
+ } else {
+ LOG(
+ ("nsOfflineCacheDevice::DeactivateEntry "
+ "skipping update since entry is not dirty\n"));
+ }
+
+ // Unlock the entry
+ Unlock(*entry->Key());
+
+ delete entry;
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::BindEntry(nsCacheEntry* entry) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::BindEntry [key=%s]\n", entry->Key()->get()));
+
+ NS_ENSURE_STATE(!entry->Data());
+
+ // This method is called to inform us that we have a new entry. The entry
+ // may collide with an existing entry in our DB, but if that happens we can
+ // assume that the entry is not being used.
+
+ // INSERT the database row
+
+ // XXX Assumption: if the row already exists, then FindEntry would have
+ // returned it. if that entry was doomed, then DoomEntry would have removed
+ // it from the table. so, we should always have to insert at this point.
+
+ // Decompose the key into "ClientID" and "Key"
+ nsAutoCString keyBuf;
+ const char *cid, *key;
+ if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
+ return NS_ERROR_UNEXPECTED;
+
+ // create binding, pick best generation number
+ RefPtr<nsOfflineCacheBinding> binding =
+ nsOfflineCacheBinding::Create(mCacheDirectory, entry->Key(), -1);
+ if (!binding) return NS_ERROR_OUT_OF_MEMORY;
+ binding->MarkNewEntry();
+
+ nsOfflineCacheRecord rec;
+ rec.clientID = cid;
+ rec.key = key;
+ rec.metaData = nullptr; // don't write any metadata now.
+ rec.metaDataLen = 0;
+ rec.generation = binding->mGeneration;
+ rec.dataSize = 0;
+ rec.fetchCount = entry->FetchCount();
+ rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
+ rec.lastModified = PRTimeFromSeconds(entry->LastModified());
+ rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
+
+ AutoResetStatement statement(mStatement_BindEntry);
+
+ nsresult rv =
+ statement->BindUTF8StringByIndex(0, nsDependentCString(rec.clientID));
+ nsresult tmp =
+ statement->BindUTF8StringByIndex(1, nsDependentCString(rec.key));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindBlobByIndex(2, rec.metaData, rec.metaDataLen);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt32ByIndex(3, rec.generation);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt32ByIndex(4, rec.dataSize);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt32ByIndex(5, rec.fetchCount);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(6, rec.lastFetched);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(7, rec.lastModified);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = statement->BindInt64ByIndex(8, rec.expirationTime);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(!hasRows, "INSERT should not result in output");
+
+ entry->SetData(binding);
+
+ // lock the entry
+ Lock(*entry->Key());
+
+ return NS_OK;
+}
+
+void nsOfflineCacheDevice::DoomEntry(nsCacheEntry* entry) {
+ LOG(("nsOfflineCacheDevice::DoomEntry [key=%s]\n", entry->Key()->get()));
+
+ // This method is called to inform us that we should mark the entry to be
+ // deleted when it is no longer in use.
+
+ // We can go ahead and delete the corresponding row in our table,
+ // but we must not delete the file on disk until we are deactivated.
+ // In another word, the file should be deleted if the entry had been
+ // deactivated.
+
+ DeleteEntry(entry, !entry->IsActive());
+}
+
+nsresult nsOfflineCacheDevice::OpenInputStreamForEntry(
+ nsCacheEntry* entry, nsCacheAccessMode mode, uint32_t offset,
+ nsIInputStream** result) {
+ LOG(("nsOfflineCacheDevice::OpenInputStreamForEntry [key=%s]\n",
+ entry->Key()->get()));
+
+ *result = nullptr;
+
+ NS_ENSURE_TRUE(!offset || (offset < entry->DataSize()), NS_ERROR_INVALID_ARG);
+
+ // return an input stream to the entry's data file. the stream
+ // may be read on a background thread.
+
+ nsOfflineCacheBinding* binding = (nsOfflineCacheBinding*)entry->Data();
+ NS_ENSURE_STATE(binding);
+
+ nsCOMPtr<nsIInputStream> in;
+ NS_NewLocalFileInputStream(getter_AddRefs(in), binding->mDataFile, PR_RDONLY);
+ if (!in) return NS_ERROR_UNEXPECTED;
+
+ // respect |offset| param
+ if (offset != 0) {
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(in);
+ NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
+
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+ }
+
+ in.swap(*result);
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::OpenOutputStreamForEntry(
+ nsCacheEntry* entry, nsCacheAccessMode mode, uint32_t offset,
+ nsIOutputStream** result) {
+ LOG(("nsOfflineCacheDevice::OpenOutputStreamForEntry [key=%s]\n",
+ entry->Key()->get()));
+
+ *result = nullptr;
+
+ NS_ENSURE_TRUE(offset <= entry->DataSize(), NS_ERROR_INVALID_ARG);
+
+ // return an output stream to the entry's data file. we can assume
+ // that the output stream will only be used on the main thread.
+
+ nsOfflineCacheBinding* binding = (nsOfflineCacheBinding*)entry->Data();
+ NS_ENSURE_STATE(binding);
+
+ nsCOMPtr<nsIOutputStream> out;
+ NS_NewLocalFileOutputStream(getter_AddRefs(out), binding->mDataFile,
+ PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 00600);
+ if (!out) return NS_ERROR_UNEXPECTED;
+
+ // respect |offset| param
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(out);
+ NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
+ if (offset != 0) seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
+
+ // truncate the file at the given offset
+ seekable->SetEOF();
+
+ nsCOMPtr<nsIOutputStream> bufferedOut;
+ nsresult rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut),
+ out.forget(), 16 * 1024);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bufferedOut.forget(result);
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::GetFileForEntry(nsCacheEntry* entry,
+ nsIFile** result) {
+ LOG(("nsOfflineCacheDevice::GetFileForEntry [key=%s]\n",
+ entry->Key()->get()));
+
+ nsOfflineCacheBinding* binding = (nsOfflineCacheBinding*)entry->Data();
+ NS_ENSURE_STATE(binding);
+
+ NS_IF_ADDREF(*result = binding->mDataFile);
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::OnDataSizeChange(nsCacheEntry* entry,
+ int32_t deltaSize) {
+ LOG(("nsOfflineCacheDevice::OnDataSizeChange [key=%s delta=%d]\n",
+ entry->Key()->get(), deltaSize));
+
+ const int32_t DELTA_THRESHOLD = 1 << 14; // 16k
+
+ // called to notify us of an impending change in the total size of the
+ // specified entry.
+
+ uint32_t oldSize = entry->DataSize();
+ NS_ASSERTION(deltaSize >= 0 || int32_t(oldSize) + deltaSize >= 0, "oops");
+ uint32_t newSize = int32_t(oldSize) + deltaSize;
+ UpdateEntrySize(entry, newSize);
+
+ mDeltaCounter += deltaSize; // this may go negative
+
+ if (mDeltaCounter >= DELTA_THRESHOLD) {
+ if (CacheSize() > mCacheCapacity) {
+ // the entry will overrun the cache capacity, doom the entry
+ // and abort
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ nsCacheService::DoomEntry(entry);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "DoomEntry() failed.");
+ return NS_ERROR_ABORT;
+ }
+
+ mDeltaCounter = 0; // reset counter
+ }
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::Visit(nsICacheVisitor* visitor) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ // called to enumerate the offline cache.
+
+ nsCOMPtr<nsICacheDeviceInfo> deviceInfo = new nsOfflineCacheDeviceInfo(this);
+
+ bool keepGoing;
+ nsresult rv =
+ visitor->VisitDevice(OFFLINE_CACHE_DEVICE_ID, deviceInfo, &keepGoing);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!keepGoing) return NS_OK;
+
+ // SELECT * from moz_cache;
+
+ nsOfflineCacheRecord rec;
+ RefPtr<nsOfflineCacheEntryInfo> info = new nsOfflineCacheEntryInfo;
+ if (!info) return NS_ERROR_OUT_OF_MEMORY;
+ info->mRec = &rec;
+
+ // XXX may want to list columns explicitly
+ nsCOMPtr<mozIStorageStatement> statement;
+ rv = mDB->CreateStatement("SELECT * FROM moz_cache;"_ns,
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ for (;;) {
+ rv = statement->ExecuteStep(&hasRows);
+ if (NS_FAILED(rv) || !hasRows) break;
+
+ statement->GetSharedUTF8String(0, nullptr, &rec.clientID);
+ statement->GetSharedUTF8String(1, nullptr, &rec.key);
+ statement->GetSharedBlob(2, &rec.metaDataLen,
+ (const uint8_t**)&rec.metaData);
+ rec.generation = statement->AsInt32(3);
+ rec.dataSize = statement->AsInt32(4);
+ rec.fetchCount = statement->AsInt32(5);
+ rec.lastFetched = statement->AsInt64(6);
+ rec.lastModified = statement->AsInt64(7);
+ rec.expirationTime = statement->AsInt64(8);
+
+ bool keepGoing;
+ rv = visitor->VisitEntry(OFFLINE_CACHE_DEVICE_ID, info, &keepGoing);
+ if (NS_FAILED(rv) || !keepGoing) break;
+ }
+
+ info->mRec = nullptr;
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::EvictEntries(const char* clientID) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::EvictEntries [cid=%s]\n",
+ clientID ? clientID : ""));
+
+ // called to evict all entries matching the given clientID.
+
+ // need trigger to fire user defined function after a row is deleted
+ // so we can delete the corresponding data file.
+ EvictionObserver evictionObserver(mDB, mEvictionFunction);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ nsresult rv;
+ if (clientID) {
+ rv = mDB->CreateStatement("DELETE FROM moz_cache WHERE ClientID=?;"_ns,
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDB->CreateStatement(
+ nsLiteralCString(
+ "DELETE FROM moz_cache_groups WHERE ActiveClientID=?;"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // TODO - Should update internal hashtables.
+ // Low priority, since this API is not widely used.
+ } else {
+ rv = mDB->CreateStatement("DELETE FROM moz_cache;"_ns,
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDB->CreateStatement("DELETE FROM moz_cache_groups;"_ns,
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+ mCaches.Clear();
+ mActiveCaches.Clear();
+ mActiveCachesByGroup.Clear();
+ }
+
+ evictionObserver.Apply();
+
+ statement = nullptr;
+ // Also evict any namespaces associated with this clientID.
+ if (clientID) {
+ rv = mDB->CreateStatement(
+ "DELETE FROM moz_cache_namespaces WHERE ClientID=?"_ns,
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ rv = mDB->CreateStatement("DELETE FROM moz_cache_namespaces;"_ns,
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::MarkEntry(const nsCString& clientID,
+ const nsACString& key,
+ uint32_t typeBits) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::MarkEntry [cid=%s, key=%s, typeBits=%d]\n",
+ clientID.get(), PromiseFlatCString(key).get(), typeBits));
+
+ AutoResetStatement statement(mStatement_MarkEntry);
+ nsresult rv = statement->BindInt32ByIndex(0, typeBits);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(2, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::UnmarkEntry(const nsCString& clientID,
+ const nsACString& key,
+ uint32_t typeBits) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::UnmarkEntry [cid=%s, key=%s, typeBits=%d]\n",
+ clientID.get(), PromiseFlatCString(key).get(), typeBits));
+
+ AutoResetStatement statement(mStatement_UnmarkEntry);
+ nsresult rv = statement->BindInt32ByIndex(0, typeBits);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(2, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remove the entry if it is now empty.
+
+ EvictionObserver evictionObserver(mDB, mEvictionFunction);
+
+ AutoResetStatement cleanupStatement(mStatement_CleanupUnmarked);
+ rv = cleanupStatement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = cleanupStatement->BindUTF8StringByIndex(1, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cleanupStatement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ evictionObserver.Apply();
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::GetMatchingNamespace(
+ const nsCString& clientID, const nsACString& key,
+ nsIApplicationCacheNamespace** out) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetMatchingNamespace [cid=%s, key=%s]\n",
+ clientID.get(), PromiseFlatCString(key).get()));
+
+ nsresult rv;
+
+ AutoResetStatement statement(mStatement_FindNamespaceEntry);
+
+ rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *out = nullptr;
+
+ bool found = false;
+ nsCString nsSpec;
+ int32_t nsType = 0;
+ nsCString nsData;
+
+ while (hasRows) {
+ int32_t itemType;
+ rv = statement->GetInt32(2, &itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!found || itemType > nsType) {
+ nsType = itemType;
+
+ rv = statement->GetUTF8String(0, nsSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->GetUTF8String(1, nsData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ found = true;
+ }
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (found) {
+ nsCOMPtr<nsIApplicationCacheNamespace> ns =
+ new nsApplicationCacheNamespace();
+ if (!ns) return NS_ERROR_OUT_OF_MEMORY;
+ rv = ns->Init(nsType, nsSpec, nsData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ns.swap(*out);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::CacheOpportunistically(const nsCString& clientID,
+ const nsACString& key) {
+ // XXX: We should also be propagating this cache entry to other matching
+ // caches. See bug 444807.
+
+ return MarkEntry(clientID, key, nsIApplicationCache::ITEM_OPPORTUNISTIC);
+}
+
+nsresult nsOfflineCacheDevice::GetTypes(const nsCString& clientID,
+ const nsACString& key,
+ uint32_t* typeBits) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetTypes [cid=%s, key=%s]\n", clientID.get(),
+ PromiseFlatCString(key).get()));
+
+ AutoResetStatement statement(mStatement_GetTypes);
+ nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!hasRows) return NS_ERROR_CACHE_KEY_NOT_FOUND;
+
+ *typeBits = statement->AsInt32(0);
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::GatherEntries(const nsCString& clientID,
+ uint32_t typeBits,
+ nsTArray<nsCString>& keys) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GatherEntries [cid=%s, typeBits=%X]\n",
+ clientID.get(), typeBits));
+
+ AutoResetStatement statement(mStatement_GatherEntries);
+ nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindInt32ByIndex(1, typeBits);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RunSimpleQuery(mStatement_GatherEntries, 0, keys);
+}
+
+nsresult nsOfflineCacheDevice::AddNamespace(const nsCString& clientID,
+ nsIApplicationCacheNamespace* ns) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ nsCString namespaceSpec;
+ nsresult rv = ns->GetNamespaceSpec(namespaceSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString data;
+ rv = ns->GetData(data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t itemType;
+ rv = ns->GetItemType(&itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("nsOfflineCacheDevice::AddNamespace [cid=%s, ns=%s, data=%s, type=%d]",
+ clientID.get(), namespaceSpec.get(), data.get(), itemType));
+
+ AutoResetStatement statement(mStatement_InsertNamespaceEntry);
+
+ rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(1, namespaceSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindUTF8StringByIndex(2, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->BindInt32ByIndex(3, itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::GetUsage(const nsACString& clientID,
+ uint32_t* usage) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetUsage [cid=%s]\n",
+ PromiseFlatCString(clientID).get()));
+
+ *usage = 0;
+
+ AutoResetStatement statement(mStatement_ApplicationCacheSize);
+
+ nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!hasRows) return NS_OK;
+
+ *usage = static_cast<uint32_t>(statement->AsInt32(0));
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::GetGroups(nsTArray<nsCString>& keys) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetGroups"));
+
+ return RunSimpleQuery(mStatement_EnumerateGroups, 0, keys);
+}
+
+nsresult nsOfflineCacheDevice::GetGroupsTimeOrdered(nsTArray<nsCString>& keys) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ LOG(("nsOfflineCacheDevice::GetGroupsTimeOrder"));
+
+ return RunSimpleQuery(mStatement_EnumerateGroupsTimeOrder, 0, keys);
+}
+
+bool nsOfflineCacheDevice::IsLocked(const nsACString& key) {
+ MutexAutoLock lock(mLock);
+ return mLockedEntries.GetEntry(key);
+}
+
+void nsOfflineCacheDevice::Lock(const nsACString& key) {
+ MutexAutoLock lock(mLock);
+ mLockedEntries.PutEntry(key);
+}
+
+void nsOfflineCacheDevice::Unlock(const nsACString& key) {
+ MutexAutoLock lock(mLock);
+ mLockedEntries.RemoveEntry(key);
+}
+
+nsresult nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement* statement,
+ uint32_t resultIndex,
+ nsTArray<nsCString>& values) {
+ bool hasRows;
+ nsresult rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsCString> valArray;
+ while (hasRows) {
+ uint32_t length;
+ valArray.AppendElement(nsDependentCString(
+ statement->AsSharedUTF8String(resultIndex, &length)));
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ values = std::move(valArray);
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::CreateApplicationCache(
+ const nsACString& group, nsIApplicationCache** out) {
+ *out = nullptr;
+
+ nsCString clientID;
+ // Some characters are special in the clientID. Escape the groupID
+ // before putting it in to the client key.
+ if (!NS_Escape(nsCString(group), clientID, url_Path)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ PRTime now = PR_Now();
+
+ // Include the timestamp to guarantee uniqueness across runs, and
+ // the gNextTemporaryClientID for uniqueness within a second.
+ clientID.Append(nsPrintfCString("|%016" PRId64 "|%d", now / PR_USEC_PER_SEC,
+ gNextTemporaryClientID++));
+
+ nsCOMPtr<nsIApplicationCache> cache =
+ new nsApplicationCache(this, group, clientID);
+ if (!cache) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsWeakPtr weak = do_GetWeakReference(cache);
+ if (!weak) return NS_ERROR_OUT_OF_MEMORY;
+
+ MutexAutoLock lock(mLock);
+ mCaches.Put(clientID, weak);
+
+ cache.swap(*out);
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::GetApplicationCache(const nsACString& clientID,
+ nsIApplicationCache** out) {
+ MutexAutoLock lock(mLock);
+ return GetApplicationCache_Unlocked(clientID, out);
+}
+
+nsresult nsOfflineCacheDevice::GetApplicationCache_Unlocked(
+ const nsACString& clientID, nsIApplicationCache** out) {
+ *out = nullptr;
+
+ nsCOMPtr<nsIApplicationCache> cache;
+
+ nsWeakPtr weak;
+ if (mCaches.Get(clientID, getter_AddRefs(weak)))
+ cache = do_QueryReferent(weak);
+
+ if (!cache) {
+ nsCString group;
+ nsresult rv = GetGroupForCache(clientID, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (group.IsEmpty()) {
+ return NS_OK;
+ }
+
+ cache = new nsApplicationCache(this, group, clientID);
+ weak = do_GetWeakReference(cache);
+ if (!weak) return NS_ERROR_OUT_OF_MEMORY;
+
+ mCaches.Put(clientID, weak);
+ }
+
+ cache.swap(*out);
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::GetActiveCache(const nsACString& group,
+ nsIApplicationCache** out) {
+ *out = nullptr;
+
+ MutexAutoLock lock(mLock);
+
+ nsCString* clientID;
+ if (mActiveCachesByGroup.Get(group, &clientID))
+ return GetApplicationCache_Unlocked(*clientID, out);
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::DeactivateGroup(const nsACString& group) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ nsCString* active = nullptr;
+
+ AutoResetStatement statement(mStatement_DeactivateGroup);
+ nsresult rv = statement->BindUTF8StringByIndex(0, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+
+ if (mActiveCachesByGroup.Get(group, &active)) {
+ mActiveCaches.RemoveEntry(*active);
+ mActiveCachesByGroup.Remove(group);
+ active = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::Evict(nsILoadContextInfo* aInfo) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ NS_ENSURE_ARG(aInfo);
+
+ nsresult rv;
+
+ mozilla::OriginAttributes const* oa = aInfo->OriginAttributesPtr();
+
+ if (oa->mInIsolatedMozBrowser == false) {
+ nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nsCacheService::GlobalInstance()->EvictEntriesInternal(
+ nsICache::STORE_OFFLINE);
+ }
+
+ nsAutoCString jaridsuffix;
+ jaridsuffix.Append('%');
+
+ nsAutoCString suffix;
+ oa->CreateSuffix(suffix);
+ jaridsuffix.Append('#');
+ jaridsuffix.Append(suffix);
+
+ AutoResetStatement statement(mStatement_EnumerateApps);
+ rv = statement->BindUTF8StringByIndex(0, jaridsuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows) {
+ nsAutoCString group;
+ rv = statement->GetUTF8String(0, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString clientID;
+ rv = statement->GetUTF8String(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRunnable> ev =
+ new nsOfflineCacheDiscardCache(this, group, clientID);
+
+ rv = nsCacheService::DispatchToCacheIOThread(ev);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+namespace { // anon
+
+class OriginMatch final : public mozIStorageFunction {
+ ~OriginMatch() = default;
+ mozilla::OriginAttributesPattern const mPattern;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+ explicit OriginMatch(mozilla::OriginAttributesPattern const& aPattern)
+ : mPattern(aPattern) {}
+};
+
+NS_IMPL_ISUPPORTS(OriginMatch, mozIStorageFunction)
+
+NS_IMETHODIMP
+OriginMatch::OnFunctionCall(mozIStorageValueArray* aFunctionArguments,
+ nsIVariant** aResult) {
+ nsresult rv;
+
+ nsAutoCString groupId;
+ rv = aFunctionArguments->GetUTF8String(0, groupId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t hash = groupId.Find("#"_ns);
+ if (hash == kNotFound) {
+ // Just ignore...
+ return NS_OK;
+ }
+
+ ++hash;
+
+ nsDependentCSubstring suffix(groupId.BeginReading() + hash,
+ groupId.Length() - hash);
+
+ mozilla::OriginAttributes oa;
+ bool ok = oa.PopulateFromSuffix(suffix);
+ NS_ENSURE_TRUE(ok, NS_ERROR_UNEXPECTED);
+
+ bool match = mPattern.Matches(oa);
+
+ RefPtr<nsVariant> outVar(new nsVariant());
+ rv = outVar->SetAsUint32(match ? 1 : 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outVar.forget(aResult);
+ return NS_OK;
+}
+
+} // namespace
+
+nsresult nsOfflineCacheDevice::Evict(
+ mozilla::OriginAttributesPattern const& aPattern) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv;
+
+ nsCOMPtr<mozIStorageFunction> function1(new OriginMatch(aPattern));
+ rv = mDB->CreateFunction("ORIGIN_MATCH"_ns, 1, function1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ class AutoRemoveFunc {
+ public:
+ mozIStorageConnection* mDB;
+ explicit AutoRemoveFunc(mozIStorageConnection* aDB) : mDB(aDB) {}
+ ~AutoRemoveFunc() { mDB->RemoveFunction("ORIGIN_MATCH"_ns); }
+ };
+ AutoRemoveFunc autoRemove(mDB);
+
+ nsCOMPtr<mozIStorageStatement> statement;
+ rv = mDB->CreateStatement(
+ nsLiteralCString("SELECT GroupID, ActiveClientID FROM moz_cache_groups "
+ "WHERE ORIGIN_MATCH(GroupID);"),
+ getter_AddRefs(statement));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AutoResetStatement statementScope(statement);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows) {
+ nsAutoCString group;
+ rv = statement->GetUTF8String(0, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString clientID;
+ rv = statement->GetUTF8String(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRunnable> ev =
+ new nsOfflineCacheDiscardCache(this, group, clientID);
+
+ rv = nsCacheService::DispatchToCacheIOThread(ev);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+bool nsOfflineCacheDevice::CanUseCache(nsIURI* keyURI,
+ const nsACString& clientID,
+ nsILoadContextInfo* loadContextInfo) {
+ {
+ MutexAutoLock lock(mLock);
+ if (!mActiveCaches.Contains(clientID)) return false;
+ }
+
+ nsAutoCString groupID;
+ nsresult rv = GetGroupForCache(clientID, groupID);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIURI> groupURI;
+ rv = NS_NewURI(getter_AddRefs(groupURI), groupID);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ // When we are choosing an initial cache to load the top
+ // level document from, the URL of that document must have
+ // the same origin as the manifest, according to the spec.
+ // The following check is here because explicit, fallback
+ // and dynamic entries might have origin different from the
+ // manifest origin.
+ if (!NS_SecurityCompareURIs(keyURI, groupURI, GetStrictFileOriginPolicy())) {
+ return false;
+ }
+
+ // Check the groupID we found is equal to groupID based
+ // on the load context demanding load from app cache.
+ // This is check of extended origin.
+
+ nsAutoCString originSuffix;
+ loadContextInfo->OriginAttributesPtr()->CreateSuffix(originSuffix);
+
+ nsAutoCString demandedGroupID;
+ rv = BuildApplicationCacheGroupID(groupURI, originSuffix, demandedGroupID);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (groupID != demandedGroupID) {
+ return false;
+ }
+
+ return true;
+}
+
+nsresult nsOfflineCacheDevice::ChooseApplicationCache(
+ const nsACString& key, nsILoadContextInfo* loadContextInfo,
+ nsIApplicationCache** out) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ NS_ENSURE_ARG(loadContextInfo);
+
+ nsresult rv;
+
+ *out = nullptr;
+
+ nsCOMPtr<nsIURI> keyURI;
+ rv = NS_NewURI(getter_AddRefs(keyURI), key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // First try to find a matching cache entry.
+ AutoResetStatement statement(mStatement_FindClient);
+ rv = statement->BindUTF8StringByIndex(0, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasRows;
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows) {
+ int32_t itemType;
+ rv = statement->GetInt32(1, &itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!(itemType & nsIApplicationCache::ITEM_FOREIGN)) {
+ nsAutoCString clientID;
+ rv = statement->GetUTF8String(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (CanUseCache(keyURI, clientID, loadContextInfo)) {
+ return GetApplicationCache(clientID, out);
+ }
+ }
+
+ rv = statement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // OK, we didn't find an exact match. Search for a client with a
+ // matching namespace.
+
+ AutoResetStatement nsstatement(mStatement_FindClientByNamespace);
+
+ rv = nsstatement->BindUTF8StringByIndex(0, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsstatement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (hasRows) {
+ int32_t itemType;
+ rv = nsstatement->GetInt32(1, &itemType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't associate with a cache based solely on a whitelist entry
+ if (!(itemType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) {
+ nsAutoCString clientID;
+ rv = nsstatement->GetUTF8String(0, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (CanUseCache(keyURI, clientID, loadContextInfo)) {
+ return GetApplicationCache(clientID, out);
+ }
+ }
+
+ rv = nsstatement->ExecuteStep(&hasRows);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsOfflineCacheDevice::CacheOpportunistically(
+ nsIApplicationCache* cache, const nsACString& key) {
+ NS_ENSURE_ARG_POINTER(cache);
+
+ nsresult rv;
+
+ nsAutoCString clientID;
+ rv = cache->GetClientID(clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CacheOpportunistically(clientID, key);
+}
+
+nsresult nsOfflineCacheDevice::ActivateCache(const nsACString& group,
+ const nsACString& clientID) {
+ NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
+
+ AutoResetStatement statement(mStatement_ActivateClient);
+ nsresult rv = statement->BindUTF8StringByIndex(0, group);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindUTF8StringByIndex(1, clientID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = statement->BindInt32ByIndex(2, SecondsFromPRTime(PR_Now()));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = statement->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MutexAutoLock lock(mLock);
+
+ nsCString* active;
+ if (mActiveCachesByGroup.Get(group, &active)) {
+ mActiveCaches.RemoveEntry(*active);
+ mActiveCachesByGroup.Remove(group);
+ active = nullptr;
+ }
+
+ if (!clientID.IsEmpty()) {
+ mActiveCaches.PutEntry(clientID);
+ mActiveCachesByGroup.Put(group, new nsCString(clientID));
+ }
+
+ return NS_OK;
+}
+
+bool nsOfflineCacheDevice::IsActiveCache(const nsACString& group,
+ const nsACString& clientID) {
+ nsCString* active = nullptr;
+ MutexAutoLock lock(mLock);
+ return mActiveCachesByGroup.Get(group, &active) && *active == clientID;
+}
+
+/**
+ * Preference accessors
+ */
+
+void nsOfflineCacheDevice::SetCacheParentDirectory(nsIFile* parentDir) {
+ if (Initialized()) {
+ NS_ERROR("cannot switch cache directory once initialized");
+ return;
+ }
+
+ if (!parentDir) {
+ mCacheDirectory = nullptr;
+ return;
+ }
+
+ // ensure parent directory exists
+ nsresult rv = EnsureDir(parentDir);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("unable to create parent directory");
+ return;
+ }
+
+ mBaseDirectory = parentDir;
+
+ // cache dir may not exist, but that's ok
+ nsCOMPtr<nsIFile> dir;
+ rv = parentDir->Clone(getter_AddRefs(dir));
+ if (NS_FAILED(rv)) return;
+ rv = dir->AppendNative("OfflineCache"_ns);
+ if (NS_FAILED(rv)) return;
+
+ mCacheDirectory = dir;
+}
+
+void nsOfflineCacheDevice::SetCapacity(uint32_t capacity) {
+ mCacheCapacity = capacity * 1024;
+}
+
+bool nsOfflineCacheDevice::AutoShutdown(nsIApplicationCache* aAppCache) {
+ if (!mAutoShutdown) return false;
+
+ mAutoShutdown = false;
+
+ Shutdown();
+
+ nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID);
+ RefPtr<nsCacheService> cacheService = nsCacheService::GlobalInstance();
+ cacheService->RemoveCustomOfflineDevice(this);
+
+ nsAutoCString clientID;
+ aAppCache->GetClientID(clientID);
+
+ MutexAutoLock lock(mLock);
+ mCaches.Remove(clientID);
+
+ return true;
+}
diff --git a/netwerk/cache/nsDiskCacheDeviceSQL.h b/netwerk/cache/nsDiskCacheDeviceSQL.h
new file mode 100644
index 0000000000..772bc21b83
--- /dev/null
+++ b/netwerk/cache/nsDiskCacheDeviceSQL.h
@@ -0,0 +1,263 @@
+/* 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 nsOfflineCacheDevice_h__
+#define nsOfflineCacheDevice_h__
+
+#include "nsCacheDevice.h"
+#include "nsIApplicationCache.h"
+#include "mozIStorageConnection.h"
+#include "mozIStorageFunction.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsInterfaceHashtable.h"
+#include "nsClassHashtable.h"
+#include "nsIWeakReference.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+
+class nsIURI;
+class nsOfflineCacheDevice;
+class mozIStorageService;
+class nsILoadContextInfo;
+namespace mozilla {
+class OriginAttributesPattern;
+}
+
+class nsApplicationCacheNamespace final : public nsIApplicationCacheNamespace {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAPPLICATIONCACHENAMESPACE
+
+ nsApplicationCacheNamespace() : mItemType(0) {}
+
+ private:
+ ~nsApplicationCacheNamespace() = default;
+
+ uint32_t mItemType;
+ nsCString mNamespaceSpec;
+ nsCString mData;
+};
+
+class nsOfflineCacheEvictionFunction final : public mozIStorageFunction {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ explicit nsOfflineCacheEvictionFunction(nsOfflineCacheDevice* device);
+
+ void Init();
+ void Reset();
+ void Apply();
+
+ private:
+ ~nsOfflineCacheEvictionFunction() = default;
+
+ nsOfflineCacheDevice* mDevice;
+ bool mTLSInited;
+};
+
+class nsOfflineCacheDevice final : public nsCacheDevice, public nsISupports {
+ public:
+ nsOfflineCacheDevice();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /**
+ * nsCacheDevice methods
+ */
+
+ virtual nsresult Init() override;
+ nsresult InitWithSqlite(mozIStorageService* ss);
+ virtual nsresult Shutdown() override;
+
+ virtual const char* GetDeviceID(void) override;
+ virtual nsCacheEntry* FindEntry(nsCString* key, bool* collision) override;
+ virtual nsresult DeactivateEntry(nsCacheEntry* entry) override;
+ virtual nsresult BindEntry(nsCacheEntry* entry) override;
+ virtual void DoomEntry(nsCacheEntry* entry) override;
+
+ virtual nsresult OpenInputStreamForEntry(nsCacheEntry* entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIInputStream** result) override;
+
+ virtual nsresult OpenOutputStreamForEntry(nsCacheEntry* entry,
+ nsCacheAccessMode mode,
+ uint32_t offset,
+ nsIOutputStream** result) override;
+
+ virtual nsresult GetFileForEntry(nsCacheEntry* entry,
+ nsIFile** result) override;
+
+ virtual nsresult OnDataSizeChange(nsCacheEntry* entry,
+ int32_t deltaSize) override;
+
+ virtual nsresult Visit(nsICacheVisitor* visitor) override;
+
+ virtual nsresult EvictEntries(const char* clientID) override;
+
+ /* Entry ownership */
+ nsresult GetOwnerDomains(const char* clientID, uint32_t* count,
+ char*** domains);
+ nsresult GetOwnerURIs(const char* clientID, const nsACString& ownerDomain,
+ uint32_t* count, char*** uris);
+ nsresult SetOwnedKeys(const char* clientID, const nsACString& ownerDomain,
+ const nsACString& ownerUrl, uint32_t count,
+ const char** keys);
+ nsresult GetOwnedKeys(const char* clientID, const nsACString& ownerDomain,
+ const nsACString& ownerUrl, uint32_t* count,
+ char*** keys);
+ nsresult AddOwnedKey(const char* clientID, const nsACString& ownerDomain,
+ const nsACString& ownerURI, const nsACString& key);
+ nsresult RemoveOwnedKey(const char* clientID, const nsACString& ownerDomain,
+ const nsACString& ownerURI, const nsACString& key);
+ nsresult KeyIsOwned(const char* clientID, const nsACString& ownerDomain,
+ const nsACString& ownerURI, const nsACString& key,
+ bool* isOwned);
+
+ nsresult ClearKeysOwnedByDomain(const char* clientID,
+ const nsACString& ownerDomain);
+ nsresult EvictUnownedEntries(const char* clientID);
+
+ static nsresult BuildApplicationCacheGroupID(nsIURI* aManifestURL,
+ nsACString const& aOriginSuffix,
+ nsACString& _result);
+
+ nsresult ActivateCache(const nsACString& group, const nsACString& clientID);
+ bool IsActiveCache(const nsACString& group, const nsACString& clientID);
+ nsresult CreateApplicationCache(const nsACString& group,
+ nsIApplicationCache** out);
+
+ nsresult GetApplicationCache(const nsACString& clientID,
+ nsIApplicationCache** out);
+ nsresult GetApplicationCache_Unlocked(const nsACString& clientID,
+ nsIApplicationCache** out);
+
+ nsresult GetActiveCache(const nsACString& group, nsIApplicationCache** out);
+
+ nsresult DeactivateGroup(const nsACString& group);
+
+ nsresult ChooseApplicationCache(const nsACString& key,
+ nsILoadContextInfo* loadContext,
+ nsIApplicationCache** out);
+
+ nsresult CacheOpportunistically(nsIApplicationCache* cache,
+ const nsACString& key);
+
+ nsresult Evict(nsILoadContextInfo* aInfo);
+ nsresult Evict(mozilla::OriginAttributesPattern const& aPattern);
+
+ nsresult GetGroups(nsTArray<nsCString>& keys);
+
+ nsresult GetGroupsTimeOrdered(nsTArray<nsCString>& keys);
+
+ bool IsLocked(const nsACString& key);
+ void Lock(const nsACString& key);
+ void Unlock(const nsACString& key);
+
+ /**
+ * Preference accessors
+ */
+
+ void SetCacheParentDirectory(nsIFile* parentDir);
+ void SetCapacity(uint32_t capacity);
+ void SetAutoShutdown() { mAutoShutdown = true; }
+ bool AutoShutdown(nsIApplicationCache* aAppCache);
+
+ nsIFile* BaseDirectory() { return mBaseDirectory; }
+ nsIFile* CacheDirectory() { return mCacheDirectory; }
+ uint32_t CacheCapacity() { return mCacheCapacity; }
+ uint32_t CacheSize();
+ uint32_t EntryCount();
+
+ private:
+ ~nsOfflineCacheDevice() = default;
+
+ friend class nsApplicationCache;
+
+ static bool GetStrictFileOriginPolicy();
+
+ bool Initialized() { return mDB != nullptr; }
+
+ nsresult InitActiveCaches();
+ nsresult UpdateEntry(nsCacheEntry* entry);
+ nsresult UpdateEntrySize(nsCacheEntry* entry, uint32_t newSize);
+ nsresult DeleteEntry(nsCacheEntry* entry, bool deleteData);
+ nsresult DeleteData(nsCacheEntry* entry);
+ nsresult EnableEvictionObserver();
+ nsresult DisableEvictionObserver();
+
+ bool CanUseCache(nsIURI* keyURI, const nsACString& clientID,
+ nsILoadContextInfo* loadContext);
+
+ nsresult MarkEntry(const nsCString& clientID, const nsACString& key,
+ uint32_t typeBits);
+ nsresult UnmarkEntry(const nsCString& clientID, const nsACString& key,
+ uint32_t typeBits);
+
+ nsresult CacheOpportunistically(const nsCString& clientID,
+ const nsACString& key);
+ nsresult GetTypes(const nsCString& clientID, const nsACString& key,
+ uint32_t* typeBits);
+
+ nsresult GetMatchingNamespace(const nsCString& clientID,
+ const nsACString& key,
+ nsIApplicationCacheNamespace** out);
+ nsresult GatherEntries(const nsCString& clientID, uint32_t typeBits,
+ nsTArray<nsCString>& keys);
+ nsresult AddNamespace(const nsCString& clientID,
+ nsIApplicationCacheNamespace* ns);
+
+ nsresult GetUsage(const nsACString& clientID, uint32_t* usage);
+
+ nsresult RunSimpleQuery(mozIStorageStatement* statment, uint32_t resultIndex,
+ nsTArray<nsCString>& values);
+
+ nsCOMPtr<mozIStorageConnection> mDB;
+ RefPtr<nsOfflineCacheEvictionFunction> mEvictionFunction;
+
+ nsCOMPtr<mozIStorageStatement> mStatement_CacheSize;
+ nsCOMPtr<mozIStorageStatement> mStatement_ApplicationCacheSize;
+ nsCOMPtr<mozIStorageStatement> mStatement_EntryCount;
+ nsCOMPtr<mozIStorageStatement> mStatement_UpdateEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_UpdateEntrySize;
+ nsCOMPtr<mozIStorageStatement> mStatement_DeleteEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_FindEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_BindEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_ClearDomain;
+ nsCOMPtr<mozIStorageStatement> mStatement_MarkEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_UnmarkEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_GetTypes;
+ nsCOMPtr<mozIStorageStatement> mStatement_FindNamespaceEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_InsertNamespaceEntry;
+ nsCOMPtr<mozIStorageStatement> mStatement_CleanupUnmarked;
+ nsCOMPtr<mozIStorageStatement> mStatement_GatherEntries;
+ nsCOMPtr<mozIStorageStatement> mStatement_ActivateClient;
+ nsCOMPtr<mozIStorageStatement> mStatement_DeactivateGroup;
+ nsCOMPtr<mozIStorageStatement> mStatement_FindClient;
+ nsCOMPtr<mozIStorageStatement> mStatement_FindClientByNamespace;
+ nsCOMPtr<mozIStorageStatement> mStatement_EnumerateApps;
+ nsCOMPtr<mozIStorageStatement> mStatement_EnumerateGroups;
+ nsCOMPtr<mozIStorageStatement> mStatement_EnumerateGroupsTimeOrder;
+
+ nsCOMPtr<nsIFile> mBaseDirectory;
+ nsCOMPtr<nsIFile> mCacheDirectory;
+ uint32_t mCacheCapacity; // in bytes
+ int32_t mDeltaCounter;
+ bool mAutoShutdown;
+
+ mozilla::Mutex mLock;
+
+ nsInterfaceHashtable<nsCStringHashKey, nsIWeakReference> mCaches;
+ nsClassHashtable<nsCStringHashKey, nsCString> mActiveCachesByGroup;
+ nsTHashtable<nsCStringHashKey> mActiveCaches;
+ nsTHashtable<nsCStringHashKey> mLockedEntries;
+
+ nsCOMPtr<nsIEventTarget> mInitEventTarget;
+};
+
+#endif // nsOfflineCacheDevice_h__
diff --git a/netwerk/cache/nsICache.idl b/netwerk/cache/nsICache.idl
new file mode 100644
index 0000000000..cd14c98312
--- /dev/null
+++ b/netwerk/cache/nsICache.idl
@@ -0,0 +1,142 @@
+/* -*- 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"
+
+typedef long nsCacheStoragePolicy;
+typedef long nsCacheAccessMode;
+
+/**
+ * nsICache is a namespace for various cache constants. It does not represent
+ * an actual object.
+ */
+[builtinclass, uuid(d6c67f38-b39a-4582-8a48-4c4f8a56dfd0)]
+interface nsICache : nsISupports
+{
+ /**
+ * Access Modes
+ *
+ *
+ * Mode Requested | Not Cached | Cached
+ * ------------------------------------------------------------------------
+ * READ | KEY_NOT_FOUND | NS_OK
+ * | Mode = NONE | Mode = READ
+ * | No Descriptor | Descriptor
+ * ------------------------------------------------------------------------
+ * WRITE | NS_OK | NS_OK (Cache service
+ * | Mode = WRITE | Mode = WRITE dooms existing
+ * | Descriptor | Descriptor cache entry)
+ * ------------------------------------------------------------------------
+ * READ_WRITE | NS_OK | NS_OK
+ * (1st req.) | Mode = WRITE | Mode = READ_WRITE
+ * | Descriptor | Descriptor
+ * ------------------------------------------------------------------------
+ * READ_WRITE | N/A | NS_OK
+ * (Nth req.) | | Mode = READ
+ * | | Descriptor
+ * ------------------------------------------------------------------------
+ *
+ *
+ * Access Requested:
+ *
+ * READ - I only want to READ, if there isn't an entry just fail
+ * WRITE - I have something new I want to write into the cache, make
+ * me a new entry and doom the old one, if any.
+ * READ_WRITE - I want to READ, but I'm willing to update an existing
+ * entry if necessary, or create a new one if none exists.
+ *
+ *
+ * Access Granted:
+ *
+ * NONE - No descriptor is provided. You get zilch. Nada. Nothing.
+ * READ - You can READ from this descriptor.
+ * WRITE - You must WRITE to this descriptor because the cache entry
+ * was just created for you.
+ * READ_WRITE - You can READ the descriptor to determine if it's valid,
+ * you may WRITE if it needs updating.
+ *
+ *
+ * Comments:
+ *
+ * If you think that you might need to modify cached data or meta data,
+ * then you must open a cache entry requesting WRITE access. Only one
+ * cache entry descriptor, per cache entry, will be granted WRITE access.
+ *
+ * Usually, you will request READ_WRITE access in order to first test the
+ * meta data and informational fields to determine if a write (ie. going
+ * to the net) may actually be necessary. If you determine that it is
+ * not, then you would mark the cache entry as valid (using MarkValid) and
+ * then simply read the data from the cache.
+ *
+ * A descriptor granted WRITE access has exclusive access to the cache
+ * entry up to the point at which it marks it as valid. Once the cache
+ * entry has been "validated", other descriptors with READ access may be
+ * opened to the cache entry.
+ *
+ * If you make a request for READ_WRITE access to a cache entry, the cache
+ * service will downgrade your access to READ if there is already a
+ * cache entry descriptor open with WRITE access.
+ *
+ * If you make a request for only WRITE access to a cache entry and another
+ * descriptor with WRITE access is currently open, then the existing cache
+ * entry will be 'doomed', and you will be given a descriptor (with WRITE
+ * access only) to a new cache entry.
+ *
+ */
+ const nsCacheAccessMode ACCESS_NONE = 0;
+ const nsCacheAccessMode ACCESS_READ = 1;
+ const nsCacheAccessMode ACCESS_WRITE = 2;
+ const nsCacheAccessMode ACCESS_READ_WRITE = 3;
+
+ /**
+ * Storage Policy
+ *
+ * The storage policy of a cache entry determines the device(s) to which
+ * it belongs. See nsICacheSession and nsICacheEntryDescriptor for more
+ * details.
+ *
+ * STORE_ANYWHERE - Allows the cache entry to be stored in any device.
+ * The cache service decides which cache device to use
+ * based on "some resource management calculation."
+ * STORE_IN_MEMORY - Requires the cache entry to reside in non-persistent
+ * storage (ie. typically in system RAM).
+ * STORE_ON_DISK - Requires the cache entry to reside in persistent
+ * storage (ie. typically on a system's hard disk).
+ * STORE_OFFLINE - Requires the cache entry to reside in persistent,
+ * reliable storage for offline use.
+ */
+ const nsCacheStoragePolicy STORE_ANYWHERE = 0;
+ const nsCacheStoragePolicy STORE_IN_MEMORY = 1;
+ const nsCacheStoragePolicy STORE_ON_DISK = 2;
+ // value 3 was used by STORE_ON_DISK_AS_FILE which was removed
+ const nsCacheStoragePolicy STORE_OFFLINE = 4;
+
+ /**
+ * All entries for a cache session are stored as streams of data or
+ * as objects. These constant my be used to specify the type of entries
+ * when calling nsICacheService::CreateSession().
+ */
+ const long NOT_STREAM_BASED = 0;
+ const long STREAM_BASED = 1;
+
+ /**
+ * The synchronous OpenCacheEntry() may be blocking or non-blocking. If a cache entry is
+ * waiting to be validated by another cache descriptor (so no new cache descriptors for that
+ * key can be created, OpenCacheEntry() will return NS_ERROR_CACHE_WAIT_FOR_VALIDATION in
+ * non-blocking mode. In blocking mode, it will wait until the cache entry for the key has
+ * been validated or doomed. If the cache entry is validated, then a descriptor for that
+ * entry will be created and returned. If the cache entry was doomed, then a descriptor
+ * will be created for a new cache entry for the key.
+ */
+ const long NON_BLOCKING = 0;
+ const long BLOCKING = 1;
+
+ /**
+ * Constant meaning no expiration time.
+ */
+ const unsigned long NO_EXPIRATION_TIME = 0xFFFFFFFF;
+};
+
diff --git a/netwerk/cache/nsICacheEntryDescriptor.idl b/netwerk/cache/nsICacheEntryDescriptor.idl
new file mode 100644
index 0000000000..085ab388a8
--- /dev/null
+++ b/netwerk/cache/nsICacheEntryDescriptor.idl
@@ -0,0 +1,164 @@
+/* -*- 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 "nsICacheVisitor.idl"
+#include "nsICache.idl"
+
+interface nsISimpleEnumerator;
+interface nsICacheListener;
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIFile;
+interface nsICacheMetaDataVisitor;
+
+
+[scriptable, uuid(90b17d31-46aa-4fb1-a206-473c966cbc18)]
+interface nsICacheEntryDescriptor : nsICacheEntryInfo
+{
+ /**
+ * Set the time at which the cache entry should be considered invalid (in
+ * seconds since the Epoch).
+ */
+ void setExpirationTime(in uint32_t expirationTime);
+
+ /**
+ * Set the cache entry data size. This will fail if the cache entry
+ * IS stream based.
+ */
+ void setDataSize(in unsigned long size);
+
+ /**
+ * Open blocking input stream to cache data. This will fail if the cache
+ * entry IS NOT stream based. 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 blocking, unbuffered input stream.
+ */
+ nsIInputStream openInputStream(in unsigned long offset);
+
+ /**
+ * Open blocking output stream to cache data. This will fail if the cache
+ * entry IS NOT stream based. Use the stream transport service to
+ * asynchronously write to this stream on a background thread. 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.
+ *
+ * @return blocking, unbuffered output stream.
+ */
+ nsIOutputStream openOutputStream(in unsigned long offset);
+
+ /**
+ * Get/set the cache data element. This will fail if the cache entry
+ * IS stream based. The cache entry holds a strong reference to this
+ * object. The object will be released when the cache entry is destroyed.
+ */
+ attribute nsISupports cacheElement;
+
+ /**
+ * Stores the Content-Length specified in the HTTP header for this
+ * entry. Checked before we write to the cache entry, to prevent ever
+ * taking up space in the cache for an entry that we know up front
+ * is going to have to be evicted anyway. See bug 588507.
+ */
+ attribute int64_t predictedDataSize;
+
+ /**
+ * Get the access granted to this descriptor. See nsICache.idl for the
+ * definitions of the access modes and a thorough description of their
+ * corresponding meanings.
+ */
+ readonly attribute nsCacheAccessMode accessGranted;
+
+ /**
+ * Get/set the storage policy of the cache entry. See nsICache.idl for
+ * the definitions of the storage policies.
+ */
+ attribute nsCacheStoragePolicy storagePolicy;
+
+ /**
+ * Get the disk file associated with the cache entry.
+ */
+ readonly attribute nsIFile file;
+
+ /**
+ * Get/set security info on the cache entry for this descriptor. This fails
+ * if the storage policy is not STORE_IN_MEMORY.
+ */
+ attribute nsISupports 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;
+
+ /**
+ * Doom the cache entry this descriptor references in order to slate it for
+ * removal. Once doomed a cache entry cannot be undoomed.
+ *
+ * A descriptor with WRITE access can doom the cache entry and choose to
+ * fail pending requests. This means that pending requests will not get
+ * a cache descriptor. This is meant as a tool for clients that wish to
+ * instruct pending requests to skip the cache.
+ */
+ void doom();
+ void doomAndFailPendingRequests(in nsresult status);
+
+ /**
+ * 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 nsICacheListener listener);
+
+ /**
+ * A writer must validate this cache object before any readers are given
+ * a descriptor to the object.
+ */
+ void markValid();
+
+ /**
+ * Explicitly close the descriptor (optional).
+ */
+
+ void close();
+
+ /**
+ * 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);
+
+ /**
+ * Visitor will be called with key/value pair for each meta data element.
+ */
+ void visitMetaData(in nsICacheMetaDataVisitor visitor);
+};
+
+
+
+[scriptable, uuid(22f9a49c-3cf8-4c23-8006-54efb11ac562)]
+interface nsICacheMetaDataVisitor : nsISupports
+{
+ /**
+ * Called for each key/value pair in the meta data for a cache entry
+ */
+ boolean visitMetaDataElement(in string key,
+ in string value);
+};
diff --git a/netwerk/cache/nsICacheListener.idl b/netwerk/cache/nsICacheListener.idl
new file mode 100644
index 0000000000..cfb2dd4708
--- /dev/null
+++ b/netwerk/cache/nsICacheListener.idl
@@ -0,0 +1,32 @@
+/* -*- 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"
+#include "nsICache.idl"
+
+
+interface nsICacheEntryDescriptor;
+
+[scriptable, uuid(8eadf2ed-8cac-4961-8025-6da6d5827e74)]
+interface nsICacheListener : nsISupports
+{
+ /**
+ * Called when the requested access (or appropriate subset) is
+ * acquired. The status parameter equals NS_OK on success.
+ * See nsICacheService.idl for accessGranted values.
+ */
+ void onCacheEntryAvailable(in nsICacheEntryDescriptor descriptor,
+ in nsCacheAccessMode accessGranted,
+ in nsresult status);
+
+ /**
+ * Called when nsCacheSession::DoomEntry() is completed. The status
+ * parameter is NS_OK when the entry was doomed, or NS_ERROR_NOT_AVAILABLE
+ * when there is no such entry.
+ */
+ void onCacheEntryDoomed(in nsresult status);
+};
diff --git a/netwerk/cache/nsICacheService.idl b/netwerk/cache/nsICacheService.idl
new file mode 100644
index 0000000000..c062a7910f
--- /dev/null
+++ b/netwerk/cache/nsICacheService.idl
@@ -0,0 +1,98 @@
+/* -*- 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"
+#include "nsICache.idl"
+
+interface nsISimpleEnumerator;
+interface nsICacheListener;
+interface nsICacheSession;
+interface nsICacheVisitor;
+interface nsIEventTarget;
+
+/**
+ * @deprecated
+ *
+ * IMPORTANT NOTE: THIS INTERFACE IS NO LONGER SUPPORTED AND PLANNED TO BE
+ * REMOVED SOON. WE STRONGLY ENCORAGE TO MIGRATE THE EXISTING CODE AND FOR
+ * THE NEW CODE USE ONLY THE NEW HTTP CACHE API IN netwerk/cache2/.
+ */
+[scriptable, uuid(14dbe1e9-f3bc-45af-92f4-2c574fcd4e39)]
+interface nsICacheService : nsISupports
+{
+ /**
+ * @throws NS_ERROR_NOT_IMPLEMENTED when the cache v2 is prefered to use.
+ *
+ * Create a cache session
+ *
+ * A cache session represents a client's access into the cache. The cache
+ * session is not "owned" by the cache service. Hence, it is possible to
+ * create duplicate cache sessions. Entries created by a cache session
+ * are invisible to other cache sessions, unless the cache sessions are
+ * equivalent.
+ *
+ * @param clientID - Specifies the name of the client using the cache.
+ * @param storagePolicy - Limits the storage policy for all entries
+ * accessed via the returned session. As a result, devices excluded
+ * by the storage policy will not be searched when opening entries
+ * from the returned session.
+ * @param streamBased - Indicates whether or not the data being cached
+ * can be represented as a stream. The storagePolicy must be
+ * consistent with the value of this field. For example, a non-stream-
+ * based cache entry can only have a storage policy of STORE_IN_MEMORY.
+ * @return new cache session.
+ */
+ nsICacheSession createSession(in string clientID,
+ in nsCacheStoragePolicy storagePolicy,
+ in boolean streamBased);
+
+ /**
+ * @throws NS_ERROR_NOT_IMPLEMENTED when the cache v2 is prefered to use.
+ *
+ * Visit entries stored in the cache. Used to implement about:cache.
+ */
+ void visitEntries(in nsICacheVisitor visitor);
+
+ /**
+ * @throws NS_ERROR_NOT_IMPLEMENTED when the cache v2 is prefered to use.
+ *
+ * Evicts all entries in all devices implied by the storage policy.
+ *
+ * @note This function may evict some items but will throw if it fails to evict
+ * everything.
+ */
+ void evictEntries(in nsCacheStoragePolicy storagePolicy);
+
+ /**
+ * Event target which is used for I/O operations
+ */
+ readonly attribute nsIEventTarget cacheIOTarget;
+};
+
+%{C++
+/**
+ * Observer service notification that is sent when
+ * nsICacheService::evictEntries() or nsICacheSession::evictEntries()
+ * is called.
+ */
+#define NS_CACHESERVICE_EMPTYCACHE_TOPIC_ID "cacheservice:empty-cache"
+%}
+
+[scriptable, builtinclass, uuid(d0fc8d38-db80-4928-bf1c-b0085ddfa9dc)]
+interface nsICacheServiceInternal : nsICacheService
+{
+ /**
+ * This is an internal interface. It changes so frequently that it probably
+ * went away while you were reading this.
+ */
+
+ /**
+ * Milliseconds for which the service lock has been held. 0 if unlocked.
+ */
+ readonly attribute double lockHeldTime;
+};
+
+
diff --git a/netwerk/cache/nsICacheSession.idl b/netwerk/cache/nsICacheSession.idl
new file mode 100644
index 0000000000..cb8871c28d
--- /dev/null
+++ b/netwerk/cache/nsICacheSession.idl
@@ -0,0 +1,88 @@
+/* -*- 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"
+#include "nsICache.idl"
+
+interface nsICacheEntryDescriptor;
+interface nsICacheListener;
+interface nsIFile;
+
+[scriptable, uuid(1dd7708c-de48-4ffe-b5aa-cd218c762887)]
+interface nsICacheSession : nsISupports
+{
+ /**
+ * Expired entries will be doomed or evicted if this attribute is set to
+ * true. If false, expired entries will be returned (useful for offline-
+ * mode and clients, such as HTTP, that can update the valid lifetime of
+ * cached content). This attribute defaults to true.
+ */
+ attribute boolean doomEntriesIfExpired;
+
+ /**
+ * When set, entries created with this session will be placed to a cache
+ * based at this directory. Use when storing entries to a different
+ * profile than the active profile of the the current running application
+ * process.
+ */
+ attribute nsIFile profileDirectory;
+
+ /**
+ * A cache session can only give out one descriptor with WRITE access
+ * to a given cache entry at a time. Until the client calls MarkValid on
+ * its descriptor, other attempts to open the same cache entry will block.
+ */
+
+ /**
+ * Synchronous cache access. This method fails if it is called on the main
+ * thread. Use asyncOpenCacheEntry() instead. This returns a unique
+ * descriptor each time it is called, even if the same key is specified.
+ * When called by multiple threads for write access, only one writable
+ * descriptor will be granted. If 'blockingMode' is set to false, it will
+ * return NS_ERROR_CACHE_WAIT_FOR_VALIDATION rather than block when another
+ * descriptor has been given WRITE access but hasn't validated the entry yet.
+ */
+ nsICacheEntryDescriptor openCacheEntry(in ACString key,
+ in nsCacheAccessMode accessRequested,
+ in boolean blockingMode);
+
+ /**
+ * Asynchronous cache access. Does not block the calling thread. Instead,
+ * the listener will be notified when the descriptor is available. If
+ * 'noWait' is set to true, the listener will be notified immediately with
+ * status NS_ERROR_CACHE_WAIT_FOR_VALIDATION rather than queuing the request
+ * when another descriptor has been given WRITE access but hasn't validated
+ * the entry yet.
+ */
+ void asyncOpenCacheEntry(in ACString key,
+ in nsCacheAccessMode accessRequested,
+ in nsICacheListener listener,
+ [optional] in boolean noWait);
+
+ /**
+ * Evict all entries for this session's clientID according to its storagePolicy.
+ */
+ void evictEntries();
+
+ /**
+ * Return whether any of the cache devices implied by the session storage policy
+ * are currently enabled for instantiation if they don't already exist.
+ */
+ boolean isStorageEnabled();
+
+ /**
+ * Asynchronously doom an entry specified by the key. Listener will be
+ * notified about the status of the operation. Null may be passed if caller
+ * doesn't care about the result.
+ */
+ void doomEntry(in ACString key, in nsICacheListener listener);
+
+ /**
+ * Private entries will be doomed when the last private browsing session
+ * finishes.
+ */
+ attribute boolean isPrivate;
+};
diff --git a/netwerk/cache/nsICacheVisitor.idl b/netwerk/cache/nsICacheVisitor.idl
new file mode 100644
index 0000000000..56b503d430
--- /dev/null
+++ b/netwerk/cache/nsICacheVisitor.idl
@@ -0,0 +1,123 @@
+/* -*- 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"
+
+/* XXX we should define device and entry info as well (stats, etc) */
+
+interface nsICacheDeviceInfo;
+interface nsICacheEntryInfo;
+
+
+[scriptable, uuid(f8c08c4b-d778-49d1-a59b-866fdc500d95)]
+interface nsICacheVisitor : nsISupports
+{
+ /**
+ * Called to provide information about a cache device.
+ *
+ * @param deviceID - specifies the device being visited.
+ * @param deviceInfo - specifies information about this device.
+ *
+ * @return true to start visiting all entries for this device.
+ * @return false to advance to the next device.
+ */
+ boolean visitDevice(in string deviceID,
+ in nsICacheDeviceInfo deviceInfo);
+
+ /**
+ * Called to provide information about a cache entry.
+ *
+ * @param deviceID - specifies the device being visited.
+ * @param entryInfo - specifies information about this entry.
+ *
+ * @return true to visit the next entry on the current device, or if the
+ * end of the device has been reached, advance to the next device.
+ * @return false to advance to the next device.
+ */
+ boolean visitEntry(in string deviceID,
+ in nsICacheEntryInfo entryInfo);
+};
+
+
+[scriptable, uuid(31d1c294-1dd2-11b2-be3a-c79230dca297)]
+interface nsICacheDeviceInfo : nsISupports
+{
+ /**
+ * Get a human readable description of the cache device.
+ */
+ readonly attribute ACString description;
+
+ /**
+ * Get a usage report, statistics, miscellaneous data about
+ * the cache device.
+ */
+ readonly attribute ACString usageReport;
+
+ /**
+ * Get the number of stored cache entries.
+ */
+ readonly attribute unsigned long entryCount;
+
+ /**
+ * Get the total size of the stored cache entries.
+ */
+ readonly attribute unsigned long totalSize;
+
+ /**
+ * Get the upper limit of the size of the data the cache can store.
+ */
+ readonly attribute unsigned long maximumSize;
+};
+
+
+[scriptable, uuid(fab51c92-95c3-4468-b317-7de4d7588254)]
+interface nsICacheEntryInfo : nsISupports
+{
+ /**
+ * Get the client id associated with this cache entry.
+ */
+ readonly attribute ACString clientID;
+
+ /**
+ * Get the id for the device that stores this cache entry.
+ */
+ readonly attribute ACString deviceID;
+
+ /**
+ * Get the key identifying the cache entry.
+ */
+ readonly attribute ACString key;
+
+ /**
+ * Get the number of times the cache entry has been opened.
+ */
+ readonly attribute long 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;
+
+ /**
+ * Get the cache entry data size.
+ */
+ readonly attribute unsigned long dataSize;
+
+ /**
+ * Find out whether or not the cache entry is stream based.
+ */
+ boolean isStreamBased();
+};
diff --git a/netwerk/cache2/AppCacheStorage.cpp b/netwerk/cache2/AppCacheStorage.cpp
new file mode 100644
index 0000000000..3bff3bc086
--- /dev/null
+++ b/netwerk/cache2/AppCacheStorage.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 "CacheLog.h"
+#include "AppCacheStorage.h"
+#include "CacheStorageService.h"
+
+#include "OldWrappers.h"
+
+#include "nsICacheEntryDoomCallback.h"
+
+#include "nsCacheService.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheService.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla::net {
+
+AppCacheStorage::AppCacheStorage(nsILoadContextInfo* aInfo,
+ nsIApplicationCache* aAppCache)
+ : CacheStorage(aInfo, true /* disk */, false /* lookup app cache */,
+ false /* skip size check */, false /* pin */),
+ mAppCache(aAppCache) {}
+
+AppCacheStorage::~AppCacheStorage() {
+ ProxyReleaseMainThread("AppCacheStorage::mAppCache", mAppCache);
+}
+
+NS_IMETHODIMP AppCacheStorage::AsyncOpenURI(
+ nsIURI* aURI, const nsACString& aIdExtension, uint32_t aFlags,
+ nsICacheEntryOpenCallback* aCallback) {
+ if (!CacheStorageService::Self()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ if (!LoadInfo()) {
+ return NS_ERROR_CACHE_KEY_NOT_FOUND;
+ }
+
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG(aCallback);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIApplicationCache> appCache = mAppCache;
+
+ if (!appCache) {
+ rv = ChooseApplicationCache(aURI, getter_AddRefs(appCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!appCache) {
+ LOG(
+ ("AppCacheStorage::AsyncOpenURI entry not found in any appcache, "
+ "giving up"));
+ aCallback->OnCacheEntryAvailable(nullptr, false, nullptr,
+ NS_ERROR_CACHE_KEY_NOT_FOUND);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> noRefURI;
+ rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(noRefURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString cacheKey;
+ rv = noRefURI->GetAsciiSpec(cacheKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This is the only way how to recognize appcache data by the anonymous
+ // flag. There is no way to switch to e.g. a different session, because
+ // there is just a single session for an appcache version (identified
+ // by the client id).
+ if (LoadInfo()->IsAnonymous()) {
+ cacheKey = "anon&"_ns + cacheKey;
+ }
+
+ nsAutoCString scheme;
+ rv = noRefURI->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<_OldCacheLoad> appCacheLoad = new _OldCacheLoad(
+ scheme, cacheKey, aCallback, appCache, LoadInfo(), WriteToDisk(), aFlags);
+ rv = appCacheLoad->Start();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP AppCacheStorage::OpenTruncate(nsIURI* aURI,
+ const nsACString& aIdExtension,
+ nsICacheEntry** aCacheEntry) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP AppCacheStorage::Exists(nsIURI* aURI,
+ const nsACString& aIdExtension,
+ bool* aResult) {
+ *aResult = false;
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP AppCacheStorage::AsyncDoomURI(
+ nsIURI* aURI, const nsACString& aIdExtension,
+ nsICacheEntryDoomCallback* aCallback) {
+ if (!CacheStorageService::Self()) return NS_ERROR_NOT_INITIALIZED;
+
+ if (!mAppCache) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (!LoadInfo()) {
+ return NS_ERROR_CACHE_KEY_NOT_FOUND;
+ }
+
+ RefPtr<_OldStorage> old = new _OldStorage(LoadInfo(), WriteToDisk(),
+ LookupAppCache(), true, mAppCache);
+ return old->AsyncDoomURI(aURI, aIdExtension, aCallback);
+}
+
+NS_IMETHODIMP AppCacheStorage::AsyncEvictStorage(
+ nsICacheEntryDoomCallback* aCallback) {
+ if (!CacheStorageService::Self()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ if (!LoadInfo()) {
+ return NS_ERROR_CACHE_KEY_NOT_FOUND;
+ }
+
+ nsresult rv;
+
+ if (!mAppCache) {
+ // Discard everything under this storage context
+ nsCOMPtr<nsIApplicationCacheService> appCacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appCacheService->Evict(LoadInfo());
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // Discard the group
+ RefPtr<_OldStorage> old = new _OldStorage(
+ LoadInfo(), WriteToDisk(), LookupAppCache(), true, mAppCache);
+ rv = old->AsyncEvictStorage(aCallback);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ if (aCallback) aCallback->OnCacheEntryDoomed(NS_OK);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP AppCacheStorage::AsyncVisitStorage(
+ nsICacheStorageVisitor* aVisitor, bool aVisitEntries) {
+ if (!CacheStorageService::Self()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ LOG(("AppCacheStorage::AsyncVisitStorage [this=%p, cb=%p]", this, aVisitor));
+
+ nsresult rv;
+
+ nsCOMPtr<nsICacheService> serv =
+ do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<_OldVisitCallbackWrapper> cb = new _OldVisitCallbackWrapper(
+ "offline", aVisitor, aVisitEntries, LoadInfo());
+ rv = nsCacheService::GlobalInstance()->VisitEntriesInternal(cb);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP AppCacheStorage::GetCacheIndexEntryAttrs(
+ nsIURI* aURI, const nsACString& aIdExtension, bool* aHasAltData,
+ uint32_t* aSizeInKB) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/cache2/AppCacheStorage.h b/netwerk/cache2/AppCacheStorage.h
new file mode 100644
index 0000000000..b8dca276d1
--- /dev/null
+++ b/netwerk/cache2/AppCacheStorage.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/. */
+
+#ifndef AppCacheStorage__h__
+#define AppCacheStorage__h__
+
+#include "CacheStorage.h"
+
+#include "nsCOMPtr.h"
+#include "nsILoadContextInfo.h"
+#include "nsIApplicationCache.h"
+
+class nsIApplicationCache;
+
+namespace mozilla {
+namespace net {
+
+class AppCacheStorage : public CacheStorage {
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(AppCacheStorage, CacheStorage)
+ NS_DECL_NSICACHESTORAGE
+
+ public:
+ AppCacheStorage(nsILoadContextInfo* aInfo, nsIApplicationCache* aAppCache);
+
+ private:
+ virtual ~AppCacheStorage();
+
+ nsCOMPtr<nsIApplicationCache> mAppCache;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheEntry.cpp b/netwerk/cache2/CacheEntry.cpp
new file mode 100644
index 0000000000..d5a7bee65f
--- /dev/null
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -0,0 +1,1913 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "CacheEntry.h"
+#include "CacheStorageService.h"
+#include "CacheObserver.h"
+#include "CacheFileUtils.h"
+#include "CacheIndex.h"
+
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsIURI.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheStorage.h"
+#include "nsISerializable.h"
+#include "nsISizeOf.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsProxyRelease.h"
+#include "nsSerializationHelper.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include <math.h>
+#include <algorithm>
+
+namespace mozilla {
+namespace 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), mClosed(false) {
+#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(GetCurrentEventTarget()),
+ 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 == true),
+ mDoomWhenFoundNonPinned(aDoomWhenFoundInPinStatus == false) {
+ 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);
+}
+
+void CacheEntry::Callback::ExchangeEntry(CacheEntry* aEntry) {
+ 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;
+}
+
+bool CacheEntry::Callback::DeferDoom(bool* aDoom) const {
+ 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)
+ : mFrecency(0),
+ mSortingExpirationTime(uint32_t(-1)),
+ mLock("CacheEntry"),
+ mFileStatus(NS_ERROR_NOT_INITIALIZED),
+ mURI(aURI),
+ mEnhanceID(aEnhanceID),
+ mStorageID(aStorageID),
+ mUseDisk(aUseDisk),
+ mSkipSizeCheck(aSkipSizeCheck),
+ mIsDoomed(false),
+ mSecurityInfoLoaded(false),
+ mPreventCallbacks(false),
+ mHasData(false),
+ mPinned(aPin),
+ mPinningKnown(false),
+ mState(NOTLOADED),
+ mRegistration(NEVERREGISTERED),
+ mWriter(nullptr),
+ mUseCount(0),
+ 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) {
+ LOG(("CacheEntry::AsyncOpen [this=%p, state=%s, flags=%d, callback=%p]", this,
+ StateString(mState), aFlags, aCallback));
+
+ 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;
+
+ MOZ_ASSERT(!readonly || !truncate, "Bad flags combination");
+ MOZ_ASSERT(!(truncate && mState > LOADING),
+ "Must not call truncate on already loaded entry");
+
+ 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", 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,
+ true, // 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);
+
+ 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) {
+ LOG(("CacheEntry::InvokeCallback [this=%p, state=%s, cb=%p]", this,
+ StateString(mState), aCallback.mCallback.get()));
+
+ mLock.AssertCurrentThreadOwns();
+
+ // 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, nullptr,
+ &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) {
+ 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));
+
+ nsresult rv;
+
+ uint32_t state;
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ state = mState;
+ }
+
+ // 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, nullptr,
+ 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, nullptr, 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, nullptr,
+ 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,
+ nullptr, 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) {
+ LOG(("CacheEntry::OnHandleClosed [this=%p, state=%s, handle=%p]", this,
+ StateString(mState), aHandle));
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (IsDoomed() && 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(int32_t* aFetchCount) {
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ return mFile->GetFetchCount(reinterpret_cast<uint32_t*>(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);
+
+ MOZ_ASSERT(mState > LOADING);
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ 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::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(nsISupports** aSecurityInfo) {
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ if (mSecurityInfoLoaded) {
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+ return NS_OK;
+ }
+ }
+
+ NS_ENSURE_SUCCESS(mFileStatus, NS_ERROR_NOT_AVAILABLE);
+
+ nsCString info;
+ nsCOMPtr<nsISupports> secInfo;
+ nsresult rv;
+
+ rv = mFile->GetElement("security-info", getter_Copies(info));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!info.IsVoid()) {
+ rv = NS_DeserializeObject(info, getter_AddRefs(secInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+
+ mSecurityInfo.swap(secInfo);
+ mSecurityInfoLoaded = true;
+
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+ }
+
+ return NS_OK;
+}
+nsresult CacheEntry::SetSecurityInfo(nsISupports* aSecurityInfo) {
+ nsresult rv;
+
+ NS_ENSURE_SUCCESS(mFileStatus, mFileStatus);
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+
+ mSecurityInfo = aSecurityInfo;
+ mSecurityInfoLoaded = true;
+ }
+
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(aSecurityInfo);
+ if (aSecurityInfo && !serializable) return NS_ERROR_UNEXPECTED;
+
+ nsCString info;
+ if (serializable) {
+ rv = NS_SerializeToString(serializable, 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() {
+ LOG(("CacheEntry::SetValid [this=%p, state=%s]", this, StateString(mState)));
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+
+ 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) {
+ LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState)));
+
+ mozilla::MutexAutoLock lock(mLock);
+
+ 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", 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.
+ NS_DispatchToCurrentThread(this);
+}
+
+NS_IMETHODIMP CacheOutputCloseListener::Run() {
+ mEntry->OnOutputClosed();
+ return NS_OK;
+}
+
+// Memory reporting
+
+size_t CacheEntry::SizeOfExcludingThis(
+ mozilla::MallocSizeOf mallocSizeOf) const {
+ size_t n = 0;
+
+ 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) const {
+ return mallocSizeOf(this) + SizeOfExcludingThis(mallocSizeOf);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheEntry.h b/netwerk/cache2/CacheEntry.h
new file mode 100644
index 0000000000..ce93bbb105
--- /dev/null
+++ b/netwerk/cache2/CacheEntry.h
@@ -0,0 +1,573 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsICacheEntry.h"
+#include "CacheFile.h"
+
+#include "nsIRunnable.h"
+#include "nsIOutputStream.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheEntryDoomCallback.h"
+
+#include "nsCOMPtr.h"
+#include "nsRefPtrHashtable.h"
+#include "nsDataHashtable.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 {
+ 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* aPersistent);
+ nsresult GetFetchCount(int32_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* aOnStartTime);
+ nsresult GetOnStopTime(uint64_t* aOnStopTime);
+ nsresult SetNetworkTimes(uint64_t onStartTime, uint64_t onStopTime);
+ nsresult SetContentType(uint8_t aContentType);
+ nsresult ForceValidFor(uint32_t aSecondsToTheFuture);
+ nsresult GetIsForcedValid(bool* aIsForcedValid);
+ nsresult OpenInputStream(int64_t offset, nsIInputStream** _retval);
+ nsresult OpenOutputStream(int64_t offset, int64_t predictedSize,
+ nsIOutputStream** _retval);
+ nsresult GetSecurityInfo(nsISupports** aSecurityInfo);
+ nsresult SetSecurityInfo(nsISupports* aSecurityInfo);
+ nsresult GetStorageDataSize(uint32_t* aStorageDataSize);
+ nsresult AsyncDoom(nsICacheEntryDoomCallback* listener);
+ nsresult GetMetaDataElement(const char* key, char** _retval);
+ 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* aAltDataSize);
+ nsresult GetAltDataType(nsACString& aAltDataType);
+ nsresult OpenAlternativeOutputStream(const nsACString& type,
+ int64_t predictedSize,
+ nsIAsyncOutputStream** _retval);
+ nsresult OpenAlternativeInputStream(const nsACString& type,
+ nsIInputStream** _retval);
+ nsresult GetLoadContextInfo(nsILoadContextInfo** aLoadContextInfo);
+ nsresult Close(void);
+ nsresult MarkValid(void);
+ nsresult MaybeMarkValid(void);
+ nsresult HasWriteAccess(bool aWriteAllowed, bool* _retval);
+
+ 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;
+ 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;
+ ::mozilla::Atomic<uint32_t, ::mozilla::Relaxed> mSortingExpirationTime;
+
+ // Memory reporting
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ 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);
+
+ // Returns true when an entry is about to be "defer" doomed and this is
+ // a "defer" callback.
+ 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);
+ 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() { ++mHandlesCount; }
+ void ReleaseHandleRef() { --mHandlesCount; }
+ // Current number of handles keeping this entry.
+ uint32_t HandlesCount() const { 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();
+ // 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;
+
+ // Reflects the number of existing handles for this entry
+ ::mozilla::ThreadSafeAutoRefCnt mHandlesCount;
+
+ nsTArray<Callback> mCallbacks;
+ nsCOMPtr<nsICacheEntryDoomCallback> mDoomCallback;
+
+ 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().
+ ::mozilla::Atomic<nsresult, ::mozilla::ReleaseAcquire> mFileStatus;
+ nsCString mURI;
+ nsCString mEnhanceID;
+ nsCString 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().
+ bool mIsDoomed;
+
+ // Following flags are all synchronized with the cache entry lock.
+
+ // Whether security info has already been looked up in metadata.
+ bool mSecurityInfoLoaded : 1;
+ // Prevents any callback invocation
+ bool mPreventCallbacks : 1;
+ // 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;
+ // The indication of pinning this entry was open with
+ bool mPinned : 1;
+ // Whether the pinning state of the entry is known (equals to the actual state
+ // of the cache file)
+ bool mPinningKnown : 1;
+
+ 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;
+
+ 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;
+
+ // 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;
+
+ // 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;
+
+ // 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() : mFlags(0) {}
+ 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;
+ } mBackgroundOperations;
+
+ nsCOMPtr<nsISupports> mSecurityInfo;
+ mozilla::TimeStamp mLoadStart;
+ uint32_t mUseCount;
+
+ 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(int32_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 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(nsISupports** aSecurityInfo) override {
+ return mEntry->GetSecurityInfo(aSecurityInfo);
+ }
+ NS_IMETHOD SetSecurityInfo(nsISupports* 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;
+};
+
+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..262c2a762c
--- /dev/null
+++ b/netwerk/cache2/CacheFile.cpp
@@ -0,0 +1,2596 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "CacheLog.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Telemetry.h"
+#include "nsComponentManagerUtils.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 {
+namespace net {
+
+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("CacheFile.mLock"),
+ mOpeningFile(false),
+ mReady(false),
+ mMemoryOnly(false),
+ mSkipSizeCheck(false),
+ mOpenAsMemoryOnly(false),
+ mPinned(false),
+ mPriority(false),
+ mDataAccessed(false),
+ mDataIsDirty(false),
+ mWritingMetadata(false),
+ mPreloadWithoutInputStreams(true),
+ mPreloadChunkCount(0),
+ mStatus(NS_OK),
+ mDataSize(-1),
+ mAltDataOffset(-1),
+ mKill(false),
+ mOutput(nullptr) {
+ LOG(("CacheFile::CacheFile() [this=%p]", this));
+}
+
+CacheFile::~CacheFile() {
+ LOG(("CacheFile::~CacheFile() [this=%p]", this));
+
+ MutexAutoLock lock(mLock);
+ 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_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);
+ 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);
+ 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);
+ 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;
+}
+
+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);
+
+ 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);
+ 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.Put(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) {
+ listener->OnFileReady(retval, isNew);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(NS_SUCCEEDED(aResult));
+ MOZ_ASSERT(!mMetadata);
+ MOZ_ASSERT(mListener);
+
+ mMetadata = new CacheFileMetadata(mHandle, mKey);
+ mMetadata->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) {
+ MOZ_ASSERT(mListener);
+
+ LOG(("CacheFile::OnMetadataRead() [this=%p, rv=0x%08" PRIx32 "]", this,
+ static_cast<uint32_t>(aResult)));
+
+ bool isNew = false;
+ 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 {
+ CacheFileAutoLock lock(this);
+ PreloadChunks(0);
+ }
+ }
+
+ InitIndexEntry();
+ }
+
+ nsCOMPtr<CacheFileListener> listener;
+ 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) {
+ 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) {
+ 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::Lock() { mLock.Lock(); }
+
+void CacheFile::Unlock() {
+ // move the elements out of mObjsToRelease
+ // so that they can be released after we unlock
+ nsTArray<RefPtr<nsISupports>> objs = std::move(mObjsToRelease);
+
+ mLock.Unlock();
+}
+
+void CacheFile::AssertOwnsLock() const { mLock.AssertCurrentThreadOwns(); }
+
+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.Put(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.Put(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;
+ } else if (off == mDataSize) {
+ if (aCaller == WRITER) {
+ // this listener is going to write to the chunk
+ chunk = new CacheFileChunk(this, aIndex, true);
+ mChunks.Put(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.Put(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_DISK_FULL:
+ 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 = GetMainThreadEventTarget();
+ }
+ item->mCallback = aCallback;
+
+ ChunkListeners* listeners;
+ if (!mChunkListeners.Get(aIndex, &listeners)) {
+ listeners = new ChunkListeners();
+ mChunkListeners.Put(aIndex, listeners);
+ }
+
+ listeners->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) {
+ 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 (auto iter = mChunks.Iter(); !iter.Done(); iter.Next()) {
+ const RefPtr<CacheFileChunk>& chunk = iter.Data();
+ LOG(
+ ("CacheFile::NotifyListenersAboutOutputRemoval() - fail2 "
+ "[this=%p, idx=%u]",
+ this, iter.Key()));
+
+ 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;
+ }
+
+ if (CacheObserver::EntryIsTooBig(totalSize, !mMemoryOnly)) {
+ return true;
+ }
+
+ return false;
+}
+
+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() {
+ 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) ? true : false;
+
+ 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 (auto iter = mChunks.ConstIter(); !iter.Done(); iter.Next()) {
+ n += iter.Data()->SizeOfIncludingThis(mallocSizeOf);
+ }
+ n += mCachedChunks.ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (auto iter = mCachedChunks.ConstIter(); !iter.Done(); iter.Next()) {
+ n += iter.Data()->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 net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheFile.h b/netwerk/cache2/CacheFile.h
new file mode 100644
index 0000000000..9b33c5dcd4
--- /dev/null
+++ b/netwerk/cache2/CacheFile.h
@@ -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/. */
+
+#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 nsIInputStream;
+class nsIOutputStream;
+class nsICacheEntryMetaDataVisitor;
+
+namespace mozilla {
+namespace net {
+
+class CacheFileInputStream;
+class CacheFileOutputStream;
+class CacheOutputCloseListener;
+class MetadataWriteTimer;
+
+#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 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) { aKey = mKey; }
+ bool IsDoomed();
+ bool IsPinned() const { return mPinned; }
+ // 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();
+ void Unlock();
+ void AssertOwnsLock() const;
+ 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();
+ void WriteMetadataIfNeeded();
+ void WriteMetadataIfNeededLocked(bool aFireAndForget = false);
+ void PostWriteTimer();
+
+ void CleanUpCachedChunks();
+
+ nsresult PadChunkWithZeroes(uint32_t aChunkIdx);
+
+ void SetError(nsresult aStatus);
+ nsresult SetAltMetadata(const char* aAltMetadata);
+
+ nsresult InitIndexEntry();
+
+ mozilla::Mutex mLock;
+ bool mOpeningFile;
+ bool mReady;
+ bool mMemoryOnly;
+ bool mSkipSizeCheck;
+ bool mOpenAsMemoryOnly;
+ bool mPinned;
+ bool mPriority;
+ bool mDataAccessed;
+ bool mDataIsDirty;
+ bool mWritingMetadata;
+ bool mPreloadWithoutInputStreams;
+ uint32_t mPreloadChunkCount;
+ nsresult mStatus;
+ int64_t mDataSize; // Size of the whole data including eventual
+ // alternative data represenation.
+ int64_t mAltDataOffset; // If there is alternative data present, it
+ // contains size of the original data, i.e.
+ // offset where alternative data starts.
+ // Otherwise it is -1.
+ nsCString mKey;
+ nsCString mAltDataType; // The type of the saved alt-data. May be empty.
+
+ RefPtr<CacheFileHandle> mHandle;
+ RefPtr<CacheFileMetadata> mMetadata;
+ nsCOMPtr<CacheFileListener> mListener;
+ nsCOMPtr<CacheFileIOListener> mDoomAfterOpenListener;
+ Atomic<bool, Relaxed> mKill;
+
+ nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mChunks;
+ nsClassHashtable<nsUint32HashKey, ChunkListeners> mChunkListeners;
+ nsRefPtrHashtable<nsUint32HashKey, CacheFileChunk> mCachedChunks;
+ // 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;
+
+ nsTArray<CacheFileInputStream*> mInputs;
+ CacheFileOutputStream* mOutput;
+
+ nsTArray<RefPtr<nsISupports>> mObjsToRelease;
+};
+
+class CacheFileAutoLock {
+ public:
+ explicit CacheFileAutoLock(CacheFile* aFile) : mFile(aFile), mLocked(true) {
+ mFile->Lock();
+ }
+ ~CacheFileAutoLock() {
+ if (mLocked) mFile->Unlock();
+ }
+ void Lock() {
+ MOZ_ASSERT(!mLocked);
+ mFile->Lock();
+ mLocked = true;
+ }
+ void Unlock() {
+ 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..faf8b9c78c
--- /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 {
+namespace 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();
+
+ 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 = GetMainThreadEventTarget();
+ }
+ 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();
+
+ // 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 net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheFileChunk.h b/netwerk/cache2/CacheFileChunk.h
new file mode 100644
index 0000000000..ea82fb3773
--- /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 aSize);
+ 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..1dc2af5848
--- /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 "CacheIndex.h"
+#include "CacheIndexIterator.h"
+#include "CacheFileUtils.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 {
+namespace net {
+
+#define CONTEXT_EVICTION_PREFIX "ce_"
+const uint32_t kContextEvictionPrefixLength =
+ sizeof(CONTEXT_EVICTION_PREFIX) - 1;
+
+bool CacheFileContextEvictor::sDiskAlreadySearched = false;
+
+CacheFileContextEvictor::CacheFileContextEvictor()
+ : mEvicting(false), mIndexIsUpToDate(false) {
+ 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;
+ } else 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::GetUTFOrigin(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 net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheFileContextEvictor.h b/netwerk/cache2/CacheFileContextEvictor.h
new file mode 100644
index 0000000000..3637cc37e6
--- /dev/null
+++ b/netwerk/cache2/CacheFileContextEvictor.h
@@ -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/. */
+
+#ifndef CacheFileContextEvictor__h__
+#define CacheFileContextEvictor__h__
+
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+
+class nsIFile;
+class nsILoadContextInfo;
+
+namespace mozilla {
+namespace net {
+
+class CacheIndexIterator;
+
+struct CacheFileContextEvictorEntry {
+ nsCOMPtr<nsILoadContextInfo> mInfo;
+ bool mPinned;
+ nsString mOrigin; // it can be empty
+ PRTime mTimeStamp; // 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;
+ // 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;
+ // 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..5ffd12a9c0
--- /dev/null
+++ b/netwerk/cache2/CacheFileIOManager.cpp
@@ -0,0 +1,4225 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "CacheFileIOManager.h"
+
+#include "../cache/nsCacheUtils.h"
+#include "CacheHashUtils.h"
+#include "CacheStorageService.h"
+#include "CacheIndex.h"
+#include "CacheFileUtils.h"
+#include "nsThreadUtils.h"
+#include "CacheFile.h"
+#include "CacheObserver.h"
+#include "nsIFile.h"
+#include "CacheFileContextEvictor.h"
+#include "nsITimer.h"
+#include "nsIDirectoryEnumerator.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 "nsDirectoryServiceUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "private/pprio.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Preferences.h"
+#include "nsNetUtil.h"
+
+// 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 {
+namespace 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
+
+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);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return true;
+}
+
+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 {
+ public:
+ ShutdownEvent()
+ : Runnable("net::ShutdownEvent"),
+ mMonitor("ShutdownEvent.mMonitor"),
+ mNotified(false) {}
+
+ protected:
+ ~ShutdownEvent() = default;
+
+ public:
+ NS_IMETHOD Run() override {
+ MonitorAutoLock mon(mMonitor);
+
+ CacheFileIOManager::gInstance->ShutdownInternal();
+
+ mNotified = true;
+ mon.Notify();
+
+ return NS_OK;
+ }
+
+ void PostAndWait() {
+ MonitorAutoLock mon(mMonitor);
+
+ 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;
+ }
+
+ TimeDuration waitTime = TimeDuration::FromSeconds(1);
+ while (!mNotified) {
+ mon.Wait(waitTime);
+ if (!mNotified) {
+ // If there is any IO blocking on the IO thread, this will
+ // try to cancel it. Returns no later than after two seconds.
+ MonitorAutoUnlock unmon(mMonitor); // Prevent delays
+ CacheFileIOManager::gInstance->mIOThread->CancelBlockingIO();
+ }
+ }
+ }
+
+ protected:
+ mozilla::Monitor mMonitor;
+ bool mNotified;
+};
+
+// 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 ? false : true;
+
+ 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()
+ : mShuttingDown(false),
+ mTreeCreated(false),
+ mTreeCreationFailed(false),
+ mOverLimitEvicting(false),
+ mCacheSizeOnHardLimit(false),
+ mRemovingTrashDirs(false) {
+ 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
+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) !=
+ mScheduledMetadataWrites.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_DISK_FULL;
+ }
+
+ 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_DISK_FULL;
+ }
+ }
+ }
+
+ // 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 ||
+ NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == 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();
+ uint32_t fetchCount = metadata->GetFetchCount();
+ uint32_t expirationTime = metadata->GetExpirationTime();
+ uint32_t lastModified = metadata->GetLastModified();
+
+ // Call directly on the callback.
+ aCallback->OnEntryInfo(uriSpec, enhanceId, dataSize, 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_DISK_FULL;
+ }
+
+ 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_DISK_FULL;
+ }
+ }
+ }
+
+ // 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) {
+ 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>(
+ "net::CacheFileIOManager::EvictByContextInternal", ioMan,
+ &CacheFileIOManager::EvictByContextInternal, aLoadContextInfo, aPinned,
+ aOrigin);
+
+ 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) {
+ 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);
+
+ // 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];
+
+ 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!");
+ }
+
+ if (aLoadContextInfo && !info->Equals(aLoadContextInfo)) {
+ continue;
+ }
+
+ if (!origin.IsEmpty()) {
+ RefPtr<MozURL> url;
+ rv = MozURL::Init(getter_AddRefs(url), uriSpec);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsAutoCString urlOrigin;
+ url->Origin(urlOrigin);
+
+ if (!urlOrigin.Equals(origin)) {
+ 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;
+
+ // 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"));
+
+ // Report the full size only once per session
+ static bool sSizeReported = false;
+ if (!sSizeReported) {
+ uint32_t cacheUsage;
+ if (NS_SUCCEEDED(CacheIndex::GetCacheSize(&cacheUsage))) {
+ cacheUsage >>= 10;
+ Telemetry::Accumulate(Telemetry::NETWORK_CACHE_SIZE_FULL_FAT,
+ cacheUsage);
+ sSizeReported = true;
+ }
+ }
+ } 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 (!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;
+}
+
+void CacheFileIOManager::SyncRemoveAllCacheFiles() {
+ LOG(("CacheFileIOManager::SyncRemoveAllCacheFiles()"));
+
+ nsresult rv;
+
+ 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;
+ 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 net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheFileIOManager.h b/netwerk/cache2/CacheFileIOManager.h
new file mode 100644
index 0000000000..579e58e328
--- /dev/null
+++ b/netwerk/cache2/CacheFileIOManager.h
@@ -0,0 +1,479 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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* aHandlle);
+ 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:
+ typedef const SHA1Sum::Hash& KeyType;
+ typedef const SHA1Sum::Hash* KeyTypePointer;
+
+ 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 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 aPinning, const nsAString& aOrigin);
+
+ 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 aPinningStatusRestriction = 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 aPinning, const nsAString& aOrigin);
+
+ 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();
+
+ // 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;
+ 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;
+ bool mTreeCreationFailed;
+ CacheFileHandles mHandles;
+ nsTArray<CacheFileHandle*> mHandlesByLastUsed;
+ nsTArray<CacheFileHandle*> mSpecialHandles;
+ nsTArray<RefPtr<CacheFile> > mScheduledMetadataWrites;
+ nsCOMPtr<nsITimer> mMetadataWritesTimer;
+ bool mOverLimitEvicting;
+ // 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;
+ bool mRemovingTrashDirs;
+ 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..dc6d0208e6
--- /dev/null
+++ b/netwerk/cache2/CacheFileInputStream.cpp
@@ -0,0 +1,719 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 {
+namespace 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::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);
+
+ 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);
+
+ 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 = GetMainThreadEventTarget();
+ }
+ }
+
+ 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 net
+} // namespace mozilla
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..738a1e9f49
--- /dev/null
+++ b/netwerk/cache2/CacheFileMetadata.cpp
@@ -0,0 +1,1049 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "../cache/nsCacheUtils.h"
+#include "nsIFile.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "prnetdb.h"
+
+namespace mozilla {
+namespace 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)
+ : CacheMemoryConsumer(NORMAL),
+ mHandle(aHandle),
+ mHashArray(nullptr),
+ mHashArraySize(0),
+ mHashCount(0),
+ mOffset(-1),
+ mBuf(nullptr),
+ mBufSize(0),
+ mWriteBuf(nullptr),
+ mElementsSize(0),
+ mIsDirty(false),
+ mAnonymous(false),
+ mAllocExactSize(false),
+ mFirstRead(true) {
+ 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)
+ : CacheMemoryConsumer(aMemoryOnly ? MEMORY_ONLY : NORMAL),
+ mHandle(nullptr),
+ mHashArray(nullptr),
+ mHashArraySize(0),
+ mHashCount(0),
+ mOffset(0),
+ mBuf(nullptr),
+ mBufSize(0),
+ mWriteBuf(nullptr),
+ mElementsSize(0),
+ mIsDirty(true),
+ mAnonymous(false),
+ mAllocExactSize(false),
+ mFirstRead(true) {
+ 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 */),
+ mHandle(nullptr),
+ mHashArray(nullptr),
+ mHashArraySize(0),
+ mHashCount(0),
+ mOffset(0),
+ mBuf(nullptr),
+ mBufSize(0),
+ mWriteBuf(nullptr),
+ mElementsSize(0),
+ mIsDirty(false),
+ mAnonymous(false),
+ mAllocExactSize(false),
+ mFirstRead(true) {
+ 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));
+
+ 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) {
+ 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));
+
+ MarkDirty();
+
+ MOZ_ASSERT(aIndex <= mHashCount);
+
+ if (aIndex > mHashCount) {
+ return NS_ERROR_INVALID_ARG;
+ } else 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));
+
+ 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)));
+
+ MOZ_ASSERT(mListener);
+ MOZ_ASSERT(mWriteBuf);
+
+ CacheFileUtils::FreeBuffer(mWriteBuf);
+ mWriteBuf = nullptr;
+
+ nsCOMPtr<CacheFileMetadataListener> listener;
+
+ mListener.swap(listener);
+ listener->OnMetadataWritten(aResult);
+
+ DoMemoryReport(MemoryUsage());
+
+ 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;
+
+ 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);
+ listener->OnMetadataRead(NS_OK);
+ 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);
+ listener->OnMetadataRead(NS_OK);
+ 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);
+ listener->OnMetadataRead(NS_OK);
+ 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);
+ listener->OnMetadataRead(NS_OK);
+ 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);
+ listener->OnMetadataRead(NS_OK);
+ 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);
+ listener->OnMetadataRead(NS_OK);
+
+ 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 net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheFileMetadata.h b/netwerk/cache2/CacheFileMetadata.h
new file mode 100644
index 0000000000..32ac9ef0f9
--- /dev/null
+++ b/netwerk/cache2/CacheFileMetadata.h
@@ -0,0 +1,237 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsString.h"
+
+class nsICacheEntryMetaDataVisitor;
+
+namespace mozilla {
+namespace net {
+
+// 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);
+ CacheFileMetadata(bool aMemoryOnly, bool aPinned, const nsACString& aKey);
+ 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;
+ uint32_t mHashArraySize;
+ uint32_t mHashCount;
+ int64_t mOffset;
+ char* mBuf; // used for parsing, then points
+ // to elements
+ uint32_t mBufSize;
+ char* mWriteBuf;
+ CacheFileMetadataHeader mMetaHdr;
+ uint32_t mElementsSize;
+ bool mIsDirty : 1;
+ bool mAnonymous : 1;
+ bool mAllocExactSize : 1;
+ bool mFirstRead : 1;
+ mozilla::OriginAttributes mOriginAttributes;
+ mozilla::TimeStamp mReadStart;
+ nsCOMPtr<CacheFileMetadataListener> mListener;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheFileOutputStream.cpp b/netwerk/cache2/CacheFileOutputStream.cpp
new file mode 100644
index 0000000000..9351765038
--- /dev/null
+++ b/netwerk/cache2/CacheFileOutputStream.cpp
@@ -0,0 +1,466 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 {
+namespace 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::Write(const char* aBuf, uint32_t aCount,
+ uint32_t* _retval) {
+ CacheFileAutoLock lock(mFile);
+
+ 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);
+
+ 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);
+
+ 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() {
+ 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 = GetMainThreadEventTarget();
+ }
+ }
+
+ 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 net
+} // namespace mozilla
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..7d208094bf
--- /dev/null
+++ b/netwerk/cache2/CacheFileUtils.cpp
@@ -0,0 +1,669 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "LoadContextInfo.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/Telemetry.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include <algorithm>
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+namespace 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::Telemetry::AccumulateTimeDelta(
+ mozilla::Telemetry::NETWORK_CACHE_V2_HIT_TIME_MS, 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) {
+ *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 CacheFileUtils
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheFileUtils.h b/netwerk/cache2/CacheFileUtils.h
new file mode 100644
index 0000000000..57e251cf14
--- /dev/null
+++ b/netwerk/cache2/CacheFileUtils.h
@@ -0,0 +1,224 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/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;
+ uint32_t mMissCnt;
+ };
+
+ // 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, sHitStats and Telemetry::Accumulated() calls.
+ static StaticMutex sLock;
+
+ // Counter of samples that is compared against kTotalSamplesReportLimit.
+ static uint32_t sRecordCnt;
+
+ // Hit rate statistics for every cache size range.
+ static HitRate sHRStats[kNumOfRanges];
+};
+
+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];
+ static uint32_t sCacheSlowCnt;
+ static uint32_t sCacheNotSlowCnt;
+};
+
+void FreeBuffer(void* aBuf);
+
+nsresult ParseAlternativeDataInfo(const char* aInfo, int64_t* _offset,
+ nsACString* _type);
+
+void BuildAlternativeDataInfo(const char* aInfo, int64_t aOffset,
+ nsACString& _retval);
+
+} // 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..1a33b00603
--- /dev/null
+++ b/netwerk/cache2/CacheHashUtils.cpp
@@ -0,0 +1,235 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "plstr.h"
+
+namespace mozilla {
+namespace 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)
+ : mA(0x9e3779b9),
+ mB(0x9e3779b9),
+ mC(aInitval),
+ mPos(0),
+ mBuf(0),
+ mBufPos(0),
+ mLength(0),
+ mFinalized(false) {}
+
+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 net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheHashUtils.h b/netwerk/cache2/CacheHashUtils.h
new file mode 100644
index 0000000000..574e0aff0a
--- /dev/null
+++ b/netwerk/cache2/CacheHashUtils.h
@@ -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/. */
+
+#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
+
+ typedef uint16_t Hash16_t;
+ typedef uint32_t Hash32_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);
+
+ uint32_t mA, mB, mC;
+ uint8_t mPos;
+ uint32_t mBuf;
+ uint8_t mBufPos;
+ uint32_t mLength;
+ bool mFinalized;
+};
+
+typedef uint64_t OriginAttrsHash;
+
+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..66f4300c72
--- /dev/null
+++ b/netwerk/cache2/CacheIOThread.cpp
@@ -0,0 +1,641 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "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/ThreadEventQueue.h"
+#include "GeckoProfiler.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+#endif
+
+#ifdef MOZ_TASK_TRACER
+# include "GeckoTaskTracer.h"
+# include "TracedTaskCommon.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+namespace { // anon
+
+class CacheIOTelemetry {
+ public:
+ typedef CacheIOThread::EventQueue::size_type 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 BlockingIOWatcher {
+#ifdef XP_WIN
+ // The native handle to the thread
+ HANDLE mThread;
+ // Event signaling back to the main thread, see NotifyOperationDone.
+ HANDLE mEvent;
+#endif
+
+ public:
+ // Created and destroyed on the main thread only
+ BlockingIOWatcher();
+ ~BlockingIOWatcher();
+
+ // 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.
+ // Waits for notification from the IO thread for up to two seconds.
+ // If that times out, it attempts to cancel the IO operation.
+ void WatchAndCancel(Monitor& aMonitor);
+ // Called by the IO thread after each operation has been
+ // finished (after each Run() call). This wakes the main
+ // thread up and makes WatchAndCancel() early exit and become
+ // a no-op.
+ void NotifyOperationDone();
+};
+
+#ifdef XP_WIN
+
+BlockingIOWatcher::BlockingIOWatcher() : mThread(NULL), mEvent(NULL) {
+ HMODULE kernel32_dll = GetModuleHandleW(L"kernel32.dll");
+ if (!kernel32_dll) {
+ return;
+ }
+
+ mEvent = ::CreateEventW(NULL, TRUE, FALSE, NULL);
+}
+
+BlockingIOWatcher::~BlockingIOWatcher() {
+ if (mEvent) {
+ CloseHandle(mEvent);
+ }
+ if (mThread) {
+ CloseHandle(mThread);
+ }
+}
+
+void BlockingIOWatcher::InitThread() {
+ // GetCurrentThread() only returns a pseudo handle, hence DuplicateHandle
+ ::DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
+ GetCurrentProcess(), &mThread, 0, FALSE,
+ DUPLICATE_SAME_ACCESS);
+}
+
+void BlockingIOWatcher::WatchAndCancel(Monitor& aMonitor) {
+ if (!mEvent) {
+ return;
+ }
+
+ // Reset before we enter the monitor to raise the chance we catch
+ // the currently pending IO op completion.
+ ::ResetEvent(mEvent);
+
+ HANDLE thread;
+ {
+ MonitorAutoLock lock(aMonitor);
+ thread = mThread;
+
+ if (!thread) {
+ return;
+ }
+ }
+
+ LOG(("Blocking IO operation pending on IO thread, waiting..."));
+
+ // It seems wise to use the I/O lag time as a maximum time to wait
+ // for an operation to finish. When that times out and cancelation
+ // succeeds, there will be no other IO operation permitted. By default
+ // this is two seconds.
+ uint32_t maxLag =
+ std::min<uint32_t>(5, CacheObserver::MaxShutdownIOLag()) * 1000;
+
+ DWORD result = ::WaitForSingleObject(mEvent, maxLag);
+ if (result == WAIT_TIMEOUT) {
+ 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=%u", error));
+ }
+ }
+}
+
+void BlockingIOWatcher::NotifyOperationDone() {
+ if (mEvent) {
+ ::SetEvent(mEvent);
+ }
+}
+
+#else // WIN
+
+// Stub code only (we don't implement IO cancelation for this platform)
+
+BlockingIOWatcher::BlockingIOWatcher() = default;
+BlockingIOWatcher::~BlockingIOWatcher() = default;
+void BlockingIOWatcher::InitThread() {}
+void BlockingIOWatcher::WatchAndCancel(Monitor&) {}
+void BlockingIOWatcher::NotifyOperationDone() {}
+
+#endif
+
+} // namespace detail
+
+CacheIOThread* CacheIOThread::sSelf = nullptr;
+
+NS_IMPL_ISUPPORTS(CacheIOThread, nsIThreadObserver)
+
+CacheIOThread::CacheIOThread()
+ : mMonitor("CacheIOThread"),
+ mThread(nullptr),
+ mXPCOMThread(nullptr),
+ mLowestLevelWaiting(LAST_LEVEL),
+ mCurrentlyExecutingLevel(0),
+ mHasXPCOMEvents(false),
+ mRerunCurrentEvent(false),
+ mShutdown(false),
+ mIOCancelableEvents(0),
+ mEventCounter(0)
+#ifdef DEBUG
+ ,
+ mInsideLoop(true)
+#endif
+{
+ for (auto& item : mQueueLength) {
+ item = 0;
+ }
+
+ sSelf = this;
+}
+
+CacheIOThread::~CacheIOThread() {
+ 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.
+ mBlockingIOWatcher = MakeUnique<detail::BlockingIOWatcher>();
+ }
+
+ mThread =
+ PR_CreateThread(PR_USER_THREAD, ThreadFunc, this, PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 128 * 1024);
+ if (!mThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ 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);
+#ifdef MOZ_TASK_TRACER
+ if (tasktracer::IsStartLogging()) {
+ runnable = tasktracer::CreateTracedRunnable(runnable.forget());
+ (static_cast<tasktracer::TracedRunnable*>(runnable.get()))->DispatchTask();
+ }
+#endif
+
+ 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 (!mBlockingIOWatcher) {
+ 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.
+ mBlockingIOWatcher->WatchAndCancel(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();
+ CacheIOThread* thread = static_cast<CacheIOThread*>(aClosure);
+ thread->ThreadFunc();
+ mozilla::IOInterposer::UnregisterCurrentThread();
+}
+
+void CacheIOThread::ThreadFunc() {
+ nsCOMPtr<nsIThreadInternal> threadInternal;
+
+ {
+ MonitorAutoLock lock(mMonitor);
+
+ MOZ_ASSERT(mBlockingIOWatcher);
+ mBlockingIOWatcher->InitThread();
+
+ auto queue =
+ MakeRefPtr<ThreadEventQueue>(MakeUnique<mozilla::EventQueue>());
+ nsCOMPtr<nsIThread> xpcomThread =
+ nsThreadManager::get().CreateCurrentThread(queue,
+ nsThread::NOT_MAIN_THREAD);
+
+ threadInternal = do_QueryInterface(xpcomThread);
+ if (threadInternal) threadInternal->SetObserver(this);
+
+ mXPCOMThread = xpcomThread.forget().take();
+
+ 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 {
+ nsIThread* thread = mXPCOMThread;
+ rv = thread->ProcessNextEvent(false, &processedEvent);
+
+ ++mEventCounter;
+ MOZ_ASSERT(mBlockingIOWatcher);
+ mBlockingIOWatcher->NotifyOperationDone();
+ } 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) {
+ 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(mBlockingIOWatcher);
+ mBlockingIOWatcher->NotifyOperationDone();
+
+ 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 net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheIOThread.h b/netwerk/cache2/CacheIOThread.h
new file mode 100644
index 0000000000..3c7b11cbf8
--- /dev/null
+++ b/netwerk/cache2/CacheIOThread.h
@@ -0,0 +1,147 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 watch and
+// cancel any long blocking synchronous IO. Must be predeclared here
+// since including windows.h breaks stuff with number of macro definition
+// conflicts.
+class BlockingIOWatcher;
+} // namespace detail
+
+class CacheIOThread final : public nsIThreadObserver {
+ virtual ~CacheIOThread();
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITHREADOBSERVER
+
+ CacheIOThread();
+
+ typedef nsTArray<nsCOMPtr<nsIRunnable>> EventQueue;
+
+ 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);
+ bool EventsPending(uint32_t aLastLevel = LAST_LEVEL);
+ nsresult DispatchInternal(already_AddRefed<nsIRunnable> aRunnable,
+ uint32_t aLevel);
+ bool YieldInternal();
+
+ static CacheIOThread* sSelf;
+
+ mozilla::Monitor mMonitor;
+ PRThread* mThread;
+ UniquePtr<detail::BlockingIOWatcher> mBlockingIOWatcher;
+ Atomic<nsIThread*> mXPCOMThread;
+ Atomic<uint32_t, Relaxed> mLowestLevelWaiting;
+ uint32_t mCurrentlyExecutingLevel;
+
+ // 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];
+ // Raised when nsIEventTarget.Dispatch() is called on this thread
+ Atomic<bool, Relaxed> mHasXPCOMEvents;
+ // See YieldAndRerun() above
+ bool mRerunCurrentEvent;
+ // Signal to process all pending events and then shutdown
+ // Synchronized by mMonitor
+ bool mShutdown;
+ // 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;
+ // Event counter that increases with every event processed.
+ Atomic<uint32_t, Relaxed> mEventCounter;
+#ifdef DEBUG
+ bool mInsideLoop;
+#endif
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheIndex.cpp b/netwerk/cache2/CacheIndex.cpp
new file mode 100644
index 0000000000..4de3698685
--- /dev/null
+++ b/netwerk/cache2/CacheIndex.cpp
@@ -0,0 +1,3903 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "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 {
+namespace net {
+
+namespace {
+
+class FrecencyComparator {
+ public:
+ bool Equals(CacheIndexRecord* a, CacheIndexRecord* b) const {
+ if (!a || !b) {
+ return false;
+ }
+
+ return a->mFrecency == b->mFrecency;
+ }
+ bool LessThan(CacheIndexRecord* a, CacheIndexRecord* 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->mFrecency == 0) {
+ return false;
+ }
+ if (b->mFrecency == 0) {
+ return true;
+ }
+
+ return a->mFrecency < b->mFrecency;
+ }
+};
+
+} // namespace
+
+CacheIndexRecord::~CacheIndexRecord() {
+ if (!StaticPrefs::network_cache_frecency_array_check_enabled()) {
+ return;
+ }
+
+ if (!(mFlags & CacheIndexEntry::kDirtyMask) &&
+ ((mFlags & CacheIndexEntry::kFileSizeMask) == 0)) {
+ return;
+ }
+
+ RefPtr<CacheIndex> index = CacheIndex::gInstance;
+ if (index) {
+ CacheIndex::sLock.AssertCurrentThreadOwns();
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ bool found = index->mFrecencyArray.RecordExisted(this);
+ MOZ_DIAGNOSTIC_ASSERT(!found);
+#endif
+ }
+}
+
+/**
+ * This helper class is responsible for keeping CacheIndex::mIndexStats and
+ * CacheIndex::mFrecencyArray up to date.
+ */
+class CacheIndexEntryAutoManage {
+ public:
+ CacheIndexEntryAutoManage(const SHA1Sum::Hash* aHash, CacheIndex* aIndex)
+ : mIndex(aIndex),
+ mOldRecord(nullptr),
+ mOldFrecency(0),
+ mDoNotSearchInIndex(false),
+ mDoNotSearchInUpdates(false) {
+ CacheIndex::sLock.AssertCurrentThreadOwns();
+
+ mHash = aHash;
+ const CacheIndexEntry* entry = FindEntry();
+ mIndex->mIndexStats.BeforeChange(entry);
+ if (entry && entry->IsInitialized() && !entry->IsRemoved()) {
+ mOldRecord = entry->mRec.get();
+ mOldFrecency = entry->mRec->mFrecency;
+ }
+ }
+
+ ~CacheIndexEntryAutoManage() {
+ CacheIndex::sLock.AssertCurrentThreadOwns();
+
+ const CacheIndexEntry* entry = FindEntry();
+ mIndex->mIndexStats.AfterChange(entry);
+ if (!entry || !entry->IsInitialized() || entry->IsRemoved()) {
+ entry = nullptr;
+ }
+
+ if (entry && !mOldRecord) {
+ mIndex->mFrecencyArray.AppendRecord(entry->mRec.get());
+ mIndex->AddRecordToIterators(entry->mRec.get());
+ } else if (!entry && mOldRecord) {
+ mIndex->mFrecencyArray.RemoveRecord(mOldRecord);
+ mIndex->RemoveRecordFromIterators(mOldRecord);
+ } else if (entry && mOldRecord) {
+ auto rec = entry->mRec.get();
+ if (rec != mOldRecord) {
+ // record has a different address, we have to replace it
+ mIndex->ReplaceRecordInIterators(mOldRecord, rec);
+
+ if (entry->mRec->mFrecency == mOldFrecency) {
+ // If frecency hasn't changed simply replace the pointer
+ mIndex->mFrecencyArray.ReplaceRecord(mOldRecord, rec);
+ } else {
+ // Remove old pointer and insert the new one at the end of the array
+ mIndex->mFrecencyArray.RemoveRecord(mOldRecord);
+ mIndex->mFrecencyArray.AppendRecord(rec);
+ }
+ } else if (entry->mRec->mFrecency != mOldFrecency) {
+ // Move the element at the end of the array
+ mIndex->mFrecencyArray.RemoveRecord(rec);
+ mIndex->mFrecencyArray.AppendRecord(rec);
+ }
+ } 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() {
+ 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;
+ CacheIndexRecord* mOldRecord;
+ uint32_t mOldFrecency;
+ bool mDoNotSearchInIndex;
+ bool mDoNotSearchInUpdates;
+};
+
+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);
+
+ 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()
+ : mState(INITIAL),
+ mShuttingDown(false),
+ mIndexNeedsUpdate(false),
+ mRemovingAll(false),
+ mIndexOnDiskIsValid(false),
+ mDontMarkIndexClean(false),
+ mIndexTimeStamp(0),
+ mUpdateEventPending(false),
+ mSkipEntries(0),
+ mProcessEntries(0),
+ mRWBuf(nullptr),
+ mRWBufSize(0),
+ mRWBufPos(0),
+ mRWPending(false),
+ mJournalReadSuccessfully(false),
+ mAsyncGetDiskConsumptionBlocked(false),
+ mTotalBytesWritten(0) {
+ 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);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ gInstance = std::move(idx);
+ return NS_OK;
+}
+
+nsresult CacheIndex::InitInternal(nsIFile* aCacheDirectory) {
+ nsresult rv;
+
+ rv = aCacheDirectory->Clone(getter_AddRefs(mCacheDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStartTime = TimeStamp::NowLoRes();
+
+ ReadIndexFromDisk();
+
+ 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);
+ break;
+ case READY:
+ // nothing to do, write the journal in Shutdown()
+ break;
+ case READING:
+ FinishRead(false);
+ break;
+ case BUILDING:
+ case UPDATING:
+ FinishUpdate(false);
+ 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);
+
+ if (oldState != READY) {
+ LOG(
+ ("CacheIndex::Shutdown() - Unexpected state. Did posting of "
+ "PreShutdownInternal() fail?"));
+ }
+
+ switch (oldState) {
+ case WRITING:
+ index->FinishWrite(false);
+ [[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);
+ break;
+ case BUILDING:
+ case UPDATING:
+ index->FinishUpdate(false);
+ 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);
+
+ 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();
+ index->WriteIndexToDiskIfNeeded();
+
+ 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);
+
+ 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();
+ index->WriteIndexToDiskIfNeeded();
+
+ 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);
+
+ 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();
+ index->WriteIndexToDiskIfNeeded();
+
+ 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);
+
+ 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();
+ index->WriteIndexToDiskIfNeeded();
+
+ 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);
+
+ 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();
+
+ 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);
+ break;
+ case READY:
+ // nothing to do
+ break;
+ case READING:
+ index->FinishRead(false);
+ break;
+ case BUILDING:
+ case UPDATING:
+ index->FinishUpdate(false);
+ 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();
+ 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();
+
+ for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
+ CacheIndexRecord* rec = iter.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()) {
+ CacheIndexRecord* record = iter.Get();
+ if (aInfo && !CacheIndexEntry::RecordMatchesLoadContextInfo(record, aInfo))
+ continue;
+
+ *aSize += CacheIndexEntry::GetFileSize(*record);
+ ++*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();
+ }
+ }),
+ 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();
+
+ for (auto iter = index->mFrecencyArray.Iter(); !iter.Done(); iter.Next()) {
+ idxIter->AddRecord(iter.Get());
+ }
+
+ 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() {
+ LOG(("CacheIndex::ProcessPendingOperations()"));
+
+ sLock.AssertCurrentThreadOwns();
+
+ 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);
+ 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() {
+ if (mState != READY || mShuttingDown || mRWPending) {
+ return false;
+ }
+
+ if (!mLastDumpTime.IsNull() &&
+ (TimeStamp::NowLoRes() - mLastDumpTime).ToMilliseconds() <
+ kMinDumpInterval) {
+ return false;
+ }
+
+ if (mIndexStats.Dirty() < kMinUnwrittenChanges) {
+ return false;
+ }
+
+ WriteIndexToDisk();
+ return true;
+}
+
+void CacheIndex::WriteIndexToDisk() {
+ LOG(("CacheIndex::WriteIndexToDisk()"));
+ mIndexStats.Log();
+
+ nsresult rv;
+
+ sLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mState == READY);
+ MOZ_ASSERT(!mRWBuf);
+ MOZ_ASSERT(!mRWHash);
+ MOZ_ASSERT(!mRWPending);
+
+ ChangeState(WRITING);
+
+ 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);
+ 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() {
+ LOG(("CacheIndex::WriteRecords()"));
+
+ nsresult rv;
+
+ sLock.AssertCurrentThreadOwns();
+ 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);
+ } else {
+ mRWPending = true;
+ }
+
+ mRWBufPos = 0;
+}
+
+void CacheIndex::FinishWrite(bool aSucceeded) {
+ LOG(("CacheIndex::FinishWrite() [succeeded=%d]", aSucceeded));
+
+ MOZ_ASSERT((!aSucceeded && mState == SHUTDOWN) || mState == WRITING);
+
+ sLock.AssertCurrentThreadOwns();
+
+ // 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);
+
+ 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();
+ mIndexStats.Log();
+
+ if (mState == WRITING) {
+ ChangeState(READY);
+ 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() {
+ LOG(("CacheIndex::ReadIndexFromDisk()"));
+
+ nsresult rv;
+
+ sLock.AssertCurrentThreadOwns();
+ MOZ_ASSERT(mState == INITIAL);
+
+ ChangeState(READING);
+
+ 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);
+ 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);
+ }
+
+ 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);
+ }
+}
+
+void CacheIndex::StartReadingIndex() {
+ LOG(("CacheIndex::StartReadingIndex()"));
+
+ nsresult rv;
+
+ sLock.AssertCurrentThreadOwns();
+
+ 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);
+ 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);
+ } else {
+ mRWPending = true;
+ }
+}
+
+void CacheIndex::ParseRecords() {
+ LOG(("CacheIndex::ParseRecords()"));
+
+ nsresult rv;
+
+ sLock.AssertCurrentThreadOwns();
+
+ 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);
+ 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);
+ return;
+ }
+
+ CacheIndexEntryAutoManage emng(tmpEntry.Hash(), this);
+
+ 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);
+ return;
+ }
+
+ mIndexOnDiskIsValid = true;
+ mJournalReadSuccessfully = false;
+
+ if (mJournalHandle) {
+ StartReadingJournal();
+ } else {
+ FinishRead(false);
+ }
+
+ 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);
+ return;
+ }
+ mRWPending = true;
+}
+
+void CacheIndex::StartReadingJournal() {
+ LOG(("CacheIndex::StartReadingJournal()"));
+
+ nsresult rv;
+
+ sLock.AssertCurrentThreadOwns();
+
+ 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);
+ 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);
+ } else {
+ mRWPending = true;
+ }
+}
+
+void CacheIndex::ParseJournal() {
+ LOG(("CacheIndex::ParseJournal()"));
+
+ nsresult rv;
+
+ sLock.AssertCurrentThreadOwns();
+
+ 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);
+ 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);
+ return;
+ }
+
+ mJournalReadSuccessfully = true;
+ FinishRead(true);
+ 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);
+ return;
+ }
+ mRWPending = true;
+}
+
+void CacheIndex::MergeJournal() {
+ LOG(("CacheIndex::MergeJournal()"));
+
+ sLock.AssertCurrentThreadOwns();
+
+ 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);
+ 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) {
+ LOG(("CacheIndex::FinishRead() [succeeded=%d]", aSucceeded));
+ sLock.AssertCurrentThreadOwns();
+
+ 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();
+ // Remove all entries that we haven't seen during this session
+ RemoveNonFreshEntries();
+ StartUpdatingIndex(true);
+ return;
+ }
+
+ if (!mJournalReadSuccessfully) {
+ mTmpJournal.Clear();
+ EnsureNoFreshEntry();
+ ProcessPendingOperations();
+ StartUpdatingIndex(false);
+ return;
+ }
+
+ MergeJournal();
+ EnsureNoFreshEntry();
+ ProcessPendingOperations();
+ mIndexStats.Log();
+
+ ChangeState(READY);
+ 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();
+}
+
+// static
+void CacheIndex::DelayedUpdateLocked() {
+ LOG(("CacheIndex::DelayedUpdateLocked()"));
+
+ sLock.AssertCurrentThreadOwns();
+
+ 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);
+ }
+}
+
+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;
+ }
+
+ rv = file->GetDirectoryEntries(getter_AddRefs(mDirEnumerator));
+ 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 ? true : false;
+ 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();
+
+ if (mUpdateTimer || mUpdateEventPending) {
+ return true;
+ }
+
+ return false;
+}
+
+void CacheIndex::BuildIndex() {
+ LOG(("CacheIndex::BuildIndex()"));
+
+ sLock.AssertCurrentThreadOwns();
+
+ MOZ_ASSERT(mPendingUpdates.Count() == 0);
+
+ nsresult rv;
+
+ if (!mDirEnumerator) {
+ {
+ // Do not do IO under the lock.
+ StaticMutexAutoUnlock unlock(sLock);
+ 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);
+ 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.
+ StaticMutexAutoUnlock unlock(sLock);
+ rv = mDirEnumerator->GetNextFile(getter_AddRefs(file));
+
+ if (file) {
+ file->Exists(&fileExists);
+ }
+ }
+ if (mState == SHUTDOWN) {
+ return;
+ }
+ if (!file) {
+ FinishUpdate(NS_SUCCEEDED(rv));
+ 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);
+ 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(bool aSwitchingToReadyState) {
+ // 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);
+ return true;
+ }
+
+ return false;
+}
+
+void CacheIndex::StartUpdatingIndex(bool aRebuild) {
+ LOG(("CacheIndex::StartUpdatingIndex() [rebuild=%d]", aRebuild));
+
+ sLock.AssertCurrentThreadOwns();
+
+ nsresult rv;
+
+ mIndexStats.Log();
+
+ ChangeState(aRebuild ? BUILDING : UPDATING);
+ mDontMarkIndexClean = false;
+
+ if (mShuttingDown || mRemovingAll) {
+ FinishUpdate(false);
+ 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);
+ }
+}
+
+void CacheIndex::UpdateIndex() {
+ LOG(("CacheIndex::UpdateIndex()"));
+
+ sLock.AssertCurrentThreadOwns();
+
+ MOZ_ASSERT(mPendingUpdates.Count() == 0);
+
+ nsresult rv;
+
+ if (!mDirEnumerator) {
+ {
+ // Do not do IO under the lock.
+ StaticMutexAutoUnlock unlock(sLock);
+ 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);
+ 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.
+ StaticMutexAutoUnlock unlock(sLock);
+ rv = mDirEnumerator->GetNextFile(getter_AddRefs(file));
+
+ if (file) {
+ file->Exists(&fileExists);
+ }
+ }
+ if (mState == SHUTDOWN) {
+ return;
+ }
+ if (!file) {
+ FinishUpdate(NS_SUCCEEDED(rv));
+ 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);
+ 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);
+
+ 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) {
+ LOG(("CacheIndex::FinishUpdate() [succeeded=%d]", aSucceeded));
+
+ MOZ_ASSERT(mState == UPDATING || mState == BUILDING ||
+ (!aSucceeded && mState == SHUTDOWN));
+
+ sLock.AssertCurrentThreadOwns();
+
+ 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();
+ }
+
+ // 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);
+ mLastDumpTime = TimeStamp::NowLoRes(); // Do not dump new index immediately
+}
+
+void CacheIndex::RemoveNonFreshEntries() {
+ 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);
+ 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) {
+ 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(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(CacheIndexRecord* aRecord) {
+ LOG(
+ ("CacheIndex::FrecencyArray::AppendRecord() [record=%p, hash=%08x%08x%08x"
+ "%08x%08x]",
+ aRecord, LOGSHA1(aRecord->mHash)));
+
+ MOZ_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->mFrecency != 0) {
+ ++mUnsortedElements;
+ }
+}
+
+void CacheIndex::FrecencyArray::RemoveRecord(CacheIndexRecord* aRecord) {
+ LOG(("CacheIndex::FrecencyArray::RemoveRecord() [record=%p]", aRecord));
+
+ decltype(mRecs)::index_type idx;
+ idx = mRecs.IndexOf(aRecord);
+ MOZ_RELEASE_ASSERT(idx != mRecs.NoIndex);
+ mRecs[idx] = nullptr;
+ ++mRemovedElements;
+
+ // Calling SortIfNeeded ensures that we get rid of removed elements in the
+ // array once we hit the limit.
+ SortIfNeeded();
+}
+
+void CacheIndex::FrecencyArray::ReplaceRecord(CacheIndexRecord* aOldRecord,
+ CacheIndexRecord* aNewRecord) {
+ 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);
+ mRecs[idx] = aNewRecord;
+}
+
+bool CacheIndex::FrecencyArray::RecordExisted(CacheIndexRecord* aRecord) {
+ return mRecs.Contains(aRecord);
+}
+
+void CacheIndex::FrecencyArray::SortIfNeeded() {
+ 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) {
+#ifdef DEBUG
+ for (uint32_t i = Length(); i < mRecs.Length(); ++i) {
+ MOZ_ASSERT(!mRecs[i]);
+ }
+#endif
+ // Removed elements are at the end after sorting.
+ mRecs.RemoveElementsAt(Length(), mRemovedElements);
+ mRemovedElements = 0;
+ }
+ }
+}
+
+void CacheIndex::AddRecordToIterators(CacheIndexRecord* aRecord) {
+ 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);
+ }
+ }
+}
+
+void CacheIndex::RemoveRecordFromIterators(CacheIndexRecord* aRecord) {
+ 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);
+ }
+}
+
+void CacheIndex::ReplaceRecordInIterators(CacheIndexRecord* aOldRecord,
+ CacheIndexRecord* aNewRecord) {
+ 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);
+ }
+}
+
+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();
+ break;
+ case UPDATING:
+ UpdateIndex();
+ break;
+ default:
+ LOG(("CacheIndex::Run() - Update/Build was canceled"));
+ }
+
+ return NS_OK;
+}
+
+void CacheIndex::OnFileOpenedInternal(FileOpenHelper* aOpener,
+ CacheFileHandle* aHandle,
+ nsresult aResult) {
+ LOG(
+ ("CacheIndex::OnFileOpenedInternal() [opener=%p, handle=%p, "
+ "result=0x%08" PRIx32 "]",
+ aOpener, aHandle, static_cast<uint32_t>(aResult)));
+ MOZ_ASSERT(CacheFileIOManager::IsOnIOThread());
+
+ nsresult rv;
+
+ sLock.AssertCurrentThreadOwns();
+
+ 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);
+ } else {
+ mIndexHandle = aHandle;
+ WriteRecords();
+ }
+ break;
+ case READING:
+ if (aOpener == mIndexFileOpener) {
+ mIndexFileOpener = nullptr;
+
+ if (NS_SUCCEEDED(aResult)) {
+ if (aHandle->FileSize() == 0) {
+ FinishRead(false);
+ CacheFileIOManager::DoomFile(aHandle, nullptr);
+ break;
+ }
+ mIndexHandle = aHandle;
+ } else {
+ FinishRead(false);
+ 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);
+ 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);
+ break;
+ }
+ } else {
+ StartReadingIndex();
+ }
+
+ 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);
+ } 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);
+ }
+ } else {
+ WriteRecords();
+ }
+ }
+ 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);
+ } else {
+ if (!mIndexOnDiskIsValid) {
+ ParseRecords();
+ } else {
+ ParseJournal();
+ }
+ }
+ 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));
+ 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);
+ } else {
+ StartReadingIndex();
+ }
+ 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) {
+ sLock.AssertCurrentThreadOwns();
+
+ if (!gInstance) return 0;
+
+ return gInstance->SizeOfExcludingThisInternal(mallocSizeOf);
+}
+
+// static
+size_t CacheIndex::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
+ StaticMutexAutoLock lock(sLock);
+
+ return mallocSizeOf(gInstance) + SizeOfExcludingThis(mallocSizeOf);
+}
+
+// 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 net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheIndex.h b/netwerk/cache2/CacheIndex.h
new file mode 100644
index 0000000000..bf98333435
--- /dev/null
+++ b/netwerk/cache2/CacheIndex.h
@@ -0,0 +1,1277 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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;
+
+typedef 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;
+} CacheIndexHeader;
+
+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;
+ OriginAttrsHash mOriginAttrsHash;
+ uint16_t mOnStartTime;
+ uint16_t mOnStopTime;
+ uint8_t mContentType;
+
+ /*
+ * 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;
+
+ CacheIndexRecord()
+ : mFrecency(0),
+ mOriginAttrsHash(0),
+ mOnStartTime(kIndexTimeNotAvailable),
+ mOnStopTime(kIndexTimeNotAvailable),
+ mContentType(nsICacheEntry::CONTENT_TYPE_UNKNOWN),
+ mFlags(0) {}
+
+ ~CacheIndexRecord();
+};
+#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 CacheIndexEntry : public PLDHashEntryHdr {
+ public:
+ typedef const SHA1Sum::Hash& KeyType;
+ typedef const SHA1Sum::Hash* KeyTypePointer;
+
+ explicit CacheIndexEntry(KeyTypePointer aKey) {
+ MOZ_COUNT_CTOR(CacheIndexEntry);
+ mRec = MakeUnique<CacheIndexRecord>();
+ LOG(("CacheIndexEntry::CacheIndexEntry() - Created record [rec=%p]",
+ mRec.get()));
+ memcpy(&mRec->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->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->mHash);
+ }
+
+ CacheIndexEntry& operator=(const CacheIndexEntry& aOther) {
+ MOZ_ASSERT(
+ memcmp(&mRec->mHash, &aOther.mRec->mHash, sizeof(SHA1Sum::Hash)) == 0);
+ mRec->mFrecency = aOther.mRec->mFrecency;
+ mRec->mOriginAttrsHash = aOther.mRec->mOriginAttrsHash;
+ mRec->mOnStartTime = aOther.mRec->mOnStartTime;
+ mRec->mOnStopTime = aOther.mRec->mOnStopTime;
+ mRec->mContentType = aOther.mRec->mContentType;
+ mRec->mFlags = aOther.mRec->mFlags;
+ return *this;
+ }
+
+ void InitNew() {
+ mRec->mFrecency = 0;
+ mRec->mOriginAttrsHash = 0;
+ mRec->mOnStartTime = kIndexTimeNotAvailable;
+ mRec->mOnStopTime = kIndexTimeNotAvailable;
+ mRec->mContentType = nsICacheEntry::CONTENT_TYPE_UNKNOWN;
+ mRec->mFlags = 0;
+ }
+
+ void Init(OriginAttrsHash aOriginAttrsHash, bool aAnonymous, bool aPinned) {
+ MOZ_ASSERT(mRec->mFrecency == 0);
+ MOZ_ASSERT(mRec->mOriginAttrsHash == 0);
+ MOZ_ASSERT(mRec->mOnStartTime == kIndexTimeNotAvailable);
+ MOZ_ASSERT(mRec->mOnStopTime == kIndexTimeNotAvailable);
+ MOZ_ASSERT(mRec->mContentType == nsICacheEntry::CONTENT_TYPE_UNKNOWN);
+ // When we init the entry it must be fresh and may be dirty
+ MOZ_ASSERT((mRec->mFlags & ~kDirtyMask) == kFreshMask);
+
+ mRec->mOriginAttrsHash = aOriginAttrsHash;
+ mRec->mFlags |= kInitializedMask;
+ if (aAnonymous) {
+ mRec->mFlags |= kAnonymousMask;
+ }
+ if (aPinned) {
+ mRec->mFlags |= kPinnedMask;
+ }
+ }
+
+ const SHA1Sum::Hash* Hash() const { return &mRec->mHash; }
+
+ bool IsInitialized() const { return !!(mRec->mFlags & kInitializedMask); }
+
+ mozilla::net::OriginAttrsHash OriginAttrsHash() const {
+ return mRec->mOriginAttrsHash;
+ }
+
+ bool Anonymous() const { return !!(mRec->mFlags & kAnonymousMask); }
+
+ bool IsRemoved() const { return !!(mRec->mFlags & kRemovedMask); }
+ void MarkRemoved() { mRec->mFlags |= kRemovedMask; }
+
+ bool IsDirty() const { return !!(mRec->mFlags & kDirtyMask); }
+ void MarkDirty() { mRec->mFlags |= kDirtyMask; }
+ void ClearDirty() { mRec->mFlags &= ~kDirtyMask; }
+
+ bool IsFresh() const { return !!(mRec->mFlags & kFreshMask); }
+ void MarkFresh() { mRec->mFlags |= kFreshMask; }
+
+ bool IsPinned() const { return !!(mRec->mFlags & kPinnedMask); }
+
+ void SetFrecency(uint32_t aFrecency) { mRec->mFrecency = aFrecency; }
+ uint32_t GetFrecency() const { return mRec->mFrecency; }
+
+ void SetHasAltData(bool aHasAltData) {
+ aHasAltData ? mRec->mFlags |= kHasAltDataMask
+ : mRec->mFlags &= ~kHasAltDataMask;
+ }
+ bool GetHasAltData() const { return !!(mRec->mFlags & kHasAltDataMask); }
+
+ void SetOnStartTime(uint16_t aTime) { mRec->mOnStartTime = aTime; }
+ uint16_t GetOnStartTime() const { return mRec->mOnStartTime; }
+
+ void SetOnStopTime(uint16_t aTime) { mRec->mOnStopTime = aTime; }
+ uint16_t GetOnStopTime() const { return mRec->mOnStopTime; }
+
+ void SetContentType(uint8_t aType) { mRec->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->mFlags &= ~kFileSizeMask;
+ mRec->mFlags |= aFileSize;
+ }
+ // Returns filesize in kilobytes.
+ uint32_t GetFileSize() const { return GetFileSize(*mRec); }
+ 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->mHash, sizeof(SHA1Sum::Hash));
+ ptr += sizeof(SHA1Sum::Hash);
+ NetworkEndian::writeUint32(ptr, mRec->mFrecency);
+ ptr += sizeof(uint32_t);
+ NetworkEndian::writeUint64(ptr, mRec->mOriginAttrsHash);
+ ptr += sizeof(uint64_t);
+ NetworkEndian::writeUint16(ptr, mRec->mOnStartTime);
+ ptr += sizeof(uint16_t);
+ NetworkEndian::writeUint16(ptr, mRec->mOnStopTime);
+ ptr += sizeof(uint16_t);
+ *ptr = mRec->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->mFlags & ~(kDirtyMask | kFreshMask));
+ }
+
+ void ReadFromBuf(void* aBuf) {
+ const uint8_t* ptr = static_cast<const uint8_t*>(aBuf);
+ MOZ_ASSERT(memcmp(&mRec->mHash, ptr, sizeof(SHA1Sum::Hash)) == 0);
+ ptr += sizeof(SHA1Sum::Hash);
+ mRec->mFrecency = NetworkEndian::readUint32(ptr);
+ ptr += sizeof(uint32_t);
+ mRec->mOriginAttrsHash = NetworkEndian::readUint64(ptr);
+ ptr += sizeof(uint64_t);
+ mRec->mOnStartTime = NetworkEndian::readUint16(ptr);
+ ptr += sizeof(uint16_t);
+ mRec->mOnStopTime = NetworkEndian::readUint16(ptr);
+ ptr += sizeof(uint16_t);
+ mRec->mContentType = *ptr;
+ ptr += sizeof(uint8_t);
+ mRec->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->mHash), IsFresh(), IsInitialized(), IsRemoved(),
+ IsDirty(), Anonymous(), OriginAttrsHash(), GetFrecency(),
+ GetHasAltData(), GetOnStartTime(), GetOnStopTime(), GetContentType(),
+ GetFileSize()));
+ }
+
+ static bool RecordMatchesLoadContextInfo(CacheIndexRecord* aRec,
+ nsILoadContextInfo* aInfo) {
+ MOZ_ASSERT(aInfo);
+
+ if (!aInfo->IsPrivate() &&
+ GetOriginAttrsHash(*aInfo->OriginAttributesPtr()) ==
+ aRec->mOriginAttrsHash &&
+ aInfo->IsAnonymous() == !!(aRec->mFlags & kAnonymousMask)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ // 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;
+
+ UniquePtr<CacheIndexRecord> 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->mHash, &aOther.mRec->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->mHash, &aDst->mRec->mHash, sizeof(SHA1Sum::Hash)) == 0);
+ if (mUpdateFlags & kFrecencyUpdatedMask) {
+ aDst->mRec->mFrecency = mRec->mFrecency;
+ }
+ aDst->mRec->mOriginAttrsHash = mRec->mOriginAttrsHash;
+ if (mUpdateFlags & kOnStartTimeUpdatedMask) {
+ aDst->mRec->mOnStartTime = mRec->mOnStartTime;
+ }
+ if (mUpdateFlags & kOnStopTimeUpdatedMask) {
+ aDst->mRec->mOnStopTime = mRec->mOnStopTime;
+ }
+ if (mUpdateFlags & kContentTypeUpdatedMask) {
+ aDst->mRec->mContentType = mRec->mContentType;
+ }
+ if (mUpdateFlags & kHasAltDataUpdatedMask &&
+ ((aDst->mRec->mFlags ^ mRec->mFlags) & kHasAltDataMask)) {
+ // Toggle the bit if we need to.
+ aDst->mRec->mFlags ^= kHasAltDataMask;
+ }
+
+ if (mUpdateFlags & kFileSizeUpdatedMask) {
+ // Copy all flags except |HasAltData|.
+ aDst->mRec->mFlags |= (mRec->mFlags & ~kHasAltDataMask);
+ } else {
+ // Copy all flags except |HasAltData| and file size.
+ aDst->mRec->mFlags &= kFileSizeMask;
+ aDst->mRec->mFlags |= (mRec->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()
+ : mCount(0),
+ mNotInitialized(0),
+ mRemoved(0),
+ mDirty(0),
+ mFresh(0),
+ mEmpty(0),
+ mSize(0)
+#ifdef DEBUG
+ ,
+ mStateLogged(false),
+ mDisableLogging(false)
+#endif
+ {
+ 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;
+ uint32_t mCountByType[nsICacheEntry::CONTENT_TYPE_LAST];
+ uint32_t mNotInitialized;
+ uint32_t mRemoved;
+ uint32_t mDirty;
+ uint32_t mFresh;
+ uint32_t mEmpty;
+ uint32_t mSize;
+ uint32_t mSizeByType[nsICacheEntry::CONTENT_TYPE_LAST];
+#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;
+
+ // Disables logging in this instance of CacheIndexStats
+ bool mDisableLogging;
+#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 struct CacheIndexRecord;
+
+ virtual ~CacheIndex();
+
+ NS_IMETHOD OnFileOpened(CacheFileHandle* aHandle, nsresult aResult) override;
+ void OnFileOpenedInternal(FileOpenHelper* aOpener, CacheFileHandle* aHandle,
+ nsresult aResult);
+ 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);
+ void PreShutdownInternal();
+
+ // This method returns false when index is not initialized or is shut down.
+ bool IsIndexUsable();
+
+ // 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();
+
+ // 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();
+ // Starts writing of index file.
+ void WriteIndexToDisk();
+ // Serializes part of mIndex hashtable to the write buffer a writes the buffer
+ // to the file.
+ void WriteRecords();
+ // Finalizes writing process.
+ void FinishWrite(bool aSucceeded);
+
+ // 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);
+ void RemoveAllIndexFiles();
+ void RemoveJournalAndTempFile();
+ // Writes journal to the disk and clears dirty flag in index header.
+ nsresult WriteLogToDisk();
+
+ // 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();
+ // Starts reading data from index file.
+ void StartReadingIndex();
+ // Parses data read from index file.
+ void ParseRecords();
+ // Starts reading data from journal file.
+ void StartReadingJournal();
+ // Parses data read from journal file.
+ void ParseJournal();
+ // Merges entries from journal into mIndex.
+ void MergeJournal();
+ // 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();
+ // In debug build this method is called after processing pending operations
+ // to make sure mIndexStats contains correct information.
+ void EnsureCorrectStats();
+ // Finalizes reading process.
+ void FinishRead(bool aSucceeded);
+
+ // 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();
+ // Posts timer event that start update or build process.
+ nsresult ScheduleUpdateTimer(uint32_t aDelay);
+ nsresult SetupDirectoryEnumerator();
+ nsresult InitEntryFromDiskData(CacheIndexEntry* aEntry,
+ CacheFileMetadata* aMetaData,
+ int64_t aFileSize);
+ // Returns true when either a timer is scheduled or event is posted.
+ bool IsUpdatePending();
+ // 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();
+
+ bool StartUpdatingIndexIfNeeded(bool aSwitchingToReadyState = false);
+ // Starts update or build process or fires a timer when it is too early after
+ // startup.
+ void StartUpdatingIndex(bool aRebuild);
+ // 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();
+ // Finalizes update or build process.
+ void FinishUpdate(bool aSucceeded);
+
+ void RemoveNonFreshEntries();
+
+ 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);
+ void NotifyAsyncGetDiskConsumptionCallbacks();
+
+ // Allocates and releases buffer used for reading and writing index.
+ void AllocBuffer();
+ void ReleaseBuffer();
+
+ // Methods used by CacheIndexEntryAutoManage to keep the iterators up to date.
+ void AddRecordToIterators(CacheIndexRecord* aRecord);
+ void RemoveRecordFromIterators(CacheIndexRecord* aRecord);
+ void ReplaceRecordInIterators(CacheIndexRecord* aOldRecord,
+ CacheIndexRecord* aNewRecord);
+
+ // Memory reporting (private part)
+ size_t SizeOfExcludingThisInternal(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ // Reports telemetry about cache, i.e. size, entry count and content type
+ // stats.
+ void DoTelemetryReport();
+
+ static mozilla::StaticRefPtr<CacheIndex> gInstance;
+ static StaticMutex sLock;
+
+ nsCOMPtr<nsIFile> mCacheDirectory;
+
+ EState mState;
+ // Timestamp of time when the index was initialized. We use it to delay
+ // initial update or build of index.
+ TimeStamp mStartTime;
+ // Set to true in PreShutdown(), it is checked on variaous places to prevent
+ // starting any process (write, update, etc.) during shutdown.
+ bool mShuttingDown;
+ // 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;
+ // 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;
+ // Whether the index file on disk exists and is valid.
+ bool mIndexOnDiskIsValid;
+ // 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;
+ // Timestamp value from index file. It is used during update process to skip
+ // entries that were last modified before this timestamp.
+ uint32_t mIndexTimeStamp;
+ // 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;
+
+ // Timer of delayed update/build.
+ nsCOMPtr<nsITimer> mUpdateTimer;
+ // True when build or update event is posted
+ bool mUpdateEventPending;
+
+ // 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;
+ // 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;
+ char* mRWBuf;
+ uint32_t mRWBufSize;
+ uint32_t mRWBufPos;
+ RefPtr<CacheHash> mRWHash;
+
+ // 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;
+
+ // Reading of journal succeeded if true.
+ bool mJournalReadSuccessfully;
+
+ // Handle used for writing and reading index file.
+ RefPtr<CacheFileHandle> mIndexHandle;
+ // Handle used for reading journal file.
+ RefPtr<CacheFileHandle> mJournalHandle;
+ // Used to check the existence of the file during reading process.
+ RefPtr<CacheFileHandle> mTmpHandle;
+
+ RefPtr<FileOpenHelper> mIndexFileOpener;
+ RefPtr<FileOpenHelper> mJournalFileOpener;
+ RefPtr<FileOpenHelper> mTmpFileOpener;
+
+ // Directory enumerator used when building and updating index.
+ nsCOMPtr<nsIDirectoryEnumerator> mDirEnumerator;
+
+ // Main index hashtable.
+ nsTHashtable<CacheIndexEntry> mIndex;
+
+ // 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;
+
+ // Contains information statistics for mIndex + mPendingUpdates.
+ CacheIndexStats mIndexStats;
+
+ // 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;
+
+ // 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<CacheIndexRecord*>* aRecs)
+ : mRecs(aRecs), mIdx(0) {
+ while (!Done() && !(*mRecs)[mIdx]) {
+ mIdx++;
+ }
+ }
+
+ bool Done() const { return mIdx == mRecs->Length(); }
+
+ CacheIndexRecord* Get() const {
+ MOZ_ASSERT(!Done());
+ return (*mRecs)[mIdx];
+ }
+
+ void Next() {
+ MOZ_ASSERT(!Done());
+ ++mIdx;
+ while (!Done() && !(*mRecs)[mIdx]) {
+ mIdx++;
+ }
+ }
+
+ private:
+ nsTArray<CacheIndexRecord*>* mRecs;
+ uint32_t mIdx;
+ };
+
+ public:
+ Iterator Iter() { return Iterator(&mRecs); }
+
+ FrecencyArray() : mUnsortedElements(0), mRemovedElements(0) {}
+
+ // Methods used by CacheIndexEntryAutoManage to keep the array up to date.
+ void AppendRecord(CacheIndexRecord* aRecord);
+ void RemoveRecord(CacheIndexRecord* aRecord);
+ void ReplaceRecord(CacheIndexRecord* aOldRecord,
+ CacheIndexRecord* aNewRecord);
+ bool RecordExisted(CacheIndexRecord* aRecord);
+ void SortIfNeeded();
+
+ size_t Length() const { return mRecs.Length() - mRemovedElements; }
+ void Clear() { mRecs.Clear(); }
+
+ private:
+ friend class CacheIndex;
+
+ nsTArray<CacheIndexRecord*> mRecs;
+ uint32_t mUnsortedElements;
+ // 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;
+ };
+
+ FrecencyArray mFrecencyArray;
+
+ nsTArray<CacheIndexIterator*> mIterators;
+
+ // 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;
+
+ 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;
+
+ // Number of bytes written to the cache since the last telemetry report
+ uint64_t mTotalBytesWritten;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/CacheIndexContextIterator.cpp b/netwerk/cache2/CacheIndexContextIterator.cpp
new file mode 100644
index 0000000000..0c53049158
--- /dev/null
+++ b/netwerk/cache2/CacheIndexContextIterator.cpp
@@ -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/. */
+
+#include "CacheLog.h"
+#include "CacheIndexContextIterator.h"
+#include "CacheIndex.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+CacheIndexContextIterator::CacheIndexContextIterator(CacheIndex* aIndex,
+ bool aAddNew,
+ nsILoadContextInfo* aInfo)
+ : CacheIndexIterator(aIndex, aAddNew), mInfo(aInfo) {}
+
+void CacheIndexContextIterator::AddRecord(CacheIndexRecord* aRecord) {
+ if (CacheIndexEntry::RecordMatchesLoadContextInfo(aRecord, mInfo)) {
+ CacheIndexIterator::AddRecord(aRecord);
+ }
+}
+
+void CacheIndexContextIterator::AddRecords(
+ const nsTArray<CacheIndexRecord*>& aRecords) {
+ // We need to add one by one so that those with wrong context are ignored.
+ for (uint32_t i = 0; i < aRecords.Length(); ++i) {
+ AddRecord(aRecords[i]);
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheIndexContextIterator.h b/netwerk/cache2/CacheIndexContextIterator.h
new file mode 100644
index 0000000000..6217783d75
--- /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(CacheIndexRecord* aRecord) override;
+ virtual void AddRecords(const nsTArray<CacheIndexRecord*>& aRecords);
+
+ 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..274b0bcc01
--- /dev/null
+++ b/netwerk/cache2/CacheIndexIterator.cpp
@@ -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/. */
+
+#include "CacheLog.h"
+#include "CacheIndexIterator.h"
+#include "CacheIndex.h"
+#include "nsString.h"
+#include "mozilla/DebugOnly.h"
+
+namespace mozilla {
+namespace 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));
+
+ Close();
+}
+
+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()->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;
+ }
+
+ DebugOnly<bool> removed = mIndex->mIterators.RemoveElement(this);
+ MOZ_ASSERT(removed);
+ mStatus = aStatus;
+
+ return NS_OK;
+}
+
+void CacheIndexIterator::AddRecord(CacheIndexRecord* aRecord) {
+ LOG(("CacheIndexIterator::AddRecord() [this=%p, record=%p]", this, aRecord));
+
+ mRecords.AppendElement(aRecord);
+}
+
+bool CacheIndexIterator::RemoveRecord(CacheIndexRecord* aRecord) {
+ LOG(("CacheIndexIterator::RemoveRecord() [this=%p, record=%p]", this,
+ aRecord));
+
+ return mRecords.RemoveElement(aRecord);
+}
+
+bool CacheIndexIterator::ReplaceRecord(CacheIndexRecord* aOldRecord,
+ CacheIndexRecord* aNewRecord) {
+ LOG(
+ ("CacheIndexIterator::ReplaceRecord() [this=%p, oldRecord=%p, "
+ "newRecord=%p]",
+ this, aOldRecord, aNewRecord));
+
+ if (RemoveRecord(aOldRecord)) {
+ AddRecord(aNewRecord);
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheIndexIterator.h b/netwerk/cache2/CacheIndexIterator.h
new file mode 100644
index 0000000000..8b952f190a
--- /dev/null
+++ b/netwerk/cache2/CacheIndexIterator.h
@@ -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/. */
+
+#ifndef CacheIndexIterator__h__
+#define CacheIndexIterator__h__
+
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "mozilla/SHA1.h"
+
+namespace mozilla {
+namespace net {
+
+class CacheIndex;
+struct CacheIndexRecord;
+
+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(CacheIndexRecord* aRecord);
+ bool RemoveRecord(CacheIndexRecord* aRecord);
+ bool ReplaceRecord(CacheIndexRecord* aOldRecord,
+ CacheIndexRecord* aNewRecord);
+
+ nsresult mStatus;
+ RefPtr<CacheIndex> mIndex;
+ nsTArray<CacheIndexRecord*> 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..67d879a6c4
--- /dev/null
+++ b/netwerk/cache2/CacheLog.cpp
@@ -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 "CacheLog.h"
+
+namespace mozilla {
+namespace 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 net
+} // namespace mozilla
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..398cd494ec
--- /dev/null
+++ b/netwerk/cache2/CacheObserver.cpp
@@ -0,0 +1,257 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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>
+
+namespace mozilla {
+namespace 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, "browser-delayed-startup-finished", 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);
+
+ 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);
+
+ if (aSize > derivedLimit) return true;
+
+ return false;
+}
+
+// static
+bool CacheObserver::IsPastShutdownIOLag() {
+#ifdef DEBUG
+ return false;
+#endif
+
+ 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;
+}
+
+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, "browser-delayed-startup-finished")) {
+ CacheStorageService::CleaupCacheDirectories();
+ 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;
+ }
+
+ MOZ_ASSERT(false, "Missing observer handler");
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
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/CacheStorage.cpp b/netwerk/cache2/CacheStorage.cpp
new file mode 100644
index 0000000000..5f3d047bf1
--- /dev/null
+++ b/netwerk/cache2/CacheStorage.cpp
@@ -0,0 +1,253 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "OldWrappers.h"
+
+#include "nsICacheEntryDoomCallback.h"
+
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheService.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla::net {
+
+NS_IMPL_ISUPPORTS(CacheStorage, nsICacheStorage)
+
+CacheStorage::CacheStorage(nsILoadContextInfo* aInfo, bool aAllowDisk,
+ bool aLookupAppCache, bool aSkipSizeCheck,
+ bool aPinning)
+ : mLoadContextInfo(aInfo ? GetLoadContextInfo(aInfo) : nullptr),
+ mWriteToDisk(aAllowDisk),
+ mLookupAppCache(aLookupAppCache),
+ 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, nullptr,
+ NS_ERROR_NOT_AVAILABLE);
+ return NS_OK;
+ }
+
+ if (MOZ_UNLIKELY(!CacheObserver::UseMemoryCache()) && !mWriteToDisk &&
+ !(aFlags & OPEN_INTERCEPTED)) {
+ aCallback->OnCacheEntryAvailable(nullptr, false, nullptr,
+ NS_ERROR_NOT_AVAILABLE);
+ return NS_OK;
+ }
+
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG(aCallback);
+
+ nsresult rv;
+
+ bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
+
+ 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);
+
+ nsCOMPtr<nsIApplicationCache> appCache;
+ if (LookupAppCache()) {
+ rv = ChooseApplicationCache(noRefURI, getter_AddRefs(appCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (appCache) {
+ // From a chosen appcache open only as readonly
+ aFlags &= ~nsICacheStorage::OPEN_TRUNCATE;
+ }
+ }
+
+ if (appCache) {
+ nsAutoCString scheme;
+ rv = noRefURI->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<_OldCacheLoad> appCacheLoad =
+ new _OldCacheLoad(scheme, asciiSpec, aCallback, appCache, LoadInfo(),
+ WriteToDisk(), aFlags);
+ rv = appCacheLoad->Start();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("CacheStorage::AsyncOpenURI loading from appcache"));
+ return NS_OK;
+ }
+
+ RefPtr<CacheEntryHandle> entry;
+ rv = CacheStorageService::Self()->AddStorageEntry(
+ this, asciiSpec, aIdExtension,
+ truncate, // replace any existing one?
+ getter_AddRefs(entry));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 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,
+ true, // 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;
+}
+
+// Internal
+
+nsresult CacheStorage::ChooseApplicationCache(nsIURI* aURI,
+ nsIApplicationCache** aCache) {
+ nsresult rv;
+
+ nsCOMPtr<nsIApplicationCacheService> appCacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString cacheKey;
+ rv = aURI->GetAsciiSpec(cacheKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appCacheService->ChooseApplicationCache(cacheKey, LoadInfo(), aCache);
+ 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..c6fd9cabb8
--- /dev/null
+++ b/netwerk/cache2/CacheStorage.h
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CacheStorage__h__
+#define CacheStorage__h__
+
+#include "nsICacheStorage.h"
+#include "CacheEntry.h"
+#include "LoadContextInfo.h"
+
+#include "nsRefPtrHashtable.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "nsILoadContextInfo.h"
+#include "nsIApplicationCache.h"
+
+class nsIURI;
+class nsIApplicationCache;
+
+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.
+typedef nsRefPtrHashtable<nsCStringHashKey, CacheEntry> TCacheEntryTable;
+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 aLookupAppCache,
+ bool aSkipSizeCheck, bool aPinning);
+
+ protected:
+ virtual ~CacheStorage() = default;
+
+ nsresult ChooseApplicationCache(nsIURI* aURI, nsIApplicationCache** aCache);
+
+ RefPtr<LoadContextInfo> mLoadContextInfo;
+ bool mWriteToDisk : 1;
+ bool mLookupAppCache : 1;
+ bool mSkipSizeCheck : 1;
+ bool mPinning : 1;
+
+ public:
+ nsILoadContextInfo* LoadInfo() const { return mLoadContextInfo; }
+ bool WriteToDisk() const {
+ return mWriteToDisk &&
+ (!mLoadContextInfo || !mLoadContextInfo->IsPrivate());
+ }
+ bool LookupAppCache() const { return mLookupAppCache; }
+ 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..d06c420ff4
--- /dev/null
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -0,0 +1,2376 @@
+/* -*- 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 "CacheFileIOManager.h"
+#include "CacheObserver.h"
+#include "CacheIndex.h"
+#include "CacheIndexIterator.h"
+#include "CacheStorage.h"
+#include "AppCacheStorage.h"
+#include "CacheEntry.h"
+#include "CacheFileUtils.h"
+
+#include "OldWrappers.h"
+#include "nsCacheService.h"
+#include "nsDeleteDir.h"
+
+#include "nsICacheStorageVisitor.h"
+#include "nsIObserverService.h"
+#include "nsIFile.h"
+#include "nsIURI.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/IntegerPrintfMacros.h"
+
+namespace mozilla {
+namespace 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.
+typedef nsClassHashtable<nsCStringHashKey, CacheEntryTable> GlobalEntryTables;
+
+/**
+ * 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)
+ : mReportedMemoryConsumption(0), mFlags(aFlags) {}
+
+void CacheMemoryConsumer::DoMemoryReport(uint32_t aCurrentSize) {
+ if (!(mFlags & DONT_REPORT) && CacheStorageService::Self()) {
+ CacheStorageService::Self()->OnMemoryConsumptionChange(this, aCurrentSize);
+ }
+}
+
+CacheStorageService::MemoryPool::MemoryPool(EType aType)
+ : mType(aType), mMemorySize(0) {}
+
+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()
+ : mLock("CacheStorageService.mLock"),
+ mForcedValidEntriesLock("CacheStorageService.mForcedValidEntriesLock"),
+ mShutdown(false),
+ mDiskPool(MemoryPool::DISK),
+ mMemoryPool(MemoryPool::MEMORY)
+#ifdef MOZ_TSAN
+ ,
+ mPurgeTimerActive(false)
+#endif
+{
+ 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(false).mFrecencyArray.Clear();
+ Pool(false).mExpirationArray.Clear();
+ Pool(true).mFrecencyArray.Clear();
+ Pool(true).mExpirationArray.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),
+ mSize(0),
+ mCancel(false) {
+ 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;
+
+ // clang-format off
+ MOZ_ATOMIC_BITFIELDS(mAtomicBitfields, 8, (
+ (bool, NotifyStorage, 1),
+ (bool, VisitEntries, 1)
+ ))
+ // clang-format on
+
+ Atomic<bool> mCancel;
+};
+
+// 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;
+
+ for (auto iterGlobal = sGlobalEntryTables->Iter(); !iterGlobal.Done();
+ iterGlobal.Next()) {
+ CacheEntryTable* entries = iterGlobal.UserData();
+ if (entries->Type() != CacheEntryTable::MEMORY_ONLY) {
+ continue;
+ }
+
+ for (auto iter = entries->Iter(); !iter.Done(); iter.Next()) {
+ CacheEntry* entry = iter.UserData();
+
+ 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 (!mEntryArray.Length() || mCancel) {
+ mCallback->OnCacheEntryVisitCompleted();
+ return NS_OK; // done
+ }
+
+ // Grab the next entry
+ RefPtr<CacheEntry> entry = mEntryArray[0];
+ mEntryArray.RemoveElementAt(0);
+
+ // 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,
+ int32_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, aFetchCount,
+ aLastModifiedTime, aExpirationTime,
+ aPinned, aInfo);
+ if (NS_FAILED(rv)) {
+ LOG((" callback failed, canceling the walk"));
+ mCancel = true;
+ }
+ }
+
+ private:
+ nsCString mContextKey;
+ nsTArray<RefPtr<CacheEntry>> mEntryArray;
+};
+
+// 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),
+ mDataSize(0),
+ mFetchCount(0),
+ mLastModifiedTime(0),
+ mExpirationTime(0),
+ mPinned(false) {}
+
+ 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, 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;
+ int32_t mFetchCount;
+ uint32_t mLastModifiedTime;
+ uint32_t mExpirationTime;
+ bool mPinned;
+ 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,
+ int32_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->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 (auto iter = sGlobalEntryTables->Iter(); !iter.Done(); iter.Next()) {
+ const nsACString& key = iter.Key();
+ 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);
+ }
+}
+
+namespace {
+
+class CleaupCacheDirectoriesRunnable : public Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ static bool Post();
+
+ private:
+ CleaupCacheDirectoriesRunnable()
+ : Runnable("net::CleaupCacheDirectoriesRunnable") {
+ nsCacheService::GetDiskCacheDirectory(getter_AddRefs(mCache1Dir));
+ CacheFileIOManager::GetCacheDirectory(getter_AddRefs(mCache2Dir));
+#if defined(MOZ_WIDGET_ANDROID)
+ CacheFileIOManager::GetProfilelessCacheDirectory(
+ getter_AddRefs(mCache2Profileless));
+#endif
+ }
+
+ virtual ~CleaupCacheDirectoriesRunnable() = default;
+ nsCOMPtr<nsIFile> mCache1Dir, mCache2Dir;
+#if defined(MOZ_WIDGET_ANDROID)
+ nsCOMPtr<nsIFile> mCache2Profileless;
+#endif
+};
+
+// static
+bool CleaupCacheDirectoriesRunnable::Post() {
+ // To obtain the cache1 directory we must unfortunately instantiate the old
+ // cache service despite it may not be used at all... This also initializes
+ // nsDeleteDir.
+ nsCOMPtr<nsICacheService> service = do_GetService(NS_CACHESERVICE_CONTRACTID);
+ if (!service) return false;
+
+ nsCOMPtr<nsIEventTarget> thread;
+ service->GetCacheIOTarget(getter_AddRefs(thread));
+ if (!thread) return false;
+
+ RefPtr<CleaupCacheDirectoriesRunnable> r =
+ new CleaupCacheDirectoriesRunnable();
+ thread->Dispatch(r, NS_DISPATCH_NORMAL);
+ return true;
+}
+
+NS_IMETHODIMP CleaupCacheDirectoriesRunnable::Run() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (mCache1Dir) {
+ nsDeleteDir::RemoveOldTrashes(mCache1Dir);
+ }
+ if (mCache2Dir) {
+ nsDeleteDir::RemoveOldTrashes(mCache2Dir);
+ }
+#if defined(MOZ_WIDGET_ANDROID)
+ if (mCache2Profileless) {
+ nsDeleteDir::RemoveOldTrashes(mCache2Profileless);
+ // Always delete the profileless cache on Android
+ nsDeleteDir::DeleteDir(mCache2Profileless, true, 30000);
+ }
+#endif
+
+ if (mCache1Dir) {
+ nsDeleteDir::DeleteDir(mCache1Dir, true, 30000);
+ }
+
+ return NS_OK;
+}
+
+} // namespace
+
+// static
+void CacheStorageService::CleaupCacheDirectories() {
+ // Make sure we schedule just once in case CleaupCacheDirectories gets called
+ // multiple times from some reason.
+ static bool runOnce = CleaupCacheDirectoriesRunnable::Post();
+ if (!runOnce) {
+ NS_WARNING("Could not start cache trashes cleanup");
+ }
+}
+
+// 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, false, 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, false);
+ storage.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::DiskCacheStorage(
+ nsILoadContextInfo* aLoadContextInfo, bool aLookupAppCache,
+ 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, aLookupAppCache,
+ 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 */, false /* no appcache */,
+ true /* ignore size checks */, true /* pin */);
+ storage.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::AppCacheStorage(
+ nsILoadContextInfo* aLoadContextInfo,
+ nsIApplicationCache* aApplicationCache, nsICacheStorage** _retval) {
+ NS_ENSURE_ARG(_retval);
+
+ nsCOMPtr<nsICacheStorage> storage;
+ // Using classification since cl believes we want to instantiate this method
+ // having the same name as the desired class...
+ storage =
+ new mozilla::net::AppCacheStorage(aLoadContextInfo, aApplicationCache);
+
+ storage.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP CacheStorageService::SynthesizedCacheStorage(
+ nsILoadContextInfo* aLoadContextInfo, nsICacheStorage** _retval) {
+ NS_ENSURE_ARG(aLoadContextInfo);
+ NS_ENSURE_ARG(_retval);
+
+ nsCOMPtr<nsICacheStorage> storage =
+ new CacheStorage(aLoadContextInfo, false, false,
+ true /* skip size checks for synthesized cache */,
+ false /* no pinning */);
+ 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);
+
+ nsTArray<nsCString> keys;
+ for (auto iter = sGlobalEntryTables->Iter(); !iter.Done(); iter.Next()) {
+ keys.AppendElement(iter.Key());
+ }
+
+ for (uint32_t i = 0; i < keys.Length(); ++i) {
+ DoomStorageEntries(keys[i], 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::GetUTFOrigin(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;
+}
+
+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 (auto iter = sGlobalEntryTables->Iter(); !iter.Done(); iter.Next()) {
+ bool matches = false;
+ rv =
+ CacheFileUtils::KeyMatchesLoadContextInfo(iter.Key(), info, &matches);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!matches) {
+ continue;
+ }
+
+ CacheEntryTable* table = iter.UserData();
+ MOZ_ASSERT(table);
+
+ nsTArray<RefPtr<CacheEntry>> entriesToDelete;
+
+ for (auto entryIter = table->Iter(); !entryIter.Done();
+ entryIter.Next()) {
+ CacheEntry* entry = entryIter.UserData();
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), entry->GetURI());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString origin;
+ rv = nsContentUtils::GetUTFOrigin(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(true).PurgeAll(mWhat);
+ mService->Pool(false).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();
+
+ return NS_OK;
+}
+
+// Methods used by CacheEntry for management of in-memory structures.
+
+namespace {
+
+class FrecencyComparator {
+ public:
+ bool Equals(CacheEntry* a, CacheEntry* b) const {
+ return a->GetFrecency() == b->GetFrecency();
+ }
+ bool LessThan(CacheEntry* a, CacheEntry* b) const {
+ // We deliberately want to keep the '0' frecency entries at the tail of the
+ // aray, because these are new entries and would just slow down purging of
+ // the pools based on frecency.
+ if (a->GetFrecency() == 0.0 && b->GetFrecency() > 0.0) {
+ return false;
+ }
+ if (a->GetFrecency() > 0.0 && b->GetFrecency() == 0.0) {
+ return true;
+ }
+
+ return a->GetFrecency() < b->GetFrecency();
+ }
+};
+
+class ExpirationComparator {
+ public:
+ bool Equals(CacheEntry* a, CacheEntry* b) const {
+ return a->GetExpirationTime() == b->GetExpirationTime();
+ }
+ bool LessThan(CacheEntry* a, CacheEntry* b) const {
+ return a->GetExpirationTime() < b->GetExpirationTime();
+ }
+};
+
+} // namespace
+
+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.mFrecencyArray.AppendElement(aEntry);
+ pool.mExpirationArray.AppendElement(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());
+ mozilla::DebugOnly<bool> removedFrecency =
+ pool.mFrecencyArray.RemoveElement(aEntry);
+ mozilla::DebugOnly<bool> removedExpiration =
+ pool.mExpirationArray.RemoveElement(aEntry);
+
+ MOZ_ASSERT(mShutdown || (removedFrecency && removedExpiration));
+
+ // 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->Put(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 = new CacheEntryTable(CacheEntryTable::MEMORY_ONLY);
+ sGlobalEntryTables->Put(memoryStorageID, entries);
+ 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);
+
+ TimeStamp validUntil;
+
+ if (!mForcedValidEntries.Get(aContextEntryKey, &validUntil)) {
+ return false;
+ }
+
+ if (validUntil.IsNull()) {
+ return false;
+ }
+
+ // Entry timeout not reached yet
+ if (TimeStamp::NowLoRes() <= validUntil) {
+ return true;
+ }
+
+ // Entry timeout has been reached
+ mForcedValidEntries.Remove(aContextEntryKey);
+ return false;
+}
+
+// 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);
+
+ // This will be the timeout
+ TimeStamp validUntil = now + TimeDuration::FromSeconds(aSecondsToTheFuture);
+
+ mForcedValidEntries.Put(aContextKey + aEntryKey, validUntil);
+}
+
+void CacheStorageService::RemoveEntryForceValid(nsACString const& aContextKey,
+ nsACString const& aEntryKey) {
+ mozilla::MutexAutoLock lock(mForcedValidEntriesLock);
+
+ LOG(("CacheStorageService::RemoveEntryForceValid context='%s' entryKey=%s",
+ aContextKey.BeginReading(), aEntryKey.BeginReading()));
+ 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() < now) {
+ 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->mReportedMemoryConsumption;
+ if (savedMemorySize == aCurrentMemoryConsumption) return;
+
+ // Exchange saved size with current one.
+ aConsumer->mReportedMemoryConsumption = aCurrentMemoryConsumption;
+
+ bool usingDisk = !(aConsumer->mFlags & 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;
+
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("net::CacheStorageService::PurgeOverMemoryLimit",
+ this, &CacheStorageService::PurgeOverMemoryLimit);
+ Dispatch(event);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CacheStorageService::GetName(nsACString& aName) {
+ aName.AssignLiteral("CacheStorageService");
+ return NS_OK;
+}
+
+void CacheStorageService::PurgeOverMemoryLimit() {
+ MOZ_ASSERT(IsOnManagementThread());
+
+ LOG(("CacheStorageService::PurgeOverMemoryLimit"));
+
+ 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(true).PurgeOverMemoryLimit();
+ Pool(false).PurgeOverMemoryLimit();
+}
+
+void CacheStorageService::MemoryPool::PurgeOverMemoryLimit() {
+ TimeStamp start(TimeStamp::Now());
+
+ uint32_t const memoryLimit = Limit();
+ if (mMemorySize > memoryLimit) {
+ LOG((" memory data consumption over the limit, abandon expired entries"));
+ PurgeExpired();
+ }
+
+ // No longer makes sense since:
+ // Memory entries are never purged partially, only as a whole when the memory
+ // cache limit is overreached.
+ // Disk entries throw the data away ASAP so that only metadata are kept.
+ // TODO when this concept of two separate pools is found working, the code
+ // should clean up.
+#if 0
+ if (mMemorySize > memoryLimit) {
+ LOG((" memory data consumption over the limit, abandon disk backed data"));
+ PurgeByFrecency(CacheEntry::PURGE_DATA_ONLY_DISK_BACKED);
+ }
+
+ if (mMemorySize > memoryLimit) {
+ LOG((" metadata consumtion over the limit, abandon disk backed entries"));
+ PurgeByFrecency(CacheEntry::PURGE_WHOLE_ONLY_DISK_BACKED);
+ }
+#endif
+
+ if (mMemorySize > memoryLimit) {
+ LOG((" memory data consumption over the limit, abandon any entry"));
+ PurgeByFrecency(CacheEntry::PURGE_WHOLE);
+ }
+
+ LOG((" purging took %1.2fms", (TimeStamp::Now() - start).ToMilliseconds()));
+}
+
+void CacheStorageService::MemoryPool::PurgeExpired() {
+ MOZ_ASSERT(IsOnManagementThread());
+
+ mExpirationArray.Sort(ExpirationComparator());
+ uint32_t now = NowInSeconds();
+
+ uint32_t const memoryLimit = Limit();
+
+ for (uint32_t i = 0;
+ mMemorySize > memoryLimit && i < mExpirationArray.Length();) {
+ if (CacheIOThread::YieldAndRerun()) return;
+
+ RefPtr<CacheEntry> entry = mExpirationArray[i];
+
+ uint32_t expirationTime = entry->GetExpirationTime();
+ if (expirationTime > 0 && expirationTime <= now &&
+ entry->Purge(CacheEntry::PURGE_WHOLE)) {
+ LOG((" purged expired, entry=%p, exptime=%u (now=%u)", entry.get(),
+ entry->GetExpirationTime(), now));
+ continue;
+ }
+
+ // not purged, move to the next one
+ ++i;
+ }
+}
+
+void CacheStorageService::MemoryPool::PurgeByFrecency(uint32_t aWhat) {
+ 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 = Limit() * 0.9;
+
+ // Let's do our best and try to shorten the array to at least this size so
+ // that it doesn't overgrow. We will ignore higher priority events and keep
+ // looping to try to purge while the array is larget than this size.
+ static size_t const kFrecencyArrayLengthLimit = 2000;
+
+ LOG(("MemoryPool::PurgeByFrecency, len=%zu", mFrecencyArray.Length()));
+
+ mFrecencyArray.Sort(FrecencyComparator());
+
+ for (uint32_t i = 0;
+ mMemorySize > memoryLimit && i < mFrecencyArray.Length();) {
+ if (mFrecencyArray.Length() <= kFrecencyArrayLengthLimit &&
+ CacheIOThread::YieldAndRerun()) {
+ LOG(("MemoryPool::PurgeByFrecency interrupted"));
+ return;
+ }
+
+ RefPtr<CacheEntry> entry = mFrecencyArray[i];
+ if (entry->Purge(aWhat)) {
+ LOG((" abandoned (%d), entry=%p, frecency=%1.10f", aWhat, entry.get(),
+ entry->GetFrecency()));
+ continue;
+ }
+
+ // not purged, move to the next one
+ ++i;
+ }
+
+ LOG(("MemoryPool::PurgeByFrecency done"));
+}
+
+void CacheStorageService::MemoryPool::PurgeAll(uint32_t aWhat) {
+ LOG(("CacheStorageService::MemoryPool::PurgeAll aWhat=%d", aWhat));
+ MOZ_ASSERT(IsOnManagementThread());
+
+ for (uint32_t i = 0; i < mFrecencyArray.Length();) {
+ if (CacheIOThread::YieldAndRerun()) return;
+
+ RefPtr<CacheEntry> entry = mFrecencyArray[i];
+
+ if (entry->Purge(aWhat)) {
+ LOG((" abandoned entry=%p", entry.get()));
+ continue;
+ }
+
+ // not purged, move to the next one
+ ++i;
+ }
+}
+
+// Methods exposed to and used by CacheStorage.
+
+nsresult CacheStorageService::AddStorageEntry(CacheStorage const* aStorage,
+ const nsACString& aURI,
+ const nsACString& aIdExtension,
+ bool aReplace,
+ 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(), aReplace, aResult);
+}
+
+nsresult CacheStorageService::AddStorageEntry(
+ const nsACString& aContextKey, const nsACString& aURI,
+ const nsACString& aIdExtension, bool aWriteToDisk, bool aSkipSizeCheck,
+ bool aPin, bool aReplace, 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* entries;
+ if (!sGlobalEntryTables->Get(aContextKey, &entries)) {
+ entries = new CacheEntryTable(CacheEntryTable::ALL_ENTRIES);
+ sGlobalEntryTables->Put(aContextKey, entries);
+ LOG((" new storage entries table for context '%s'",
+ aContextKey.BeginReading()));
+ }
+
+ bool entryExists = entries->Get(entryKey, getter_AddRefs(entry));
+
+ if (entryExists && !aReplace) {
+ // 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"));
+ aReplace = true;
+ }
+ }
+
+ // If truncate is demanded, delete and doom the current entry
+ if (entryExists && aReplace) {
+ 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.
+ aReplace = 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 (aReplace) {
+ RemoveEntryForceValid(aContextKey, entryKey);
+ }
+
+ // Entry is not in the hashtable or has just been truncated...
+ entry = new CacheEntry(aContextKey, aURI, aIdExtension, aWriteToDisk,
+ aSkipSizeCheck, aPin);
+ entries->Put(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 (auto iter = memoryEntries->Iter(); !iter.Done(); iter.Next()) {
+ auto entry = iter.Data();
+ RemoveExactEntry(diskEntries, iter.Key(), 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;
+ }
+ int32_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, 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 const* 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.Put(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
+ n += Pool(true).mFrecencyArray.ShallowSizeOfExcludingThis(mallocSizeOf);
+ n += Pool(true).mExpirationArray.ShallowSizeOfExcludingThis(mallocSizeOf);
+ n += Pool(false).mFrecencyArray.ShallowSizeOfExcludingThis(mallocSizeOf);
+ n += Pool(false).mExpirationArray.ShallowSizeOfExcludingThis(mallocSizeOf);
+ // 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) {
+ 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.");
+
+ MutexAutoLock lock(mLock);
+
+ // 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 (auto iter1 = sGlobalEntryTables->Iter(); !iter1.Done(); iter1.Next()) {
+ CacheStorageService::Self()->Lock().AssertCurrentThreadOwns();
+
+ CacheEntryTable* table = iter1.UserData();
+
+ size_t size = 0;
+ mozilla::MallocSizeOf mallocSizeOf = CacheStorageService::MallocSizeOf;
+
+ size += table->ShallowSizeOfIncludingThis(mallocSizeOf);
+ for (auto iter2 = table->Iter(); !iter2.Done(); iter2.Next()) {
+ size += iter2.Key().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 = iter2.Data();
+ 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>" : iter1.Key().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 net
+} // namespace mozilla
diff --git a/netwerk/cache2/CacheStorageService.h b/netwerk/cache2/CacheStorageService.h
new file mode 100644
index 0000000000..107e574701
--- /dev/null
+++ b/netwerk/cache2/CacheStorageService.h
@@ -0,0 +1,437 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsICacheStorageService.h"
+#include "nsIMemoryReporter.h"
+#include "nsINamed.h"
+#include "nsITimer.h"
+#include "nsICacheTesting.h"
+
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Mutex.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;
+ uint32_t mReportedMemoryConsumption : 30;
+ uint32_t mFlags : 2;
+
+ 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();
+
+ // Takes care of deleting any pending trashes for both cache1 and cache2
+ // as well as old cache directory.
+ static void CleaupCacheDirectories();
+
+ 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.
+ nsDataHashtable<nsCStringHashKey, TimeStamp> 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,
+ int32_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* aVisitor);
+
+ 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 in management ordered arrays, a mechanism
+ * helping with weighted purge of entries.
+ * Management arrays keep hard reference to the entry. Entry is
+ * responsible to remove it self or the service is responsible to
+ * remove the entry when it's no longer needed.
+ */
+ void RegisterEntry(CacheEntry* aEntry);
+
+ /**
+ * Deregisters the entry from management arrays. References are
+ * then released.
+ */
+ 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);
+
+ 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& aEntryKeyWithContext);
+
+ private:
+ // These are helpers for telemetry monitoring of the memory pools.
+ void TelemetryPrune(TimeStamp& now);
+ void TelemetryRecordEntryCreation(CacheEntry const* entry);
+ void TelemetryRecordEntryRemoval(CacheEntry const* 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, bool aReplace,
+ 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 PurgeOverMemoryLimit();
+
+ 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, bool aReplace,
+ CacheEntryHandle** aResult);
+
+ nsresult ClearOriginInternal(
+ const nsAString& aOrigin,
+ const mozilla::OriginAttributes& aOriginAttributes, bool aAnonymous);
+
+ static CacheStorageService* sSelf;
+
+ mozilla::Mutex mLock;
+ mozilla::Mutex mForcedValidEntriesLock;
+
+ Atomic<bool, Relaxed> mShutdown;
+
+ // Accessible only on the service thread
+ class MemoryPool {
+ public:
+ enum EType {
+ DISK,
+ MEMORY,
+ } mType;
+
+ explicit MemoryPool(EType aType);
+ ~MemoryPool();
+
+ nsTArray<RefPtr<CacheEntry>> mFrecencyArray;
+ nsTArray<RefPtr<CacheEntry>> mExpirationArray;
+ Atomic<uint32_t, Relaxed> mMemorySize;
+
+ bool OnMemoryConsumptionChange(uint32_t aSavedMemorySize,
+ uint32_t aCurrentMemoryConsumption);
+ /**
+ * Purges entries from memory based on the frecency ordered array.
+ */
+ void PurgeOverMemoryLimit();
+ void PurgeExpired();
+ void PurgeByFrecency(uint32_t aWhat);
+ void PurgeAll(uint32_t aWhat);
+
+ private:
+ uint32_t Limit() const;
+ MemoryPool() = delete;
+ };
+
+ MemoryPool mDiskPool;
+ MemoryPool mMemoryPool;
+ 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;
+#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.
+ nsDataHashtable<nsCStringHashKey, mozilla::TimeStamp> mPurgeTimeStamps;
+
+ // nsICacheTesting
+ class IOThreadSuspender : public Runnable {
+ public:
+ IOThreadSuspender()
+ : Runnable("net::CacheStorageService::IOThreadSuspender"),
+ mMon("IOThreadSuspender"),
+ mSignaled(false) {}
+ void Notify();
+
+ private:
+ virtual ~IOThreadSuspender() = default;
+ NS_IMETHOD Run() override;
+
+ Monitor mMon;
+ bool mSignaled;
+ };
+
+ 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, GetMainThreadEventTarget());
+}
+
+} // 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/OldWrappers.cpp b/netwerk/cache2/OldWrappers.cpp
new file mode 100644
index 0000000000..b96dc2ff29
--- /dev/null
+++ b/netwerk/cache2/OldWrappers.cpp
@@ -0,0 +1,1037 @@
+/* -*- 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/. */
+
+// Stuff to link the old imp to the new api - will go away!
+
+#include "CacheLog.h"
+#include "OldWrappers.h"
+#include "CacheStorage.h"
+#include "CacheStorageService.h"
+#include "LoadContextInfo.h"
+#include "nsCacheService.h"
+
+#include "nsIURI.h"
+#include "nsICacheSession.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheService.h"
+#include "nsIStreamTransportService.h"
+#include "nsIFile.h"
+#include "nsICacheEntryDoomCallback.h"
+#include "nsICacheListener.h"
+#include "nsICacheStorageVisitor.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsProxyRelease.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Telemetry.h"
+
+static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
+
+static uint32_t const CHECK_MULTITHREADED =
+ nsICacheStorage::CHECK_MULTITHREADED;
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+// Fires the doom callback back on the main thread
+// after the cache I/O thread is looped.
+
+class DoomCallbackSynchronizer : public Runnable {
+ public:
+ explicit DoomCallbackSynchronizer(nsICacheEntryDoomCallback* cb)
+ : Runnable("net::DoomCallbackSynchronizer"), mCB(cb) {}
+ nsresult Dispatch();
+
+ private:
+ virtual ~DoomCallbackSynchronizer() = default;
+
+ NS_DECL_NSIRUNNABLE
+ nsCOMPtr<nsICacheEntryDoomCallback> mCB;
+};
+
+nsresult DoomCallbackSynchronizer::Dispatch() {
+ nsresult rv;
+
+ nsCOMPtr<nsICacheService> serv =
+ do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIEventTarget> eventTarget;
+ rv = serv->GetCacheIOTarget(getter_AddRefs(eventTarget));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = eventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP DoomCallbackSynchronizer::Run() {
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(this);
+ } else {
+ if (mCB) mCB->OnCacheEntryDoomed(NS_OK);
+ }
+ return NS_OK;
+}
+
+// Receives doom callback from the old API and forwards to the new API
+
+class DoomCallbackWrapper : public nsICacheListener {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHELISTENER
+
+ explicit DoomCallbackWrapper(nsICacheEntryDoomCallback* cb) : mCB(cb) {}
+
+ private:
+ virtual ~DoomCallbackWrapper() = default;
+
+ nsCOMPtr<nsICacheEntryDoomCallback> mCB;
+};
+
+NS_IMPL_ISUPPORTS(DoomCallbackWrapper, nsICacheListener);
+
+NS_IMETHODIMP DoomCallbackWrapper::OnCacheEntryAvailable(
+ nsICacheEntryDescriptor* descriptor, nsCacheAccessMode accessGranted,
+ nsresult status) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP DoomCallbackWrapper::OnCacheEntryDoomed(nsresult status) {
+ if (!mCB) return NS_ERROR_NULL_POINTER;
+
+ mCB->OnCacheEntryDoomed(status);
+ mCB = nullptr;
+ return NS_OK;
+}
+
+} // namespace
+
+// _OldVisitCallbackWrapper
+// Receives visit callbacks from the old API and forwards it to the new API
+
+NS_IMPL_ISUPPORTS(_OldVisitCallbackWrapper, nsICacheVisitor)
+
+_OldVisitCallbackWrapper::~_OldVisitCallbackWrapper() {
+ if (!mHit) {
+ // The device has not been found, to not break the chain, simulate
+ // storage info callback.
+ mCB->OnCacheStorageInfo(0, 0, 0, nullptr);
+ }
+
+ if (mVisitEntries) {
+ mCB->OnCacheEntryVisitCompleted();
+ }
+}
+
+NS_IMETHODIMP _OldVisitCallbackWrapper::VisitDevice(
+ const char* deviceID, nsICacheDeviceInfo* deviceInfo, bool* _retval) {
+ if (!mCB) return NS_ERROR_NULL_POINTER;
+
+ *_retval = false;
+ if (strcmp(deviceID, mDeviceID)) {
+ // Not the device we want to visit
+ return NS_OK;
+ }
+
+ mHit = true;
+
+ nsresult rv;
+
+ uint32_t capacity;
+ rv = deviceInfo->GetMaximumSize(&capacity);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> dir;
+ if (!strcmp(mDeviceID, "disk")) {
+ nsCacheService::GetDiskCacheDirectory(getter_AddRefs(dir));
+ } else if (!strcmp(mDeviceID, "offline")) {
+ nsCacheService::GetAppCacheDirectory(getter_AddRefs(dir));
+ }
+
+ if (mLoadInfo && mLoadInfo->IsAnonymous()) {
+ // Anonymous visiting reports 0, 0 since we cannot count that
+ // early the number of anon entries.
+ mCB->OnCacheStorageInfo(0, 0, capacity, dir);
+ } else {
+ // Non-anon visitor counts all non-anon + ALL ANON entries,
+ // there is no way to determine the number of entries when
+ // using the old cache APIs - there is no concept of anonymous
+ // storage.
+ uint32_t entryCount;
+ rv = deviceInfo->GetEntryCount(&entryCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t totalSize;
+ rv = deviceInfo->GetTotalSize(&totalSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCB->OnCacheStorageInfo(entryCount, totalSize, capacity, dir);
+ }
+
+ *_retval = mVisitEntries;
+ return NS_OK;
+}
+
+NS_IMETHODIMP _OldVisitCallbackWrapper::VisitEntry(const char* deviceID,
+ nsICacheEntryInfo* entryInfo,
+ bool* _retval) {
+ MOZ_ASSERT(!strcmp(deviceID, mDeviceID));
+
+ nsresult rv;
+
+ *_retval = true;
+
+ // Read all informative properties from the entry.
+ nsAutoCString clientId;
+ rv = entryInfo->GetClientID(clientId);
+ if (NS_FAILED(rv)) return NS_OK;
+
+ if (mLoadInfo->IsPrivate() !=
+ StringBeginsWith(clientId, "HTTP-memory-only-PB"_ns)) {
+ return NS_OK;
+ }
+
+ nsAutoCString cacheKey, enhanceId;
+ rv = entryInfo->GetKey(cacheKey);
+ if (NS_FAILED(rv)) return NS_OK;
+
+ if (StringBeginsWith(cacheKey, "anon&"_ns)) {
+ if (!mLoadInfo->IsAnonymous()) return NS_OK;
+
+ cacheKey = Substring(cacheKey, 5, cacheKey.Length());
+ } else if (mLoadInfo->IsAnonymous()) {
+ return NS_OK;
+ }
+
+ if (StringBeginsWith(cacheKey, "id="_ns)) {
+ int32_t uriSpecEnd = cacheKey.Find("&uri=");
+ if (uriSpecEnd == kNotFound) // Corrupted, ignore
+ return NS_OK;
+
+ enhanceId = Substring(cacheKey, 3, uriSpecEnd - 3);
+ cacheKey = Substring(cacheKey, uriSpecEnd + 1, cacheKey.Length());
+ }
+
+ if (StringBeginsWith(cacheKey, "uri="_ns)) {
+ cacheKey = Substring(cacheKey, 4, cacheKey.Length());
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ // cacheKey is strip of any prefixes
+ rv = NS_NewURI(getter_AddRefs(uri), cacheKey);
+ if (NS_FAILED(rv)) return NS_OK;
+
+ uint32_t dataSize;
+ if (NS_FAILED(entryInfo->GetDataSize(&dataSize))) dataSize = 0;
+ int32_t fetchCount;
+ if (NS_FAILED(entryInfo->GetFetchCount(&fetchCount))) fetchCount = 0;
+ uint32_t expirationTime;
+ if (NS_FAILED(entryInfo->GetExpirationTime(&expirationTime)))
+ expirationTime = 0;
+ uint32_t lastModified;
+ if (NS_FAILED(entryInfo->GetLastModified(&lastModified))) lastModified = 0;
+
+ // Send them to the consumer.
+ rv = mCB->OnCacheEntryInfo(uri, enhanceId, (int64_t)dataSize, fetchCount,
+ lastModified, expirationTime, false, mLoadInfo);
+
+ *_retval = NS_SUCCEEDED(rv);
+ return NS_OK;
+}
+
+// _OldGetDiskConsumption
+
+// static
+nsresult _OldGetDiskConsumption::Get(
+ nsICacheStorageConsumptionObserver* aCallback) {
+ nsresult rv;
+
+ nsCOMPtr<nsICacheService> serv =
+ do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<_OldGetDiskConsumption> cb = new _OldGetDiskConsumption(aCallback);
+
+ // _OldGetDiskConsumption stores the found size value, but until dispatched
+ // to the main thread it doesn't call on the consupmtion observer. See bellow.
+ rv = serv->VisitEntries(cb);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We are called from CacheStorageService::AsyncGetDiskConsumption whose IDL
+ // documentation claims the callback is always delievered asynchronously
+ // back to the main thread. Despite we know the result synchronosusly when
+ // querying the old cache, we need to stand the word and dispatch the result
+ // to the main thread asynchronously. Hence the dispatch here.
+ return NS_DispatchToMainThread(cb);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(_OldGetDiskConsumption, Runnable, nsICacheVisitor)
+
+_OldGetDiskConsumption::_OldGetDiskConsumption(
+ nsICacheStorageConsumptionObserver* aCallback)
+ : Runnable("net::_OldGetDiskConsumption"), mCallback(aCallback), mSize(0) {}
+
+NS_IMETHODIMP
+_OldGetDiskConsumption::Run() {
+ mCallback->OnNetworkCacheDiskConsumption(mSize);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+_OldGetDiskConsumption::VisitDevice(const char* deviceID,
+ nsICacheDeviceInfo* deviceInfo,
+ bool* _retval) {
+ if (!strcmp(deviceID, "disk")) {
+ uint32_t size;
+ nsresult rv = deviceInfo->GetTotalSize(&size);
+ if (NS_SUCCEEDED(rv)) mSize = (int64_t)size;
+ }
+
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+_OldGetDiskConsumption::VisitEntry(const char* deviceID,
+ nsICacheEntryInfo* entryInfo,
+ bool* _retval) {
+ MOZ_CRASH("Unexpected");
+ return NS_OK;
+}
+
+// _OldCacheEntryWrapper
+
+_OldCacheEntryWrapper::_OldCacheEntryWrapper(nsICacheEntryDescriptor* desc)
+ : mOldDesc(desc), mOldInfo(desc), mCacheEntryId(CacheEntry::GetNextId()) {
+ LOG(("Creating _OldCacheEntryWrapper %p for descriptor %p", this, desc));
+}
+
+_OldCacheEntryWrapper::_OldCacheEntryWrapper(nsICacheEntryInfo* info)
+ : mOldDesc(nullptr),
+ mOldInfo(info),
+ mCacheEntryId(CacheEntry::GetNextId()) {
+ LOG(("Creating _OldCacheEntryWrapper %p for info %p", this, info));
+}
+
+_OldCacheEntryWrapper::~_OldCacheEntryWrapper() {
+ LOG(("Destroying _OldCacheEntryWrapper %p for descriptor %p", this,
+ mOldInfo.get()));
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::GetIsForcedValid(bool* aIsForcedValid) {
+ // Unused stub
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::ForceValidFor(
+ uint32_t aSecondsToTheFuture) {
+ // Unused stub
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMPL_ISUPPORTS(_OldCacheEntryWrapper, nsICacheEntry)
+
+NS_IMETHODIMP _OldCacheEntryWrapper::AsyncDoom(
+ nsICacheEntryDoomCallback* listener) {
+ RefPtr<DoomCallbackWrapper> cb =
+ listener ? new DoomCallbackWrapper(listener) : nullptr;
+ return AsyncDoom(cb);
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::GetDataSize(int64_t* aSize) {
+ uint32_t size;
+ nsresult rv = GetDataSize(&size);
+ if (NS_FAILED(rv)) return rv;
+
+ *aSize = size;
+ return NS_OK;
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::GetAltDataSize(int64_t* aSize) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::GetAltDataType(nsACString& aType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::GetPersistent(bool* aPersistToDisk) {
+ if (!mOldDesc) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsresult rv;
+
+ nsCacheStoragePolicy policy;
+ rv = mOldDesc->GetStoragePolicy(&policy);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aPersistToDisk = policy != nsICache::STORE_IN_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::Recreate(bool aMemoryOnly,
+ nsICacheEntry** aResult) {
+ NS_ENSURE_TRUE(mOldDesc, NS_ERROR_NOT_AVAILABLE);
+
+ nsCacheAccessMode mode;
+ nsresult rv = mOldDesc->GetAccessGranted(&mode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!(mode & nsICache::ACCESS_WRITE)) return NS_ERROR_NOT_AVAILABLE;
+
+ LOG(("_OldCacheEntryWrapper::Recreate [this=%p]", this));
+
+ if (aMemoryOnly) mOldDesc->SetStoragePolicy(nsICache::STORE_IN_MEMORY);
+
+ nsCOMPtr<nsICacheEntry> self(this);
+ self.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::OpenInputStream(int64_t offset,
+ nsIInputStream** _retval) {
+ if (offset > PR_UINT32_MAX) return NS_ERROR_INVALID_ARG;
+
+ return OpenInputStream(uint32_t(offset), _retval);
+}
+NS_IMETHODIMP _OldCacheEntryWrapper::OpenOutputStream(
+ int64_t offset, int64_t predictedSize, nsIOutputStream** _retval) {
+ if (offset > PR_UINT32_MAX) return NS_ERROR_INVALID_ARG;
+
+ return OpenOutputStream(uint32_t(offset), _retval);
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::MaybeMarkValid() {
+ LOG(("_OldCacheEntryWrapper::MaybeMarkValid [this=%p]", this));
+
+ NS_ENSURE_TRUE(mOldDesc, NS_ERROR_NULL_POINTER);
+
+ nsCacheAccessMode mode;
+ nsresult rv = mOldDesc->GetAccessGranted(&mode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mode & nsICache::ACCESS_WRITE) {
+ LOG(("Marking cache entry valid [entry=%p, descr=%p]", this, mOldDesc));
+ return mOldDesc->MarkValid();
+ }
+
+ LOG(("Not marking read-only cache entry valid [entry=%p, descr=%p]", this,
+ mOldDesc));
+ return NS_OK;
+}
+
+NS_IMETHODIMP _OldCacheEntryWrapper::HasWriteAccess(bool aWriteAllowed_unused,
+ bool* aWriteAccess) {
+ NS_ENSURE_TRUE(mOldDesc, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_ARG(aWriteAccess);
+
+ nsCacheAccessMode mode;
+ nsresult rv = mOldDesc->GetAccessGranted(&mode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aWriteAccess = !!(mode & nsICache::ACCESS_WRITE);
+
+ LOG(("_OldCacheEntryWrapper::HasWriteAccess [this=%p, write-access=%d]", this,
+ *aWriteAccess));
+
+ return NS_OK;
+}
+
+namespace {
+
+class MetaDataVisitorWrapper : public nsICacheMetaDataVisitor {
+ virtual ~MetaDataVisitorWrapper() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICACHEMETADATAVISITOR
+ explicit MetaDataVisitorWrapper(nsICacheEntryMetaDataVisitor* cb) : mCB(cb) {}
+ nsCOMPtr<nsICacheEntryMetaDataVisitor> mCB;
+};
+
+NS_IMPL_ISUPPORTS(MetaDataVisitorWrapper, nsICacheMetaDataVisitor)
+
+NS_IMETHODIMP
+MetaDataVisitorWrapper::VisitMetaDataElement(char const* key, char const* value,
+ bool* goon) {
+ *goon = true;
+ return mCB->OnMetaDataElement(key, value);
+}
+
+} // namespace
+
+NS_IMETHODIMP _OldCacheEntryWrapper::VisitMetaData(
+ nsICacheEntryMetaDataVisitor* cb) {
+ RefPtr<MetaDataVisitorWrapper> w = new MetaDataVisitorWrapper(cb);
+ return mOldDesc->VisitMetaData(w);
+}
+
+namespace {
+
+void GetCacheSessionNameForStoragePolicy(const nsACString& scheme,
+ nsCacheStoragePolicy storagePolicy,
+ bool isPrivate,
+ OriginAttributes const* originAttribs,
+ nsACString& sessionName) {
+ MOZ_ASSERT(!isPrivate || storagePolicy == nsICache::STORE_IN_MEMORY);
+
+ // HTTP
+ if (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https")) {
+ switch (storagePolicy) {
+ case nsICache::STORE_IN_MEMORY:
+ if (isPrivate)
+ sessionName.AssignLiteral("HTTP-memory-only-PB");
+ else
+ sessionName.AssignLiteral("HTTP-memory-only");
+ break;
+ case nsICache::STORE_OFFLINE:
+ // XXX This is actually never used, only added to prevent
+ // any compatibility damage.
+ sessionName.AssignLiteral("HTTP-offline");
+ break;
+ default:
+ sessionName.AssignLiteral("HTTP");
+ break;
+ }
+ }
+ // FTP
+ else if (scheme.EqualsLiteral("ftp")) {
+ if (isPrivate)
+ sessionName.AssignLiteral("FTP-private");
+ else
+ sessionName.AssignLiteral("FTP");
+ }
+ // all remaining URL scheme
+ else {
+ // Since with the new API a consumer cannot specify its own session name
+ // and partitioning of the cache is handled stricly only by the cache
+ // back-end internally, we will use a separate session name to pretend
+ // functionality of the new API wrapping the Darin's cache for all other
+ // URL schemes.
+ // Deliberately omitting |anonymous| since other session types don't
+ // recognize it too.
+ sessionName.AssignLiteral("other");
+ if (isPrivate) sessionName.AppendLiteral("-private");
+ }
+
+ nsAutoCString suffix;
+ originAttribs->CreateSuffix(suffix);
+ sessionName.Append(suffix);
+}
+
+nsresult GetCacheSession(const nsACString& aScheme, bool aWriteToDisk,
+ nsILoadContextInfo* aLoadInfo,
+ nsIApplicationCache* aAppCache,
+ nsICacheSession** _result) {
+ nsresult rv;
+
+ nsCacheStoragePolicy storagePolicy;
+ if (aAppCache)
+ storagePolicy = nsICache::STORE_OFFLINE;
+ else if (!aWriteToDisk || aLoadInfo->IsPrivate())
+ storagePolicy = nsICache::STORE_IN_MEMORY;
+ else
+ storagePolicy = nsICache::STORE_ANYWHERE;
+
+ nsAutoCString clientId;
+ if (aAppCache) {
+ aAppCache->GetClientID(clientId);
+ } else {
+ GetCacheSessionNameForStoragePolicy(
+ aScheme, storagePolicy, aLoadInfo->IsPrivate(),
+ aLoadInfo->OriginAttributesPtr(), clientId);
+ }
+
+ LOG((" GetCacheSession for client=%s, policy=%d", clientId.get(),
+ storagePolicy));
+
+ nsCOMPtr<nsICacheService> serv =
+ do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICacheSession> session;
+ rv = nsCacheService::GlobalInstance()->CreateSessionInternal(
+ clientId.get(), storagePolicy, nsICache::STREAM_BASED,
+ getter_AddRefs(session));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = session->SetIsPrivate(aLoadInfo->IsPrivate());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = session->SetDoomEntriesIfExpired(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aAppCache) {
+ nsCOMPtr<nsIFile> profileDirectory;
+ aAppCache->GetProfileDirectory(getter_AddRefs(profileDirectory));
+ if (profileDirectory) rv = session->SetProfileDirectory(profileDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ session.forget(_result);
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMPL_ISUPPORTS_INHERITED(_OldCacheLoad, Runnable, nsICacheListener)
+
+_OldCacheLoad::_OldCacheLoad(const nsACString& aScheme,
+ const nsACString& aCacheKey,
+ nsICacheEntryOpenCallback* aCallback,
+ nsIApplicationCache* aAppCache,
+ nsILoadContextInfo* aLoadInfo, bool aWriteToDisk,
+ uint32_t aFlags)
+ : Runnable("net::_OldCacheLoad"),
+ mScheme(aScheme),
+ mCacheKey(aCacheKey),
+ mCallback(aCallback),
+ mLoadInfo(GetLoadContextInfo(aLoadInfo)),
+ mFlags(aFlags),
+ mWriteToDisk(aWriteToDisk),
+ mNew(true),
+ mOpening(true),
+ mSync(false),
+ mStatus(NS_ERROR_UNEXPECTED),
+ mRunCount(0),
+ mAppCache(aAppCache) {}
+
+_OldCacheLoad::~_OldCacheLoad() {
+ ProxyReleaseMainThread("_OldCacheLoad::mAppCache", mAppCache);
+}
+
+nsresult _OldCacheLoad::Start() {
+ LOG(("_OldCacheLoad::Start [this=%p, key=%s]", this, mCacheKey.get()));
+
+ mLoadStart = mozilla::TimeStamp::Now();
+
+ nsresult rv;
+
+ // Consumers that can invoke this code as first and off the main thread
+ // are responsible for initiating these two services on the main thread.
+ // Currently no one does that.
+
+ // XXX: Start the cache service; otherwise DispatchToCacheIOThread will
+ // fail.
+ nsCOMPtr<nsICacheService> service =
+ do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+
+ // Ensure the stream transport service gets initialized on the main thread
+ if (NS_SUCCEEDED(rv) && NS_IsMainThread()) {
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(kStreamTransportServiceCID, &rv);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = service->GetCacheIOTarget(getter_AddRefs(mCacheThread));
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ bool onCacheTarget;
+ rv = mCacheThread->IsOnCurrentThread(&onCacheTarget);
+ if (NS_SUCCEEDED(rv) && onCacheTarget) {
+ mSync = true;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ if (mSync) {
+ rv = Run();
+ } else {
+ rv = mCacheThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+_OldCacheLoad::Run() {
+ LOG(("_OldCacheLoad::Run [this=%p, key=%s, cb=%p]", this, mCacheKey.get(),
+ mCallback.get()));
+
+ nsresult rv;
+
+ if (mOpening) {
+ mOpening = false;
+ nsCOMPtr<nsICacheSession> session;
+ rv = GetCacheSession(mScheme, mWriteToDisk, mLoadInfo, mAppCache,
+ getter_AddRefs(session));
+ if (NS_SUCCEEDED(rv)) {
+ // AsyncOpenCacheEntry isn't really async when its called on the
+ // cache service thread.
+
+ nsCacheAccessMode cacheAccess;
+ if (mFlags & nsICacheStorage::OPEN_TRUNCATE)
+ cacheAccess = nsICache::ACCESS_WRITE;
+ else if ((mFlags & nsICacheStorage::OPEN_READONLY) || mAppCache)
+ cacheAccess = nsICache::ACCESS_READ;
+ else
+ cacheAccess = nsICache::ACCESS_READ_WRITE;
+
+ LOG((" session->AsyncOpenCacheEntry with access=%d", cacheAccess));
+
+ bool bypassBusy = mFlags & nsICacheStorage::OPEN_BYPASS_IF_BUSY;
+
+ if (mSync && cacheAccess == nsICache::ACCESS_WRITE) {
+ nsCOMPtr<nsICacheEntryDescriptor> entry;
+ rv = session->OpenCacheEntry(mCacheKey, cacheAccess, bypassBusy,
+ getter_AddRefs(entry));
+
+ nsCacheAccessMode grantedAccess = 0;
+ if (NS_SUCCEEDED(rv)) {
+ entry->GetAccessGranted(&grantedAccess);
+ }
+
+ return OnCacheEntryAvailable(entry, grantedAccess, rv);
+ }
+
+ rv = session->AsyncOpenCacheEntry(mCacheKey, cacheAccess, this,
+ bypassBusy);
+ if (NS_SUCCEEDED(rv)) return NS_OK;
+ }
+
+ // Opening failed, propagate the error to the consumer
+ LOG((" Opening cache entry failed with rv=0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ mStatus = rv;
+ mNew = false;
+ NS_DispatchToMainThread(this);
+ } else {
+ if (!mCallback) {
+ LOG((" duplicate call, bypassed"));
+ return NS_OK;
+ }
+
+ if (NS_SUCCEEDED(mStatus)) {
+ if (mFlags & nsICacheStorage::OPEN_TRUNCATE) {
+ mozilla::Telemetry::AccumulateTimeDelta(
+ mozilla::Telemetry::NETWORK_CACHE_V1_TRUNCATE_TIME_MS, mLoadStart);
+ } else if (mNew) {
+ mozilla::Telemetry::AccumulateTimeDelta(
+ mozilla::Telemetry::NETWORK_CACHE_V1_MISS_TIME_MS, mLoadStart);
+ } else {
+ mozilla::Telemetry::AccumulateTimeDelta(
+ mozilla::Telemetry::NETWORK_CACHE_V1_HIT_TIME_MS, mLoadStart);
+ }
+ }
+
+ if (!(mFlags & CHECK_MULTITHREADED)) Check();
+
+ // break cycles
+ nsCOMPtr<nsICacheEntryOpenCallback> cb = std::move(mCallback);
+ mCacheThread = nullptr;
+ nsCOMPtr<nsICacheEntry> entry = std::move(mCacheEntry);
+
+ rv = cb->OnCacheEntryAvailable(entry, mNew, mAppCache, mStatus);
+
+ if (NS_FAILED(rv) && entry) {
+ LOG((" cb->OnCacheEntryAvailable failed with rv=0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ if (mNew)
+ entry->AsyncDoom(nullptr);
+ else
+ entry->Close();
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+_OldCacheLoad::OnCacheEntryAvailable(nsICacheEntryDescriptor* entry,
+ nsCacheAccessMode access,
+ nsresult status) {
+ LOG(
+ ("_OldCacheLoad::OnCacheEntryAvailable [this=%p, ent=%p, cb=%p, "
+ "appcache=%p, access=%x]",
+ this, entry, mCallback.get(), mAppCache.get(), access));
+
+ // XXX Bug 759805: Sometimes we will call this method directly from
+ // HttpCacheQuery::Run when AsyncOpenCacheEntry fails, but
+ // AsyncOpenCacheEntry will also call this method. As a workaround, we just
+ // ensure we only execute this code once.
+ NS_ENSURE_TRUE(mRunCount == 0, NS_ERROR_UNEXPECTED);
+ ++mRunCount;
+
+ mCacheEntry = entry ? new _OldCacheEntryWrapper(entry) : nullptr;
+ mStatus = status;
+ mNew = access == nsICache::ACCESS_WRITE;
+
+ if (mFlags & CHECK_MULTITHREADED) Check();
+
+ if (mSync) return Run();
+
+ return NS_DispatchToMainThread(this);
+}
+
+void _OldCacheLoad::Check() {
+ if (!mCacheEntry) return;
+
+ if (mNew) return;
+
+ uint32_t result;
+ nsresult rv = mCallback->OnCacheEntryCheck(mCacheEntry, mAppCache, &result);
+ LOG((" OnCacheEntryCheck result ent=%p, cb=%p, appcache=%p, rv=0x%08" PRIx32
+ ", result=%d",
+ mCacheEntry.get(), mCallback.get(), mAppCache.get(),
+ static_cast<uint32_t>(rv), result));
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("cache check failed");
+ }
+
+ if (NS_FAILED(rv) || result == nsICacheEntryOpenCallback::ENTRY_NOT_WANTED) {
+ mCacheEntry->Close();
+ mCacheEntry = nullptr;
+ mStatus = NS_ERROR_CACHE_KEY_NOT_FOUND;
+ }
+}
+
+NS_IMETHODIMP
+_OldCacheLoad::OnCacheEntryDoomed(nsresult) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+// nsICacheStorage old cache wrapper
+
+NS_IMPL_ISUPPORTS(_OldStorage, nsICacheStorage)
+
+_OldStorage::_OldStorage(nsILoadContextInfo* aInfo, bool aAllowDisk,
+ bool aLookupAppCache, bool aOfflineStorage,
+ nsIApplicationCache* aAppCache)
+ : mLoadInfo(GetLoadContextInfo(aInfo)),
+ mAppCache(aAppCache),
+ mWriteToDisk(aAllowDisk),
+ mLookupAppCache(aLookupAppCache),
+ mOfflineStorage(aOfflineStorage) {}
+
+_OldStorage::~_OldStorage() = default;
+
+NS_IMETHODIMP _OldStorage::AsyncOpenURI(nsIURI* aURI,
+ const nsACString& aIdExtension,
+ uint32_t aFlags,
+ nsICacheEntryOpenCallback* aCallback) {
+ NS_ENSURE_ARG(aURI);
+ NS_ENSURE_ARG(aCallback);
+
+#ifdef MOZ_LOGGING
+ nsAutoCString uriSpec;
+ aURI->GetAsciiSpec(uriSpec);
+ LOG(("_OldStorage::AsyncOpenURI [this=%p, uri=%s, ide=%s, flags=%x]", this,
+ uriSpec.get(), aIdExtension.BeginReading(), aFlags));
+#endif
+
+ nsresult rv;
+
+ nsAutoCString cacheKey, scheme;
+ rv = AssembleCacheKey(aURI, aIdExtension, cacheKey, scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mAppCache && (mLookupAppCache || mOfflineStorage)) {
+ rv = ChooseApplicationCache(cacheKey, getter_AddRefs(mAppCache));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mAppCache) {
+ // From a chosen appcache open only as readonly
+ aFlags &= ~nsICacheStorage::OPEN_TRUNCATE;
+ }
+ }
+
+ RefPtr<_OldCacheLoad> cacheLoad = new _OldCacheLoad(
+ scheme, cacheKey, aCallback, mAppCache, mLoadInfo, mWriteToDisk, aFlags);
+
+ rv = cacheLoad->Start();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP _OldStorage::OpenTruncate(nsIURI* aURI,
+ const nsACString& aIdExtension,
+ nsICacheEntry** aCacheEntry) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP _OldStorage::Exists(nsIURI* aURI, const nsACString& aIdExtension,
+ bool* aResult) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP _OldStorage::AsyncDoomURI(nsIURI* aURI,
+ const nsACString& aIdExtension,
+ nsICacheEntryDoomCallback* aCallback) {
+ LOG(("_OldStorage::AsyncDoomURI"));
+
+ nsresult rv;
+
+ nsAutoCString cacheKey, scheme;
+ rv = AssembleCacheKey(aURI, aIdExtension, cacheKey, scheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICacheSession> session;
+ rv = GetCacheSession(scheme, mWriteToDisk, mLoadInfo, mAppCache,
+ getter_AddRefs(session));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<DoomCallbackWrapper> cb =
+ aCallback ? new DoomCallbackWrapper(aCallback) : nullptr;
+ rv = session->DoomEntry(cacheKey, cb);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP _OldStorage::AsyncEvictStorage(
+ nsICacheEntryDoomCallback* aCallback) {
+ LOG(("_OldStorage::AsyncEvictStorage"));
+
+ nsresult rv;
+
+ if (!mAppCache && mOfflineStorage) {
+ nsCOMPtr<nsIApplicationCacheService> appCacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appCacheService->Evict(mLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (mAppCache) {
+ nsCOMPtr<nsICacheSession> session;
+ rv = GetCacheSession(""_ns, mWriteToDisk, mLoadInfo, mAppCache,
+ getter_AddRefs(session));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = session->EvictEntries();
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // Oh, I'll be so happy when session names are gone...
+ nsCOMPtr<nsICacheSession> session;
+ rv = GetCacheSession("http"_ns, mWriteToDisk, mLoadInfo, mAppCache,
+ getter_AddRefs(session));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = session->EvictEntries();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This clears any data from schemes other than http or ftp.
+ rv = GetCacheSession(""_ns, mWriteToDisk, mLoadInfo, mAppCache,
+ getter_AddRefs(session));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = session->EvictEntries();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aCallback) {
+ RefPtr<DoomCallbackSynchronizer> sync =
+ new DoomCallbackSynchronizer(aCallback);
+ rv = sync->Dispatch();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP _OldStorage::AsyncVisitStorage(nsICacheStorageVisitor* aVisitor,
+ bool aVisitEntries) {
+ LOG(("_OldStorage::AsyncVisitStorage"));
+
+ NS_ENSURE_ARG(aVisitor);
+
+ nsresult rv;
+
+ nsCOMPtr<nsICacheService> serv =
+ do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char* deviceID;
+ if (mAppCache || mOfflineStorage) {
+ deviceID = const_cast<char*>("offline");
+ } else if (!mWriteToDisk || mLoadInfo->IsPrivate()) {
+ deviceID = const_cast<char*>("memory");
+ } else {
+ deviceID = const_cast<char*>("disk");
+ }
+
+ RefPtr<_OldVisitCallbackWrapper> cb = new _OldVisitCallbackWrapper(
+ deviceID, aVisitor, aVisitEntries, mLoadInfo);
+ rv = nsCacheService::GlobalInstance()->VisitEntriesInternal(cb);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP _OldStorage::GetCacheIndexEntryAttrs(
+ nsIURI* aURI, const nsACString& aIdExtension, bool* aHasAltData,
+ uint32_t* aSizeInKB) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// Internal
+
+nsresult _OldStorage::AssembleCacheKey(nsIURI* aURI,
+ nsACString const& aIdExtension,
+ nsACString& aCacheKey,
+ nsACString& aScheme) {
+ // Copied from nsHttpChannel::AssembleCacheKey
+
+ aCacheKey.Truncate();
+
+ nsresult rv;
+
+ rv = aURI->GetScheme(aScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uriSpec;
+ if (aScheme.EqualsLiteral("http") || aScheme.EqualsLiteral("https")) {
+ if (mLoadInfo->IsAnonymous()) {
+ aCacheKey.AssignLiteral("anon&");
+ }
+
+ if (!aIdExtension.IsEmpty()) {
+ aCacheKey.AppendPrintf("id=%s&", aIdExtension.BeginReading());
+ }
+
+ nsCOMPtr<nsIURI> noRefURI;
+ rv = NS_GetURIWithoutRef(aURI, getter_AddRefs(noRefURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = noRefURI->GetAsciiSpec(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aCacheKey.IsEmpty()) {
+ aCacheKey.AppendLiteral("uri=");
+ }
+ } else {
+ rv = aURI->GetAsciiSpec(uriSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aCacheKey.Append(uriSpec);
+
+ return NS_OK;
+}
+
+nsresult _OldStorage::ChooseApplicationCache(const nsACString& cacheKey,
+ nsIApplicationCache** aCache) {
+ nsresult rv;
+
+ nsCOMPtr<nsIApplicationCacheService> appCacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appCacheService->ChooseApplicationCache(cacheKey, mLoadInfo, aCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cache2/OldWrappers.h b/netwerk/cache2/OldWrappers.h
new file mode 100644
index 0000000000..46a2f222b6
--- /dev/null
+++ b/netwerk/cache2/OldWrappers.h
@@ -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/. */
+
+// Stuff to link the old imp to the new api - will go away!
+
+#ifndef OLDWRAPPERS__H__
+#define OLDWRAPPERS__H__
+
+#include "nsICacheEntry.h"
+#include "nsICacheListener.h"
+#include "nsICacheStorage.h"
+
+#include "nsCOMPtr.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsICacheEntryDescriptor.h"
+#include "nsICacheStorageVisitor.h"
+#include "nsThreadUtils.h"
+#include "mozilla/TimeStamp.h"
+
+class nsIURI;
+class nsICacheEntryOpenCallback;
+class nsICacheStorageConsumptionObserver;
+class nsIApplicationCache;
+class nsILoadContextInfo;
+
+namespace mozilla {
+namespace net {
+
+class CacheStorage;
+
+class _OldCacheEntryWrapper : public nsICacheEntry {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsICacheEntryDescriptor
+ NS_IMETHOD SetExpirationTime(uint32_t expirationTime) override {
+ return !mOldDesc ? NS_ERROR_NULL_POINTER
+ : mOldDesc->SetExpirationTime(expirationTime);
+ }
+ nsresult OpenInputStream(uint32_t offset, nsIInputStream** _retval) {
+ return !mOldDesc ? NS_ERROR_NULL_POINTER
+ : mOldDesc->OpenInputStream(offset, _retval);
+ }
+ nsresult OpenOutputStream(uint32_t offset, nsIOutputStream** _retval) {
+ return !mOldDesc ? NS_ERROR_NULL_POINTER
+ : mOldDesc->OpenOutputStream(offset, _retval);
+ }
+ NS_IMETHOD OpenAlternativeOutputStream(
+ const nsACString& type, int64_t predictedSize,
+ nsIAsyncOutputStream** _retval) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ NS_IMETHOD OpenAlternativeInputStream(const nsACString& type,
+ nsIInputStream** _retval) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD GetSecurityInfo(nsISupports** aSecurityInfo) override {
+ return !mOldDesc ? NS_ERROR_NULL_POINTER
+ : mOldDesc->GetSecurityInfo(aSecurityInfo);
+ }
+ NS_IMETHOD SetSecurityInfo(nsISupports* aSecurityInfo) override {
+ return !mOldDesc ? NS_ERROR_NULL_POINTER
+ : mOldDesc->SetSecurityInfo(aSecurityInfo);
+ }
+ NS_IMETHOD GetStorageDataSize(uint32_t* aStorageDataSize) override {
+ return !mOldDesc ? NS_ERROR_NULL_POINTER
+ : mOldDesc->GetStorageDataSize(aStorageDataSize);
+ }
+ nsresult AsyncDoom(nsICacheListener* listener) {
+ return !mOldDesc ? NS_ERROR_NULL_POINTER : mOldDesc->AsyncDoom(listener);
+ }
+ NS_IMETHOD MarkValid(void) override {
+ return !mOldDesc ? NS_ERROR_NULL_POINTER : mOldDesc->MarkValid();
+ }
+ NS_IMETHOD Close(void) override {
+ return !mOldDesc ? NS_ERROR_NULL_POINTER : mOldDesc->Close();
+ }
+ NS_IMETHOD GetMetaDataElement(const char* key, char** _retval) override {
+ return !mOldDesc ? NS_ERROR_NULL_POINTER
+ : mOldDesc->GetMetaDataElement(key, _retval);
+ }
+ NS_IMETHOD SetMetaDataElement(const char* key, const char* value) override {
+ return !mOldDesc ? NS_ERROR_NULL_POINTER
+ : mOldDesc->SetMetaDataElement(key, value);
+ }
+
+ NS_IMETHOD GetDiskStorageSizeInKB(uint32_t* aDiskStorageSize) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ // nsICacheEntryInfo
+ NS_IMETHOD GetKey(nsACString& aKey) override {
+ return mOldInfo->GetKey(aKey);
+ }
+ NS_IMETHOD GetCacheEntryId(uint64_t* aCacheEntryId) override {
+ *aCacheEntryId = mCacheEntryId;
+ return NS_OK;
+ }
+ NS_IMETHOD GetFetchCount(int32_t* aFetchCount) override {
+ return mOldInfo->GetFetchCount(aFetchCount);
+ }
+ NS_IMETHOD GetLastFetched(uint32_t* aLastFetched) override {
+ return mOldInfo->GetLastFetched(aLastFetched);
+ }
+ NS_IMETHOD GetLastModified(uint32_t* aLastModified) override {
+ return mOldInfo->GetLastModified(aLastModified);
+ }
+ NS_IMETHOD GetExpirationTime(uint32_t* aExpirationTime) override {
+ return mOldInfo->GetExpirationTime(aExpirationTime);
+ }
+ nsresult GetDataSize(uint32_t* aDataSize) {
+ return mOldInfo->GetDataSize(aDataSize);
+ }
+ NS_IMETHOD GetOnStartTime(uint64_t* aTime) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ NS_IMETHOD GetOnStopTime(uint64_t* aTime) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ NS_IMETHOD SetNetworkTimes(uint64_t aOnStartTime,
+ uint64_t aOnStopTime) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ NS_IMETHOD SetContentType(uint8_t aContentType) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ NS_IMETHOD GetLoadContextInfo(nsILoadContextInfo** aInfo) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ NS_IMETHOD Dismiss() override { return NS_ERROR_NOT_IMPLEMENTED; }
+
+ NS_IMETHOD AsyncDoom(nsICacheEntryDoomCallback* listener) override;
+ NS_IMETHOD GetPersistent(bool* aPersistToDisk) override;
+ NS_IMETHOD GetIsForcedValid(bool* aIsForcedValid) override;
+ NS_IMETHOD ForceValidFor(uint32_t aSecondsToTheFuture) override;
+ NS_IMETHOD SetValid() override { return NS_OK; }
+ NS_IMETHOD MetaDataReady() override { return NS_OK; }
+ NS_IMETHOD Recreate(bool, nsICacheEntry**) override;
+ NS_IMETHOD GetDataSize(int64_t* size) override;
+ NS_IMETHOD GetAltDataSize(int64_t* size) override;
+ NS_IMETHOD GetAltDataType(nsACString& aType) override;
+ NS_IMETHOD OpenInputStream(int64_t offset, nsIInputStream** _retval) override;
+ NS_IMETHOD OpenOutputStream(int64_t offset, int64_t predictedSize,
+ nsIOutputStream** _retval) override;
+ NS_IMETHOD MaybeMarkValid() override;
+ NS_IMETHOD HasWriteAccess(bool aWriteOnly, bool* aWriteAccess) override;
+ NS_IMETHOD VisitMetaData(nsICacheEntryMetaDataVisitor*) override;
+
+ explicit _OldCacheEntryWrapper(nsICacheEntryDescriptor* desc);
+ explicit _OldCacheEntryWrapper(nsICacheEntryInfo* info);
+
+ private:
+ virtual ~_OldCacheEntryWrapper();
+
+ _OldCacheEntryWrapper() = delete;
+ nsICacheEntryDescriptor* mOldDesc; // ref holded in mOldInfo
+ nsCOMPtr<nsICacheEntryInfo> mOldInfo;
+
+ const uint64_t mCacheEntryId;
+};
+
+class _OldCacheLoad : public Runnable, public nsICacheListener {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSICACHELISTENER
+
+ _OldCacheLoad(const nsACString& aScheme, const nsACString& aCacheKey,
+ nsICacheEntryOpenCallback* aCallback,
+ nsIApplicationCache* aAppCache, nsILoadContextInfo* aLoadInfo,
+ bool aWriteToDisk, uint32_t aFlags);
+
+ nsresult Start();
+
+ protected:
+ virtual ~_OldCacheLoad();
+
+ private:
+ void Check();
+
+ nsCOMPtr<nsIEventTarget> mCacheThread;
+
+ nsCString const mScheme;
+ nsCString const mCacheKey;
+ nsCOMPtr<nsICacheEntryOpenCallback> mCallback;
+ nsCOMPtr<nsILoadContextInfo> mLoadInfo;
+ uint32_t const mFlags;
+
+ bool const mWriteToDisk : 1;
+ bool mNew : 1;
+ bool mOpening : 1;
+ bool mSync : 1;
+
+ nsCOMPtr<nsICacheEntry> mCacheEntry;
+ nsresult mStatus;
+ uint32_t mRunCount;
+ nsCOMPtr<nsIApplicationCache> mAppCache;
+
+ mozilla::TimeStamp mLoadStart;
+};
+
+class _OldStorage : public nsICacheStorage {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHESTORAGE
+
+ public:
+ _OldStorage(nsILoadContextInfo* aInfo, bool aAllowDisk, bool aLookupAppCache,
+ bool aOfflineStorage, nsIApplicationCache* aAppCache);
+
+ private:
+ virtual ~_OldStorage();
+ nsresult AssembleCacheKey(nsIURI* aURI, nsACString const& aIdExtension,
+ nsACString& aCacheKey, nsACString& aScheme);
+ nsresult ChooseApplicationCache(const nsACString& cacheKey,
+ nsIApplicationCache** aCache);
+
+ nsCOMPtr<nsILoadContextInfo> mLoadInfo;
+ nsCOMPtr<nsIApplicationCache> mAppCache;
+ bool const mWriteToDisk : 1;
+ bool const mLookupAppCache : 1;
+ bool const mOfflineStorage : 1;
+};
+
+class _OldVisitCallbackWrapper : public nsICacheVisitor {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICACHEVISITOR
+
+ _OldVisitCallbackWrapper(char const* deviceID, nsICacheStorageVisitor* cb,
+ bool visitEntries, nsILoadContextInfo* aInfo)
+ : mCB(cb),
+ mVisitEntries(visitEntries),
+ mDeviceID(deviceID),
+ mLoadInfo(aInfo),
+ mHit(false) {}
+
+ private:
+ virtual ~_OldVisitCallbackWrapper();
+ nsCOMPtr<nsICacheStorageVisitor> mCB;
+ bool mVisitEntries;
+ char const* mDeviceID;
+ nsCOMPtr<nsILoadContextInfo> mLoadInfo;
+ bool mHit; // set to true when the device was found
+};
+
+class _OldGetDiskConsumption : public Runnable, public nsICacheVisitor {
+ public:
+ static nsresult Get(nsICacheStorageConsumptionObserver* aCallback);
+
+ private:
+ explicit _OldGetDiskConsumption(
+ nsICacheStorageConsumptionObserver* aCallback);
+ virtual ~_OldGetDiskConsumption() = default;
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSICACHEVISITOR
+ NS_DECL_NSIRUNNABLE
+
+ nsCOMPtr<nsICacheStorageConsumptionObserver> mCallback;
+ int64_t mSize;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/cache2/moz.build b/netwerk/cache2/moz.build
new file mode 100644
index 0000000000..f46600cfdf
--- /dev/null
+++ b/netwerk/cache2/moz.build
@@ -0,0 +1,62 @@
+# -*- 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",
+ "nsICacheStorage.idl",
+ "nsICacheStorageService.idl",
+ "nsICacheStorageVisitor.idl",
+ "nsICacheTesting.idl",
+]
+
+XPIDL_MODULE = "necko_cache2"
+
+EXPORTS += [
+ "CacheObserver.h",
+ "CacheStorageService.h",
+]
+
+SOURCES += [
+ "AppCacheStorage.cpp",
+ "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",
+ "OldWrappers.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ "/netwerk/cache",
+]
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/netwerk/cache2/nsICacheEntry.idl b/netwerk/cache2/nsICacheEntry.idl
new file mode 100644
index 0000000000..c9732496b1
--- /dev/null
+++ b/netwerk/cache2/nsICacheEntry.idl
@@ -0,0 +1,364 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 "nsICache.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+interface nsIAsyncOutputStream;
+interface nsICacheEntryDoomCallback;
+
+interface nsICacheListener;
+interface nsIFile;
+interface nsICacheEntryMetaDataVisitor;
+interface nsILoadContextInfo;
+
+[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 long 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;
+
+ /**
+ * 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 nsISupports 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..5cafd63770
--- /dev/null
+++ b/netwerk/cache2/nsICacheEntryOpenCallback.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsICacheEntry;
+interface nsIApplicationCache;
+
+[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.
+ * @param aApplicationCache
+ * Optional, application cache the entry has been found in, if any.
+ * @return
+ * State of the entry, see the constants just above.
+ */
+ unsigned long onCacheEntryCheck(in nsICacheEntry aEntry,
+ in nsIApplicationCache aApplicationCache);
+
+ /**
+ * 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. May be null when
+ * loading from a particular application cache and the URI has not
+ * been found in that application cache.
+ * @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 aApplicationCache
+ * When an entry had been found in an application cache, this is the
+ * given application cache. It should be associated with the loading
+ * channel.
+ * @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
+ * - cache entry was not found in a given application cache
+ */
+ void onCacheEntryAvailable(in nsICacheEntry aEntry,
+ in boolean aNew,
+ in nsIApplicationCache aApplicationCache,
+ in nsresult aResult);
+};
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..c3aa08e555
--- /dev/null
+++ b/netwerk/cache2/nsICacheStorageService.idl
@@ -0,0 +1,155 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 nsIApplicationCache;
+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.
+ *
+ * @param aLookupAppCache
+ * When set true (for top level document loading channels) app cache will
+ * be first to check on to find entries in.
+ */
+ nsICacheStorage diskCacheStorage(in nsILoadContextInfo aLoadContextInfo,
+ in bool aLookupAppCache);
+
+ /**
+ * 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);
+
+ /**
+ * Get storage for a specified application cache obtained using some different
+ * mechanism.
+ *
+ * @param aLoadContextInfo
+ * Mandatory reference to a load context information.
+ * @param aApplicationCache
+ * Optional reference to an existing appcache. When left null, this will
+ * work with offline cache as a whole.
+ */
+ nsICacheStorage appCacheStorage(in nsILoadContextInfo aLoadContextInfo,
+ in nsIApplicationCache aApplicationCache);
+
+ /**
+ * Get storage for synthesized cache entries that we currently use for ServiceWorker interception in non-e10s mode.
+ *
+ * This cache storage has no limits on file size to allow the ServiceWorker to intercept large files.
+ */
+ nsICacheStorage synthesizedCacheStorage(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 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..2d16b26615
--- /dev/null
+++ b/netwerk/cache2/nsICacheStorageVisitor.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 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 long 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..e83a2fcae2
--- /dev/null
+++ b/netwerk/cookie/Cookie.cpp
@@ -0,0 +1,219 @@
+/* -*- 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 "mozilla/Encoding.h"
+#include "mozilla/dom/ToJSValue.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 = 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 the creationTime given to us is higher than the running maximum,
+ // update our maximum.
+ if (cookie->mData.creationTime() > gLastCreationTime) {
+ gLastCreationTime = cookie->mData.creationTime();
+ }
+
+ // If sameSite is not a sensible value, assume strict
+ if (cookie->mData.sameSite() < 0 ||
+ cookie->mData.sameSite() > nsICookie::SAMESITE_STRICT) {
+ cookie->mData.sameSite() = nsICookie::SAMESITE_STRICT;
+ }
+
+ // If rawSameSite is not a sensible value, assume equal to sameSite.
+ if (!Cookie::ValidateRawSame(cookie->mData)) {
+ cookie->mData.rawSameSite() = nsICookie::SAMESITE_NONE;
+ }
+
+ 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::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 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::ValidateRawSame(const CookieStruct& aCookieData) {
+ return aCookieData.rawSameSite() == aCookieData.sameSite() ||
+ aCookieData.rawSameSite() == nsICookie::SAMESITE_NONE;
+}
+
+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..94a49db7c6
--- /dev/null
+++ b/netwerk/cookie/Cookie.h
@@ -0,0 +1,145 @@
+/* -*- 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) {}
+
+ public:
+ // Returns false if rawSameSite has an invalid value, compared to sameSite.
+ static bool ValidateRawSame(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);
+
+ 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 const OriginAttributes& OriginAttributesRef() const {
+ return mOriginAttributes;
+ }
+ inline int32_t SameSite() const { return mData.sameSite(); }
+ inline int32_t RawSameSite() const { return mData.rawSameSite(); }
+ 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;
+ }
+
+ 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..07cfd9efcb
--- /dev/null
+++ b/netwerk/cookie/CookieCommons.cpp
@@ -0,0 +1,698 @@
+/* -*- 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/ContentBlocking.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/ContentBlockingNotifier.h"
+#include "mozilla/ScopeExit.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"
+
+constexpr auto CONSOLE_SCHEMEFUL_CATEGORY = "cookieSchemeful"_ns;
+
+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();
+ if (isPrefix && aPath[cookiePathLen] == '/') {
+ return true;
+ }
+
+ return false;
+}
+
+// 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);
+ }
+
+ return aPrincipal->GetBaseDomain(aBaseDomain);
+}
+
+// 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, 0x00};
+
+ return aCookieData.name().FindCharInSet(illegalNameCharacters, 0) == -1;
+}
+
+bool CookieCommons::CheckHttpValue(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, 0x5C, and 0x7F are missing from this list. This is
+ // for parity with Chrome. This only applies to cookies set via the Set-Cookie
+ // header, as document.cookie is defined to be UTF-8. Hooray for
+ // symmetry!</sarcasm>
+ 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, 0x00};
+ return aCookieData.value().FindCharInSet(illegalCharacters, 0) == -1;
+}
+
+// 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;
+ }
+
+ // Here we can have any legacy permission value.
+
+ // now we need to figure out what type of accept policy we're dealing with
+ // if we accept cookies normally, just bail and return
+ if (StaticPrefs::network_cookie_lifetimePolicy() ==
+ nsICookieService::ACCEPT_NORMALLY) {
+ return true;
+ }
+
+ // declare this here since it'll be used in all of the remaining cases
+ int64_t currentTime = PR_Now() / PR_USEC_PER_SEC;
+ int64_t delta = aCookieData.expiry() - currentTime;
+
+ // We are accepting the cookie, but,
+ // if it's not a session cookie, we may have to limit its lifetime.
+ if (!aCookieData.isSession() && delta > 0) {
+ if (StaticPrefs::network_cookie_lifetimePolicy() ==
+ nsICookieService::ACCEPT_SESSION) {
+ // limit lifetime to session
+ aCookieData.isSession() = true;
+ }
+ }
+
+ return true;
+}
+
+namespace {
+
+CookieStatus CookieStatusForWindow(nsPIDOMWindowInner* aWindow,
+ nsIURI* aDocumentURI) {
+ MOZ_ASSERT(aWindow);
+ MOZ_ASSERT(aDocumentURI);
+
+ if (!nsContentUtils::IsThirdPartyWindowOrChannel(aWindow, nullptr,
+ aDocumentURI)) {
+ 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->EffectiveStoragePrincipal();
+ 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()) &&
+ !ContentBlocking::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;
+ }
+ }
+
+ // 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;
+ bool canSetCookie = false;
+ CookieService::CanSetCookie(principalURI, baseDomain, cookieData,
+ requireHostMatch, cookieStatus, cookieString,
+ false, isForeignAndNotAddon, 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;
+ if (aChannel) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ nsresult rv =
+ loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ cookieJarSettings = CookieJarSettings::GetBlockingAll();
+ }
+ } else {
+ cookieJarSettings = CookieJarSettings::Create();
+ }
+
+ MOZ_ASSERT(cookieJarSettings);
+ return cookieJarSettings.forget();
+}
+
+// static
+bool CookieCommons::ShouldIncludeCrossSiteCookieForDocument(Cookie* aCookie) {
+ MOZ_ASSERT(aCookie);
+
+ int32_t sameSiteAttr = 0;
+ aCookie->GetSameSite(&sameSiteAttr);
+
+ return sameSiteAttr == nsICookie::SAMESITE_NONE;
+}
+
+bool CookieCommons::IsSafeTopLevelNav(nsIChannel* aChannel) {
+ if (!aChannel) {
+ return false;
+ }
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ if (loadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT &&
+ loadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) {
+ return false;
+ }
+ return NS_IsSafeMethodNav(aChannel);
+}
+
+bool CookieCommons::IsSameSiteForeign(nsIChannel* aChannel, nsIURI* aHostURI) {
+ 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));
+ RefPtr<BasePrincipal> triggeringPrincipal =
+ BasePrincipal::Cast(loadInfo->TriggeringPrincipal());
+ if (triggeringPrincipal->AddonPolicy() &&
+ triggeringPrincipal->AddonAllowsLoad(channelURI)) {
+ return false;
+ }
+
+ bool isForeign = true;
+ nsresult rv;
+ if (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT ||
+ loadInfo->GetExternalContentPolicyType() ==
+ 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);
+ } else {
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+ do_GetService(THIRDPARTYUTIL_CONTRACTID);
+ if (!thirdPartyUtil) {
+ return true;
+ }
+ rv = thirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign);
+ }
+ // 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;
+ }
+
+ // 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 (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ rv = loadInfo->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 : loadInfo->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) {
+ return true;
+ }
+ }
+ }
+ return isForeign;
+}
+
+namespace {
+
+bool MaybeCompareSchemeInternal(Cookie* aCookie,
+ nsICookie::schemeType aSchemeType) {
+ MOZ_ASSERT(aCookie);
+
+ // This is an old cookie without a scheme yet. Let's consider it valid.
+ if (aCookie->SchemeMap() == nsICookie::SCHEME_UNSET) {
+ return true;
+ }
+
+ return !!(aCookie->SchemeMap() & aSchemeType);
+}
+
+} // namespace
+
+// static
+bool CookieCommons::MaybeCompareSchemeWithLogging(
+ nsIConsoleReportCollector* aCRC, nsIURI* aHostURI, Cookie* aCookie,
+ nsICookie::schemeType aSchemeType) {
+ MOZ_ASSERT(aCookie);
+ MOZ_ASSERT(aHostURI);
+
+ if (MaybeCompareSchemeInternal(aCookie, aSchemeType)) {
+ return true;
+ }
+
+ nsAutoCString uri;
+ nsresult rv = aHostURI->GetSpec(uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return !StaticPrefs::network_cookie_sameSite_schemeful();
+ }
+
+ if (!StaticPrefs::network_cookie_sameSite_schemeful()) {
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_SCHEMEFUL_CATEGORY,
+ "CookieSchemefulRejectForBeta"_ns,
+ AutoTArray<nsString, 2>{NS_ConvertUTF8toUTF16(aCookie->Name()),
+ NS_ConvertUTF8toUTF16(uri)});
+ return true;
+ }
+
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_SCHEMEFUL_CATEGORY,
+ "CookieSchemefulReject"_ns,
+ AutoTArray<nsString, 2>{NS_ConvertUTF8toUTF16(aCookie->Name()),
+ NS_ConvertUTF8toUTF16(uri)});
+ return false;
+}
+
+// static
+bool CookieCommons::MaybeCompareScheme(Cookie* aCookie,
+ nsICookie::schemeType aSchemeType) {
+ MOZ_ASSERT(aCookie);
+
+ if (!StaticPrefs::network_cookie_sameSite_schemeful()) {
+ return true;
+ }
+
+ return MaybeCompareSchemeInternal(aCookie, aSchemeType);
+}
+
+// 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..dcb8ecb6cd
--- /dev/null
+++ b/netwerk/cookie/CookieCommons.h
@@ -0,0 +1,143 @@
+/* -*- 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 "prtime.h"
+#include "nsString.h"
+#include "nsICookie.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 CheckHttpValue(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);
+
+ static bool MaybeCompareSchemeWithLogging(nsIConsoleReportCollector* aCRC,
+ nsIURI* aHostURI, Cookie* aCookie,
+ nsICookie::schemeType aSchemeType);
+
+ static bool MaybeCompareScheme(Cookie* aCookie,
+ nsICookie::schemeType aSchemeType);
+
+ 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
+ // origin navigation.
+ static bool IsSameSiteForeign(nsIChannel* aChannel, nsIURI* aHostURI);
+};
+
+} // 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..9da16f7dbc
--- /dev/null
+++ b/netwerk/cookie/CookieJarSettings.cpp
@@ -0,0 +1,619 @@
+/* -*- 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/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 "nsGlobalWindowInner.h"
+#include "nsIPrincipal.h"
+#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
+# include "nsIProtocolHandler.h"
+#endif
+#include "nsIClassInfoImpl.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() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sBlockinAll) {
+ return do_AddRef(sBlockinAll);
+ }
+
+ sBlockinAll =
+ new CookieJarSettings(nsICookieService::BEHAVIOR_REJECT,
+ OriginAttributes::IsFirstPartyEnabled(), eFixed);
+ ClearOnShutdown(&sBlockinAll);
+
+ return do_AddRef(sBlockinAll);
+}
+
+// static
+already_AddRefed<nsICookieJarSettings> CookieJarSettings::Create() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<CookieJarSettings> cookieJarSettings = new CookieJarSettings(
+ nsICookieManager::GetCookieBehavior(),
+ OriginAttributes::IsFirstPartyEnabled(), eProgressive);
+ return cookieJarSettings.forget();
+}
+
+// static
+already_AddRefed<nsICookieJarSettings> CookieJarSettings::Create(
+ uint32_t aCookieBehavior, const nsAString& aPartitionKey,
+ bool aIsFirstPartyIsolated, bool aIsOnContentBlockingAllowList) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<CookieJarSettings> cookieJarSettings = new CookieJarSettings(
+ aCookieBehavior, aIsFirstPartyIsolated, eProgressive);
+ cookieJarSettings->mPartitionKey = aPartitionKey;
+ cookieJarSettings->mIsOnContentBlockingAllowList =
+ aIsOnContentBlockingAllowList;
+
+ return cookieJarSettings.forget();
+}
+
+CookieJarSettings::CookieJarSettings(uint32_t aCookieBehavior,
+ bool aIsFirstPartyIsolated, State aState)
+ : mCookieBehavior(aCookieBehavior),
+ mIsFirstPartyIsolated(aIsFirstPartyIsolated),
+ mIsOnContentBlockingAllowList(false),
+ mState(aState),
+ mToBeMerged(false) {
+ 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(TaskCategory::Other, r.forget());
+ }
+}
+
+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::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::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::CookiePermission(nsIPrincipal* aPrincipal,
+ uint32_t* aCookiePermission) {
+ MOZ_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()) {
+ nsCOMPtr<nsIPrincipal> principal =
+ Permission::ClonePrincipalForPermission(aPrincipal);
+ if (NS_WARN_IF(!principal)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ for (const RefPtr<nsIPermission>& permission : mCookiePermissions) {
+ bool match = false;
+ rv = permission->MatchesPrincipalForPermission(principal, 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_ASSERT(NS_IsMainThread());
+
+ aData.isFixed() = mState == eFixed;
+ aData.cookieBehavior() = mCookieBehavior;
+ aData.isFirstPartyIsolated() = mIsFirstPartyIsolated;
+ aData.isOnContentBlockingAllowList() = mIsOnContentBlockingAllowList;
+ aData.partitionKey() = mPartitionKey;
+
+ 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_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.isFixed() ? eFixed : eProgressive);
+
+ cookieJarSettings->mIsOnContentBlockingAllowList =
+ aData.isOnContentBlockingAllowList();
+ cookieJarSettings->mCookiePermissions = std::move(list);
+ cookieJarSettings->mPartitionKey = aData.partitionKey();
+
+ cookieJarSettings.forget(aCookieJarSettings);
+}
+
+void CookieJarSettings::Merge(const CookieJarSettingsArgs& aData) {
+ MOZ_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);
+
+ 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);
+ 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 ||
+ IsRejectThirdPartyWithExceptions(aCookieBehavior);
+}
+
+// static
+bool CookieJarSettings::IsRejectThirdPartyWithExceptions(
+ uint32_t aCookieBehavior) {
+ return aCookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN &&
+ StaticPrefs::network_cookie_rejectForeignWithExceptions_enabled();
+}
+
+NS_IMETHODIMP
+CookieJarSettings::Read(nsIObjectInputStream* aStream) {
+ 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;
+ }
+
+ 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) {
+ 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(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..34a8e3e796
--- /dev/null
+++ b/netwerk/cookie/CookieJarSettings.h
@@ -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/. */
+
+#ifndef mozilla_net_CookieJarSettings_h
+#define mozilla_net_CookieJarSettings_h
+
+#include "nsICookieJarSettings.h"
+#include "nsDataHashtable.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();
+
+ static already_AddRefed<nsICookieJarSettings> Create();
+
+ static already_AddRefed<nsICookieJarSettings> Create(
+ uint32_t aCookieBehavior, const nsAString& aPartitionKey,
+ bool aIsFirstPartyIsolated, bool aIsOnContentBlockingAllowList);
+
+ 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);
+ const nsAString& GetPartitionKey() { return mPartitionKey; };
+
+ // 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);
+
+ // This static method returns true if aCookieBehavior is
+ // BEHAVIOR_REJECT_FOREIGN and
+ // network.cookie.rejectForeignWithExceptions.enabled pref is set to true.
+ static bool IsRejectThirdPartyWithExceptions(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,
+ State aState);
+ ~CookieJarSettings();
+
+ uint32_t mCookieBehavior;
+ bool mIsFirstPartyIsolated;
+ CookiePermissionList mCookiePermissions;
+ bool mIsOnContentBlockingAllowList;
+ nsString mPartitionKey;
+
+ State mState;
+
+ bool mToBeMerged;
+};
+
+} // 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..5724a07083
--- /dev/null
+++ b/netwerk/cookie/CookieLogging.cpp
@@ -0,0 +1,184 @@
+/* -*- 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"
+
+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/CookiePersistentStorage.cpp b/netwerk/cookie/CookiePersistentStorage.cpp
new file mode 100644
index 0000000000..acd898837a
--- /dev/null
+++ b/netwerk/cookie/CookiePersistentStorage.cpp
@@ -0,0 +1,2017 @@
+/* -*- 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/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 "nsICookieService.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsILineInputStream.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 = 12;
+
+// 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;
+
+#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));
+
+ // 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 ? true : false);
+ 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();
+
+ return storage.forget();
+}
+
+CookiePersistentStorage::CookiePersistentStorage()
+ : mMonitor("CookiePersistentStorage"),
+ mInitialized(false),
+ mCorruptFlag(OK) {}
+
+void CookiePersistentStorage::NotifyChangedInternal(nsISupports* aSubject,
+ const char16_t* aData,
+ bool aOldCookieIsSession) {
+ // Notify for topic "session-cookie-changed" to update the copy of session
+ // cookies in session restore component.
+
+ // Filter out notifications for individual non-session cookies.
+ if (u"changed"_ns.Equals(aData) || u"deleted"_ns.Equals(aData) ||
+ u"added"_ns.Equals(aData)) {
+ nsCOMPtr<nsICookie> xpcCookie = do_QueryInterface(aSubject);
+ MOZ_ASSERT(xpcCookie);
+ auto cookie = static_cast<Cookie*>(xpcCookie.get());
+ if (!cookie->IsSession() && !aOldCookieIsSession) {
+ return;
+ }
+ }
+
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(aSubject, "session-cookie-changed", aData);
+ }
+}
+
+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);
+
+ 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);
+
+ CookieStorage::RemoveCookiesFromExactHost(aHost, aBaseDomain, aPattern);
+
+ DebugOnly<nsresult> rv = transaction.Commit();
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+void CookiePersistentStorage::RemoveCookieFromDB(const CookieListIter& aIter) {
+ // if it's a non-session cookie, remove it from the db
+ if (aIter.Cookie()->IsSession() || !mDBConn) {
+ return;
+ }
+
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+ mStmtDelete->NewBindingParamsArray(getter_AddRefs(paramsArray));
+
+ PrepareCookieRemoval(aIter, 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 CookieListIter& aIter, mozIStorageBindingParamsArray* aParamsArray) {
+ // if it's a non-session cookie, remove it from the db
+ if (aIter.Cookie()->IsSession() || !mDBConn) {
+ return;
+ }
+
+ nsCOMPtr<mozIStorageBindingParams> params;
+ aParamsArray->NewBindingParams(getter_AddRefs(params));
+
+ DebugOnly<nsresult> rv =
+ params->BindUTF8StringByName("name"_ns, aIter.Cookie()->Name());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindUTF8StringByName("host"_ns, aIter.Cookie()->Host());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = params->BindUTF8StringByName("path"_ns, aIter.Cookie()->Path());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsAutoCString suffix;
+ aIter.Cookie()->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,
+ 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);
+
+ 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 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 "
+ "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 "
+ "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);
+
+ // Create a new constCookie and assign the data.
+ return MakeUnique<CookieStruct>(
+ name, value, host, path, expiry, lastAccessed, creationTime, isHttpOnly,
+ false, isSecure, sameSite, rawSameSite,
+ static_cast<nsICookie::schemeType>(schemeMap));
+}
+
+void CookiePersistentStorage::EnsureReadComplete() {
+ 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;
+ }
+
+ for (uint32_t i = 0; i < mReadArray.Length(); ++i) {
+ CookieDomainTuple& tuple = mReadArray[i];
+ MOZ_ASSERT(!tuple.cookie->isSession());
+
+ RefPtr<Cookie> cookie =
+ Cookie::Create(*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();
+
+ 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,
+ 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 "
+ ") VALUES ("
+ ":originAttributes, "
+ ":name, "
+ ":value, "
+ ":host, "
+ ":path, "
+ ":expiry, "
+ ":lastAccessed, "
+ ":creationTime, "
+ ":isSecure, "
+ ":isHttpOnly, "
+ ":sameSite, "
+ ":rawSameSite, "
+ ":schemeMap "
+ ")"),
+ 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, "
+ "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);
+
+ 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, paramsArray);
+ self->RemoveCookieFromListInternal(aIter);
+ },
+ [paramsArray, self]() {
+ if (paramsArray) {
+ self->DeleteFromDB(paramsArray);
+ }
+ });
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cookie/CookiePersistentStorage.h b/netwerk/cookie/CookiePersistentStorage.h
new file mode 100644
index 0000000000..40c969f2a7
--- /dev/null
+++ b/netwerk/cookie/CookiePersistentStorage.h
@@ -0,0 +1,158 @@
+/* -*- 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 EnsureReadComplete();
+
+ void CleanupCachedStatements();
+ void CleanupDBConnection();
+
+ void Activate();
+
+ void RebuildCorruptDB();
+ void HandleDBClosed();
+
+ nsresult RunInTransaction(nsICookieTransactionCallback* aCallback);
+
+ // 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(nsISupports* aSubject, const char16_t* aData,
+ bool aOldCOokieIsSession) override;
+
+ void RemoveAllInternal() override;
+
+ void RemoveCookieFromDB(const CookieListIter& aIter) 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 CookieListIter& aIter,
+ 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 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;
+
+ 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..5aaa60c241
--- /dev/null
+++ b/netwerk/cookie/CookiePrivateStorage.h
@@ -0,0 +1,47 @@
+/* -*- 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"
+
+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{};
+
+ protected:
+ const char* NotificationTopic() const override {
+ return "private-cookie-changed";
+ }
+
+ void NotifyChangedInternal(nsISupports* aSubject, const char16_t* aData,
+ bool aOldCookieIsSession) override {}
+
+ void RemoveAllInternal() override {}
+
+ void RemoveCookieFromDB(const CookieListIter& aIter) 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 {}
+};
+
+} // 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..33543891d0
--- /dev/null
+++ b/netwerk/cookie/CookieService.cpp
@@ -0,0 +1,2374 @@
+/* -*- 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/ClearOnShutdown.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/dom/Promise.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 "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"
+
+using namespace mozilla::dom;
+
+// static
+uint32_t nsICookieManager::GetCookieBehavior() {
+ bool isFirstPartyIsolated = OriginAttributes::IsFirstPartyEnabled();
+ uint32_t cookieBehavior =
+ mozilla::StaticPrefs::network_cookie_cookieBehavior();
+
+ if (isFirstPartyIsolated &&
+ cookieBehavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
+ cookieBehavior = nsICookieService::BEHAVIOR_REJECT_TRACKER;
+ }
+ return cookieBehavior;
+}
+
+namespace mozilla {
+namespace net {
+
+/******************************************************************************
+ * CookieService impl:
+ * useful types & constants
+ ******************************************************************************/
+
+static StaticRefPtr<CookieService> gCookieService;
+
+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 aLaxByDefault) {
+ int32_t sameSiteAttr = 0;
+ aCookie->GetSameSite(&sameSiteAttr);
+
+ // it if's a cross origin request and the cookie is same site only (strict)
+ // don't send it
+ if (sameSiteAttr == nsICookie::SAMESITE_STRICT) {
+ return false;
+ }
+
+ 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 (StaticPrefs::network_cookie_sameSite_laxPlusPOST_timeout() > 0 &&
+ aLaxByDefault && sameSiteAttr == nsICookie::SAMESITE_LAX &&
+ aCookie->RawSameSite() == nsICookie::SAMESITE_NONE &&
+ currentTimeInUsec - aCookie->CreationTime() <=
+ (StaticPrefs::network_cookie_sameSite_laxPlusPOST_timeout() *
+ PR_USEC_PER_SEC) &&
+ !NS_IsSafeMethodNav(aChannel)) {
+ return true;
+ }
+
+ // if it's a cross origin request, the cookie is same site lax, but it's not a
+ // top-level navigation, don't send it
+ return sameSiteAttr != nsICookie::SAMESITE_LAX || 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.
+ 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();
+
+ 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.
+ mPersistentStorage = CookiePersistentStorage::Create();
+ mPrivateStorage = CookiePrivateStorage::Create();
+
+ mPersistentStorage->Activate();
+}
+
+void CookieService::CloseCookieStorages() {
+ // return if we already closed
+ if (!mPersistentStorage) {
+ return;
+ }
+
+ // Let's nullify both storages before calling Close().
+ RefPtr<CookiePrivateStorage> privateStorage;
+ privateStorage.swap(mPrivateStorage);
+
+ RefPtr<CookiePersistentStorage> 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(uint32_t* aCookieBehavior) {
+ NS_ENSURE_ARG_POINTER(aCookieBehavior);
+ *aCookieBehavior = nsICookieManager::GetCookieBehavior();
+ 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->EffectiveStoragePrincipal();
+
+ if (!CookieCommons::IsSchemeSupported(principal)) {
+ return NS_OK;
+ }
+
+ nsICookie::schemeType schemeType =
+ CookieCommons::PrincipalToSchemeType(principal);
+
+ 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 potentiallyTurstworthy = 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) {
+ thirdParty = nsContentUtils::IsThirdPartyWindowOrChannel(innerWindow,
+ nullptr, nullptr);
+ }
+
+ 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)) {
+ continue;
+ }
+
+ // if the cookie is secure and the host scheme isn't, we can't send it
+ if (cookie->IsSecure() && !potentiallyTurstworthy) {
+ continue;
+ }
+
+ if (!CookieCommons::MaybeCompareScheme(cookie, schemeType)) {
+ 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 isSameSiteForeign = CookieCommons::IsSameSiteForeign(aChannel, aHostURI);
+
+ 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, true, 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) {
+ thirdParty = nsContentUtils::IsThirdPartyWindowOrChannel(innerWindow,
+ nullptr, nullptr);
+ }
+
+ if (thirdParty &&
+ !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(cookie)) {
+ 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);
+ 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);
+ }
+
+ 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, 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));
+
+ // add the cookie to the list. AddCookie() takes care of logging.
+ storage->AddCookie(crc, baseDomain, attrs, cookie, currentTimeInUsec,
+ aHostURI, aCookieHeader, true);
+ }
+
+ 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->EnsureReadComplete();
+ return mPersistentStorage->RunInTransaction(aCallback);
+}
+
+/******************************************************************************
+ * nsICookieManager impl:
+ * nsICookieManager
+ ******************************************************************************/
+
+NS_IMETHODIMP
+CookieService::RemoveAll() {
+ if (!IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mPersistentStorage->EnsureReadComplete();
+ mPersistentStorage->RemoveAll();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieService::GetCookies(nsTArray<RefPtr<nsICookie>>& aCookies) {
+ if (!IsInitialized()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mPersistentStorage->EnsureReadComplete();
+
+ // 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->EnsureReadComplete();
+
+ // We expose only non-private cookies.
+ mPersistentStorage->GetCookies(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::HandleValue 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, 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);
+ 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::HandleValue 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 aHttpBound,
+ 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);
+
+ nsICookie::schemeType schemeType = CookieCommons::URIToSchemeType(aHostURI);
+
+ // 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 potentiallyTurstworthy =
+ 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 can't send it
+ if (cookie->IsSecure() && !potentiallyTurstworthy) {
+ continue;
+ }
+
+ // The scheme doesn't match.
+ if (!CookieCommons::MaybeCompareSchemeWithLogging(crc, aHostURI, cookie,
+ schemeType)) {
+ continue;
+ }
+
+ if (aHttpBound && aIsSameSiteForeign &&
+ !ProcessSameSiteCookieForForeignRequest(
+ aChannel, cookie, aIsSafeTopLevelNav, laxByDefault)) {
+ 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;
+ }
+
+ // 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());
+}
+
+// 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, 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 potentiallyTurstworthy =
+ 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;
+ }
+
+ 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;
+ }
+
+ // magic prefix checks. MUST be run after CheckDomain() and CheckPath()
+ if (!CheckPrefixes(aCookieData, potentiallyTurstworthy)) {
+ 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 (aFromHttp && !CookieCommons::CheckHttpValue(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() && !potentiallyTurstworthy) {
+ 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.
+ if ((aCookieData.sameSite() != 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;
+ }
+
+ 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)) {
+ continue;
+ }
+ ++lastSpace;
+ }
+ aTokenString.Rebind(start, lastSpace);
+
+ aEqualsFound = (*aIter == '=');
+ if (aEqualsFound) {
+ // find <value>
+ while (++aIter != aEndIter && iswhitespace(*aIter)) {
+ continue;
+ }
+
+ 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)) {
+ continue;
+ }
+
+ 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 SetSameSiteDefaultAttribute(CookieStruct& aCookieData,
+ bool laxByDefault) {
+ aCookieData.rawSameSite() = nsICookie::SAMESITE_NONE;
+ if (laxByDefault) {
+ aCookieData.sameSite() = nsICookie::SAMESITE_LAX;
+ } else {
+ aCookieData.sameSite() = nsICookie::SAMESITE_NONE;
+ }
+}
+
+// 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";
+
+ nsACString::const_char_iterator cookieStart;
+ aCookieHeader.BeginReading(cookieStart);
+
+ nsACString::const_char_iterator cookieEnd;
+ aCookieHeader.EndReading(cookieEnd);
+
+ aCookieData.isSecure() = false;
+ aCookieData.isHttpOnly() = false;
+
+ bool laxByDefault =
+ StaticPrefs::network_cookie_sameSite_laxByDefault() &&
+ !nsContentUtils::IsURIInPrefList(
+ aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts");
+ SetSameSiteDefaultAttribute(aCookieData, laxByDefault);
+
+ 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;
+ }
+
+ bool sameSiteSet = false;
+
+ // 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 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)) {
+ aCookieData.sameSite() = nsICookie::SAMESITE_LAX;
+ aCookieData.rawSameSite() = nsICookie::SAMESITE_LAX;
+ sameSiteSet = true;
+ } else if (tokenValue.LowerCaseEqualsLiteral(kSameSiteStrict)) {
+ aCookieData.sameSite() = nsICookie::SAMESITE_STRICT;
+ aCookieData.rawSameSite() = nsICookie::SAMESITE_STRICT;
+ sameSiteSet = true;
+ } else if (tokenValue.LowerCaseEqualsLiteral(kSameSiteNone)) {
+ aCookieData.sameSite() = nsICookie::SAMESITE_NONE;
+ aCookieData.rawSameSite() = nsICookie::SAMESITE_NONE;
+ sameSiteSet = true;
+ } else {
+ // Reset to defaults if unknown token value (see Bug 1682450)
+ SetSameSiteDefaultAttribute(aCookieData, laxByDefault);
+ sameSiteSet = false;
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::infoFlag, CONSOLE_SAMESITE_CATEGORY,
+ "CookieSameSiteValueInvalid2"_ns,
+ AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
+ }
+ }
+ }
+
+ Telemetry::Accumulate(Telemetry::COOKIE_SAMESITE_SET_VS_UNSET,
+ sameSiteSet ? 1 : 0);
+
+ // re-assign aCookieHeader, in case we need to process another cookie
+ aCookieHeader.Assign(Substring(cookieStart, cookieEnd));
+
+ // If same-site is set to 'none' but this is not a secure context, let's abort
+ // the parsing.
+ if (!aCookieData.isSecure() &&
+ aCookieData.sameSite() == nsICookie::SAMESITE_NONE) {
+ if (laxByDefault &&
+ StaticPrefs::network_cookie_sameSite_noneRequiresSecure()) {
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::infoFlag, CONSOLE_SAMESITE_CATEGORY,
+ "CookieRejectedNonRequiresSecure2"_ns,
+ AutoTArray<nsString, 1>{NS_ConvertUTF8toUTF16(aCookieData.name())});
+ return newCookie;
+ }
+
+ // if SameSite=Lax by default is disabled, we want to warn the user.
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag, CONSOLE_SAMESITE_CATEGORY,
+ "CookieRejectedNonRequiresSecureForBeta2"_ns,
+ AutoTArray<nsString, 2>{NS_ConvertUTF8toUTF16(aCookieData.name()),
+ SAMESITE_MDN_URL});
+ }
+
+ if (aCookieData.rawSameSite() == nsICookie::SAMESITE_NONE &&
+ aCookieData.sameSite() == nsICookie::SAMESITE_LAX) {
+ 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::ValidateRawSame(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() ? GET_COOKIE : SET_COOKIE,
+ 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() ? GET_COOKIE : SET_COOKIE,
+ 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() ? GET_COOKIE : SET_COOKIE,
+ 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()) {
+ bool rejectThirdPartyWithExceptions =
+ CookieJarSettings::IsRejectThirdPartyWithExceptions(
+ aCookieJarSettings->GetCookieBehavior());
+
+ uint32_t rejectReason =
+ rejectThirdPartyWithExceptions
+ ? nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
+ : 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() ? GET_COOKIE : SET_COOKIE,
+ aHostURI, aCookieHeader,
+ "cookies are disabled in trackers");
+ if (aIsThirdPartySocialTrackingResource) {
+ *aRejectedReason =
+ nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER;
+ } else if (rejectThirdPartyWithExceptions) {
+ *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN;
+ } 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() ? GET_COOKIE : SET_COOKIE,
+ 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() ? GET_COOKIE : SET_COOKIE,
+ 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() ? GET_COOKIE : SET_COOKIE,
+ 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;
+}
+
+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 0
+ } else {
+ /**
+ * The following test is part of the RFC2109 spec. Loosely speaking, it says that a site
+ * cannot set a cookie for a path that it is not on. See bug 155083. However this patch
+ * broke several sites -- nordea (bug 155768) and citibank (bug 156725). So this test has
+ * been disabled, unless we can evangelize these sites.
+ */
+ // get path from aHostURI
+ nsAutoCString pathFromURI;
+ if (NS_FAILED(aHostURI->GetPathQueryRef(pathFromURI)) ||
+ !StringBeginsWith(pathFromURI, aCookieData.path())) {
+ return false;
+ }
+#endif
+ }
+
+ 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;
+ }
+
+ if (aCookieData.path().Contains('\t')) {
+ return false;
+ }
+
+ return true;
+}
+
+// CheckPrefixes
+//
+// Reject cookies whose name starts with the magic prefixes from
+// https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00
+// 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::HandleValue 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) {
+ NS_ENSURE_ARG_POINTER(aOriginAttributes);
+ NS_ENSURE_ARG_POINTER(aFoundCookie);
+
+ 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);
+ *aFoundCookie = storage->FindCookie(baseDomain, *aOriginAttributes, aHost,
+ aName, aPath, iter);
+ 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->EnsureReadComplete();
+
+ *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::HandleValue 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->EnsureReadComplete();
+
+ 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->EnsureReadComplete();
+
+ // 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->EnsureReadComplete();
+ return mPersistentStorage;
+}
+
+CookieStorage* CookieService::PickStorage(
+ const OriginAttributesPattern& aAttrs) {
+ MOZ_ASSERT(IsInitialized());
+
+ if (aAttrs.mPrivateBrowsingId.WasPassed() &&
+ aAttrs.mPrivateBrowsingId.Value() > 0) {
+ return mPrivateStorage;
+ }
+
+ mPersistentStorage->EnsureReadComplete();
+ return mPersistentStorage;
+}
+
+bool CookieService::SetCookiesFromIPC(const nsACString& aBaseDomain,
+ const OriginAttributes& aAttrs,
+ nsIURI* aHostURI, bool aFromHttp,
+ const nsTArray<CookieStruct>& aCookies) {
+ 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;
+ }
+
+ if (!CookieCommons::CheckName(cookieData)) {
+ return false;
+ }
+
+ if (aFromHttp && !CookieCommons::CheckHttpValue(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);
+ }
+
+ return true;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/cookie/CookieService.h b/netwerk/cookie/CookieService.h
new file mode 100644
index 0000000000..fbb5ff04bf
--- /dev/null
+++ b/netwerk/cookie/CookieService.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_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,
+ 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 aHttpBound,
+ 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);
+
+ protected:
+ virtual ~CookieService();
+
+ bool IsInitialized() const;
+
+ void InitCookieStorages();
+ void CloseCookieStorages();
+
+ void EnsureReadComplete(bool aInitDBConn);
+ 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 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<CookiePersistentStorage> mPersistentStorage;
+ RefPtr<CookiePrivateStorage> 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..a20d1d2b12
--- /dev/null
+++ b/netwerk/cookie/CookieServiceChild.cpp
@@ -0,0 +1,634 @@
+/* -*- 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 "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 "nsIChannel.h"
+#include "nsIClassifiedChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIURI.h"
+#include "nsIPrefBranch.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "ThirdPartyUtil.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+// Pref string constants
+static const char kCookieMoveIntervalSecs[] =
+ "network.cookie.move.interval_sec";
+
+static StaticRefPtr<CookieServiceChild> gCookieChildService;
+static uint32_t gMoveCookiesIntervalSeconds = 10;
+
+already_AddRefed<CookieServiceChild> CookieServiceChild::GetSingleton() {
+ if (!gCookieChildService) {
+ gCookieChildService = new CookieServiceChild();
+ ClearOnShutdown(&gCookieChildService);
+ }
+
+ return do_AddRef(gCookieChildService);
+}
+
+NS_IMPL_ISUPPORTS(CookieServiceChild, nsICookieService, nsIObserver,
+ nsITimerCallback, nsISupportsWeakReference)
+
+CookieServiceChild::CookieServiceChild() {
+ NS_ASSERTION(IsNeckoChild(), "not a child process");
+
+ auto* cc = static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return;
+ }
+
+ // This corresponds to Release() in DeallocPCookieService.
+ NS_ADDREF_THIS();
+
+ NeckoChild::InitNeckoChild();
+
+ // Create a child PCookieService actor.
+ 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");
+
+ // Init our prefs and observer.
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ NS_WARNING_ASSERTION(prefBranch, "no prefservice");
+ if (prefBranch) {
+ prefBranch->AddObserver(kCookieMoveIntervalSecs, this, true);
+ PrefChanged(prefBranch);
+ }
+
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ }
+}
+
+void CookieServiceChild::MoveCookies() {
+ TimeStamp start = TimeStamp::Now();
+ for (auto iter = mCookiesMap.Iter(); !iter.Done(); iter.Next()) {
+ CookiesList* cookiesList = iter.UserData();
+ CookiesList newCookiesList;
+ for (uint32_t i = 0; i < cookiesList->Length(); ++i) {
+ Cookie* cookie = cookiesList->ElementAt(i);
+ RefPtr<Cookie> newCookie = cookie->Clone();
+ newCookiesList.AppendElement(newCookie);
+ }
+ *cookiesList = std::move(newCookiesList);
+ }
+
+ Telemetry::AccumulateTimeDelta(Telemetry::COOKIE_TIME_MOVING_MS, start);
+}
+
+NS_IMETHODIMP
+CookieServiceChild::Notify(nsITimer* aTimer) {
+ if (aTimer == mCookieTimer) {
+ MoveCookies();
+ } else {
+ MOZ_CRASH("Unknown timer");
+ }
+ return NS_OK;
+}
+
+CookieServiceChild::~CookieServiceChild() { gCookieChildService = nullptr; }
+
+void CookieServiceChild::TrackCookieLoad(nsIChannel* aChannel) {
+ if (!CanSend()) {
+ return;
+ }
+
+ 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 isSameSiteForeign = CookieCommons::IsSameSiteForeign(aChannel, uri);
+ SendPrepareCookieList(
+ uri, result.contains(ThirdPartyAnalysis::IsForeign),
+ result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
+ result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
+ result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
+ rejectedReason, isSafeTopLevelNav, isSameSiteForeign, attrs);
+}
+
+IPCResult CookieServiceChild::RecvRemoveAll() {
+ mCookiesMap.Clear();
+ return IPC_OK();
+}
+
+IPCResult CookieServiceChild::RecvRemoveCookie(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 IPC_OK();
+ }
+
+ 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())) {
+ cookiesList->RemoveElementAt(i);
+ break;
+ }
+ }
+
+ return IPC_OK();
+}
+
+IPCResult CookieServiceChild::RecvAddCookie(const CookieStruct& aCookie,
+ const OriginAttributes& aAttrs) {
+ RefPtr<Cookie> cookie = Cookie::Create(aCookie, aAttrs);
+ RecordDocumentCookie(cookie, aAttrs);
+ 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);
+ RecvRemoveCookie(cookieStruct, aAttrsList.ElementAt(i));
+ }
+ 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);
+ }
+
+ return IPC_OK();
+}
+
+void CookieServiceChild::PrefChanged(nsIPrefBranch* aPrefBranch) {
+ int32_t val;
+ if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookieMoveIntervalSecs, &val))) {
+ gMoveCookiesIntervalSeconds = clamped<uint32_t>(val, 0, 3600);
+ if (gMoveCookiesIntervalSeconds && !mCookieTimer) {
+ NS_NewTimerWithCallback(getter_AddRefs(mCookieTimer), this,
+ gMoveCookiesIntervalSeconds * 1000,
+ nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY);
+ }
+ if (!gMoveCookiesIntervalSeconds && mCookieTimer) {
+ mCookieTimer->Cancel();
+ mCookieTimer = nullptr;
+ }
+ if (mCookieTimer) {
+ mCookieTimer->SetDelay(gMoveCookiesIntervalSeconds * 1000);
+ }
+ }
+}
+
+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.LookupOrAdd(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->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::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* /*aData*/) {
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ if (mCookieTimer) {
+ mCookieTimer->Cancel();
+ mCookieTimer = nullptr;
+ }
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+ } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
+ if (prefBranch) {
+ PrefChanged(prefBranch);
+ }
+ } else {
+ MOZ_ASSERT(false, "unexpected topic!");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieServiceChild::GetCookieStringFromDocument(Document* aDocument,
+ nsACString& aCookieString) {
+ NS_ENSURE_ARG(aDocument);
+
+ aCookieString.Truncate();
+
+ nsCOMPtr<nsIPrincipal> principal = aDocument->EffectiveStoragePrincipal();
+
+ if (!CookieCommons::IsSchemeSupported(principal)) {
+ return NS_OK;
+ }
+
+ nsICookie::schemeType schemeType =
+ CookieCommons::PrincipalToSchemeType(principal);
+
+ 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;
+ principal->GetAsciiHost(hostFromURI);
+
+ 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) {
+ thirdParty = nsContentUtils::IsThirdPartyWindowOrChannel(innerWindow,
+ nullptr, nullptr);
+ }
+
+ 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)) {
+ continue;
+ }
+
+ // if the cookie is secure and the host scheme isn't, we can't send it
+ if (cookie->IsSecure() && !isPotentiallyTrustworthy) {
+ continue;
+ }
+
+ if (!CookieCommons::MaybeCompareScheme(cookie, schemeType)) {
+ 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(
+ 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) {
+ thirdParty = nsContentUtils::IsThirdPartyWindowOrChannel(innerWindow,
+ nullptr, nullptr);
+ }
+
+ if (thirdParty &&
+ !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(cookie)) {
+ 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
+ // 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.
+ 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()) &&
+ existingCookie->IsHttpOnly()) {
+ // Can't overwrite an httponly cookie from a script context.
+ return NS_OK;
+ }
+ }
+ }
+
+ RecordDocumentCookie(cookie, attrs);
+
+ if (CanSend()) {
+ nsTArray<CookieStruct> cookiesToSend;
+ cookiesToSend.AppendElement(cookie->ToIPC());
+
+ // Asynchronously call the parent.
+ 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 moreCookies;
+ do {
+ CookieStruct cookieData;
+ bool canSetCookie = false;
+ moreCookies = CookieService::CanSetCookie(
+ aHostURI, baseDomain, cookieData, requireHostMatch, cookieStatus,
+ cookieString, true, isForeignAndNotAddon, 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");
+ 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()) {
+ 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..e0a49e8ebb
--- /dev/null
+++ b/netwerk/cookie/CookieServiceChild.h
@@ -0,0 +1,87 @@
+/* -*- 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 "nsIObserver.h"
+#include "nsIPrefBranch.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 nsIObserver,
+ public nsITimerCallback,
+ public nsSupportsWeakReference {
+ friend class PCookieServiceChild;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOOKIESERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+
+ typedef nsTArray<RefPtr<Cookie>> CookiesList;
+ typedef nsClassHashtable<CookieKey, CookiesList> CookiesMap;
+
+ CookieServiceChild();
+
+ static already_AddRefed<CookieServiceChild> GetSingleton();
+
+ void TrackCookieLoad(nsIChannel* aChannel);
+
+ private:
+ ~CookieServiceChild();
+ void MoveCookies();
+
+ void RecordDocumentCookie(Cookie* aCookie, const OriginAttributes& aAttrs);
+
+ uint32_t CountCookiesFromHashTable(const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttrs);
+
+ void PrefChanged(nsIPrefBranch* aPrefBranch);
+
+ 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);
+
+ CookiesMap mCookiesMap;
+ nsCOMPtr<nsITimer> mCookieTimer;
+ 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..d87c1c551e
--- /dev/null
+++ b/netwerk/cookie/CookieServiceParent.cpp
@@ -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/. */
+
+#include "CookieCommons.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 "nsNetCID.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");
+ 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);
+ auto cookie = static_cast<Cookie*>(xpcCookie.get());
+ attrs = cookie->OriginAttributesRef();
+ cookieStruct = cookie->ToIPC();
+ if (cookie->IsHttpOnly()) {
+ // Child only needs to exist if an HttpOnly cookie exists, not its value
+ cookieStruct.value() = "";
+ }
+ cookieStructList.AppendElement(cookieStruct);
+ attrsList.AppendElement(attrs);
+ }
+ Unused << SendRemoveBatchDeletedCookies(cookieStructList, attrsList);
+}
+
+void CookieServiceParent::RemoveAll() { Unused << SendRemoveAll(); }
+
+void CookieServiceParent::RemoveCookie(nsICookie* aCookie) {
+ auto cookie = static_cast<Cookie*>(aCookie);
+ const OriginAttributes& attrs = cookie->OriginAttributesRef();
+ CookieStruct cookieStruct = cookie->ToIPC();
+ if (cookie->IsHttpOnly()) {
+ cookieStruct.value() = "";
+ }
+ Unused << SendRemoveCookie(cookieStruct, attrs);
+}
+
+void CookieServiceParent::AddCookie(nsICookie* aCookie) {
+ auto cookie = static_cast<Cookie*>(aCookie);
+ const OriginAttributes& attrs = cookie->OriginAttributesRef();
+ CookieStruct cookieStruct = cookie->ToIPC();
+ if (cookie->IsHttpOnly()) {
+ cookieStruct.value() = "";
+ }
+ Unused << SendAddCookie(cookieStruct, attrs);
+}
+
+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 aIsSameSiteForeign = CookieCommons::IsSameSiteForeign(aChannel, uri);
+
+ StoragePrincipalHelper::PrepareEffectiveStoragePrincipalOriginAttributes(
+ aChannel, attrs);
+
+ // Send matching cookies to Child.
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil;
+ thirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID);
+
+ uint32_t rejectedReason = 0;
+ ThirdPartyAnalysisResult result = thirdPartyUtil->AnalyzeChannel(
+ aChannel, false, nullptr, nullptr, &rejectedReason);
+
+ 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, aIsSameSiteForeign, false, attrs,
+ foundCookieList);
+ nsTArray<CookieStruct> matchingCookiesList;
+ SerialializeCookieList(foundCookieList, matchingCookiesList);
+ Unused << SendTrackCookiesLoad(matchingCookiesList, attrs);
+}
+
+// static
+void CookieServiceParent::SerialializeCookieList(
+ const nsTArray<Cookie*>& aFoundCookieList,
+ nsTArray<CookieStruct>& aCookiesList) {
+ for (uint32_t i = 0; i < aFoundCookieList.Length(); i++) {
+ Cookie* cookie = aFoundCookieList.ElementAt(i);
+ CookieStruct* cookieStruct = aCookiesList.AppendElement();
+ *cookieStruct = cookie->ToIPC();
+ if (cookie->IsHttpOnly()) {
+ // Value only needs to exist if an HttpOnly cookie exists.
+ cookieStruct->value() = "";
+ }
+ }
+}
+
+IPCResult CookieServiceParent::RecvPrepareCookieList(
+ 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 OriginAttributes& aAttrs) {
+ // Send matching cookies to Child.
+ if (!aHost) {
+ return IPC_FAIL(this, "aHost must not be null");
+ }
+
+ 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, false, aAttrs,
+ foundCookieList);
+ nsTArray<CookieStruct> matchingCookiesList;
+ SerialializeCookieList(foundCookieList, matchingCookiesList);
+ Unused << SendTrackCookiesLoad(matchingCookiesList, aAttrs);
+ 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) {
+ 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);
+ 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..e8ac54671e
--- /dev/null
+++ b/netwerk/cookie/CookieServiceParent.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 mozilla_net_CookieServiceParent_h
+#define mozilla_net_CookieServiceParent_h
+
+#include "mozilla/net/PCookieServiceParent.h"
+
+class nsIArray;
+class nsICookie;
+namespace mozilla {
+class OriginAttributes;
+}
+
+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(nsICookie* aCookie);
+
+ void AddCookie(nsICookie* 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; }
+
+ 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 RecvPrepareCookieList(
+ 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 OriginAttributes& aAttrs);
+
+ static void SerialializeCookieList(const nsTArray<Cookie*>& aFoundCookieList,
+ nsTArray<CookieStruct>& aCookiesList);
+
+ RefPtr<CookieService> mCookieService;
+ bool mProcessingCookie;
+};
+
+} // 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..9c22301569
--- /dev/null
+++ b/netwerk/cookie/CookieStorage.cpp
@@ -0,0 +1,887 @@
+/* -*- 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 "CookieStorage.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "nsIMutableArray.h"
+#include "nsTPriorityQueue.h"
+#include "prprf.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:
+ CompareCookiesByAge mAgeComparator;
+ 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 mAgeComparator.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;
+}
+
+// ---------------------------------------------------------------------------
+// CookieStorage
+
+NS_IMPL_ISUPPORTS(CookieStorage, nsIObserver, nsISupportsWeakReference)
+
+CookieStorage::CookieStorage()
+ : mCookieCount(0),
+ mCookieOldestTime(INT64_MAX),
+ mMaxNumberOfCookies(kMaxNumberOfCookies),
+ mMaxCookiesPerHost(kMaxCookiesPerHost),
+ mCookieQuotaPerHost(kCookieQuotaPerHost),
+ mCookiePurgeAge(kCookiePurgeAge) {}
+
+CookieStorage::~CookieStorage() = default;
+
+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);
+ }
+}
+
+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 (auto iter = mHostTable.ConstIter(); !iter.Done(); iter.Next()) {
+ const CookieEntry::ArrayType& cookies = iter.Get()->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 (auto iter = mHostTable.ConstIter(); !iter.Done(); iter.Next()) {
+ const CookieEntry::ArrayType& cookies = iter.Get()->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 (auto iter = mHostTable.ConstIter(); !iter.Done(); iter.Next()) {
+ const CookieEntry::ArrayType& cookies = iter.Get()->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, u"deleted");
+ }
+}
+
+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, u"deleted");
+ }
+ }
+ }
+}
+
+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, u"deleted");
+ }
+ }
+ }
+}
+
+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, u"cleared");
+}
+
+// notify observers that the cookie list changed. there are five possible
+// values for aData:
+// "deleted" means a cookie was deleted. aSubject is the deleted cookie.
+// "added" means a cookie was added. aSubject is the added cookie.
+// "changed" means a cookie was altered. aSubject is the new cookie.
+// "cleared" means the entire cookie list was cleared. aSubject is null.
+// "batch-deleted" means a set of cookies was purged. aSubject is the list of
+// cookies.
+void CookieStorage::NotifyChanged(nsISupports* aSubject, const char16_t* aData,
+ bool aOldCookieIsSession) {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (!os) {
+ return;
+ }
+ // Notify for topic "private-cookie-changed" or "cookie-changed"
+ os->NotifyObservers(aSubject, NotificationTopic(), aData);
+
+ NotifyChangedInternal(aSubject, aData, 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) {
+ 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);
+ }
+ 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");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag,
+ CONSOLE_REJECTION_CATEGORY, "CookieRejectedExpired"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookie->Name()),
+ });
+ 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->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");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag,
+ CONSOLE_REJECTION_CATEGORY, "CookieRejectedExpired"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookie->Name()),
+ });
+ NotifyChanged(oldCookie, u"deleted", 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");
+ CookieLogging::LogMessageToConsole(
+ aCRC, aHostURI, nsIScriptError::warningFlag,
+ CONSOLE_REJECTION_CATEGORY, "CookieRejectedExpired"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(aCookie->Name()),
+ });
+ 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(getter_AddRefs(purgedList), evictedCookie);
+ MOZ_ASSERT((*it).entry);
+ }
+
+ } 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);
+ }
+ }
+ }
+
+ // 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, u"batch-deleted");
+ }
+
+ NotifyChanged(aCookie, foundCookie ? u"changed" : u"added",
+ 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(nsIArray** aPurgedList,
+ nsICookie* aCookie) {
+ if (!*aPurgedList) {
+ COOKIE_LOGSTRING(LogLevel::Debug, ("Creating new purge list"));
+ nsCOMPtr<nsIArray> purgedList = CreatePurgeList(aCookie);
+ purgedList.forget(aPurgedList);
+ 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);
+ 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);
+ }
+ }
+
+ 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..9db3d8a412
--- /dev/null
+++ b/netwerk/cookie/CookieStorage.h
@@ -0,0 +1,200 @@
+/* -*- 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 "nsIObserver.h"
+#include "nsTHashtable.h"
+#include "nsWeakReference.h"
+#include <functional>
+
+class nsIArray;
+class nsICookie;
+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
+ typedef nsTArray<RefPtr<Cookie>> ArrayType;
+ typedef ArrayType::index_type IndexType;
+
+ 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; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) 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, const char16_t* aData,
+ 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);
+
+ static void CreateOrUpdatePurgeList(nsIArray** aPurgedList,
+ nsICookie* aCookie);
+
+ virtual void StaleCookies(const nsTArray<Cookie*>& aCookieList,
+ int64_t aCurrentTimeInUsec) = 0;
+
+ virtual void Close() = 0;
+
+ protected:
+ CookieStorage();
+ virtual ~CookieStorage();
+
+ 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(nsISupports* aSubject,
+ const char16_t* aData,
+ 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 CookieListIter& aIter) = 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;
+
+ 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;
+
+ int64_t mCookieOldestTime;
+
+ uint16_t mMaxNumberOfCookies;
+ uint16_t mMaxCookiesPerHost;
+ uint16_t mCookieQuotaPerHost;
+ int64_t mCookiePurgeAge;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_CookieStorage_h
diff --git a/netwerk/cookie/CookieXPCShellUtils.jsm b/netwerk/cookie/CookieXPCShellUtils.jsm
new file mode 100644
index 0000000000..433151ed92
--- /dev/null
+++ b/netwerk/cookie/CookieXPCShellUtils.jsm
@@ -0,0 +1,60 @@
+/* -*- 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/. */
+"use strict";
+
+var EXPORTED_SYMBOLS = ["CookieXPCShellUtils"];
+
+const { ExtensionTestUtils } = ChromeUtils.import(
+ "resource://testing-common/ExtensionXPCShellUtils.jsm"
+);
+
+const { AddonTestUtils } = ChromeUtils.import(
+ "resource://testing-common/AddonTestUtils.jsm"
+);
+
+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(
+ null,
+ // 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..931299ef44
--- /dev/null
+++ b/netwerk/cookie/PCookieService.ipdl
@@ -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 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";
+using refcounted 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
+ */
+
+nested(upto inside_cpow) sync protocol PCookieService
+{
+ manager PNecko;
+
+parent:
+ nested(inside_cpow) async SetCookies(nsCString baseDomain,
+ OriginAttributes attrs,
+ nsIURI host,
+ bool fromHttp,
+ CookieStruct[] cookies);
+
+ async PrepareCookieList(nsIURI host,
+ bool isForeign,
+ bool isThirdPartyTrackingResource,
+ bool isThirdPartySocialTrackingResource,
+ bool firstPartyStorageAccessPermissionGranted,
+ uint32_t rejectedReason,
+ bool isSafeTopLevelNav,
+ bool isSameSiteForeign,
+ OriginAttributes attrs);
+
+ 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..b21d234b35
--- /dev/null
+++ b/netwerk/cookie/moz.build
@@ -0,0 +1,76 @@
+# -*- 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",
+ "nsICookiePermission.idl",
+ "nsICookieService.idl",
+]
+
+XPIDL_MODULE = "necko_cookie"
+
+
+EXPORTS.mozilla.net = [
+ "CookieJarSettings.h",
+ "CookieKey.h",
+ "CookiePersistentStorage.h",
+ "CookiePrivateStorage.h",
+ "CookieService.h",
+ "CookieServiceChild.h",
+ "CookieServiceParent.h",
+ "CookieStorage.h",
+]
+UNIFIED_SOURCES += [
+ "Cookie.cpp",
+ "CookieCommons.cpp",
+ "CookieJarSettings.cpp",
+ "CookieLogging.cpp",
+ "CookiePersistentStorage.cpp",
+ "CookiePrivateStorage.cpp",
+ "CookieService.cpp",
+ "CookieServiceChild.cpp",
+ "CookieServiceParent.cpp",
+ "CookieStorage.cpp",
+]
+XPCSHELL_TESTS_MANIFESTS += [
+ "test/unit/xpcshell.ini",
+]
+
+BROWSER_CHROME_MANIFESTS += [
+ "test/browser/browser.ini",
+]
+
+MOCHITEST_MANIFESTS += [
+ "test/mochitest/mochitest.ini",
+]
+
+IPDL_SOURCES = [
+ "PCookieService.ipdl",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/intl/uconv",
+ "/netwerk/base",
+ "/netwerk/protocol/http",
+]
+
+TESTING_JS_MODULES += [
+ "CookieXPCShellUtils.jsm",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/netwerk/cookie/nsICookie.idl b/netwerk/cookie/nsICookie.idl
new file mode 100644
index 0000000000..0b2e5e97bc
--- /dev/null
+++ b/netwerk/cookie/nsICookie.idl
@@ -0,0 +1,139 @@
+/* -*- 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.
+ */
+
+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;
+
+ /**
+ * 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;
+};
diff --git a/netwerk/cookie/nsICookieJarSettings.idl b/netwerk/cookie/nsICookieJarSettings.idl
new file mode 100644
index 0000000000..052c957557
--- /dev/null
+++ b/netwerk/cookie/nsICookieJarSettings.idl
@@ -0,0 +1,59 @@
+/* -*- 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;
+
+/**
+ * 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;
+
+ /**
+ * Whether our cookie behavior mandates rejecting third-party contexts.
+ */
+ [infallible] readonly attribute boolean rejectThirdPartyContexts;
+
+ [infallible] readonly attribute boolean limitForeignContexts;
+
+ /**
+ * 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;
+
+ /**
+ * 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);
+};
diff --git a/netwerk/cookie/nsICookieManager.idl b/netwerk/cookie/nsICookieManager.idl
new file mode 100644
index 0000000000..3d815846a5
--- /dev/null
+++ b/netwerk/cookie/nsICookieManager.idl
@@ -0,0 +1,254 @@
+/* -*- 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 "network.cookie.cookieBehavior".
+ */
+ readonly attribute uint32_t cookieBehavior;
+ %{C++
+ static uint32_t GetCookieBehavior();
+ %}
+
+ /**
+ * 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);
+
+ /**
+ * 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/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..52ed13f61c
--- /dev/null
+++ b/netwerk/cookie/nsICookieService.idl
@@ -0,0 +1,168 @@
+/* -*- 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 "batch-deleted" notification will likely be
+ * immediately followed by "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: see below.
+ * data : "deleted"
+ * a cookie was deleted. the subject is an nsICookie representing
+ * the deleted cookie.
+ * "added"
+ * a cookie was added. the subject is an nsICookie representing
+ * the added cookie.
+ * "changed"
+ * a cookie was changed. the subject is an nsICookie representing
+ * the new cookie. (note that host, path, and name are invariant
+ * for a given cookie; other parameters may change.)
+ * "batch-deleted"
+ * a set of cookies was purged (typically, because they have either
+ * expired or because the cookie list has grown too large). The subject
+ * is an nsIArray of nsICookie's representing the deleted cookies.
+ * Note that the array could contain a single cookie.
+ * "cleared"
+ * the entire cookie list was cleared. the subject is null.
+ *
+ * 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;
+
+ /*
+ * Possible values for the "network.cookie.lifetimePolicy" preference.
+ */
+ const uint32_t ACCEPT_NORMALLY = 0; // accept normally
+ // Value = 1 is considered the same as 0 (See Bug 606655).
+ const uint32_t ACCEPT_SESSION = 2; // downgrade to session
+ // Value = 3 is considered the same as 0
+
+ /*
+ * 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.ini b/netwerk/cookie/test/browser/browser.ini
new file mode 100644
index 0000000000..9a4ec0bacd
--- /dev/null
+++ b/netwerk/cookie/test/browser/browser.ini
@@ -0,0 +1,19 @@
+[DEFAULT]
+support-files =
+ file_empty.html
+ file_empty.js
+ head.js
+
+[browser_broadcastChannel.js]
+[browser_cookies.js]
+support-files = server.sjs
+[browser_domCache.js]
+[browser_indexedDB.js]
+[browser_originattributes.js]
+[browser_storage.js]
+[browser_serviceWorker.js]
+[browser_sharedWorker.js]
+[browser_sameSiteConsole.js]
+support-files = sameSite.sjs
+[browser_oversize.js]
+support-files = oversize.sjs
diff --git a/netwerk/cookie/test/browser/browser_broadcastChannel.js b/netwerk/cookie/test/browser/browser_broadcastChannel.js
new file mode 100644
index 0000000000..3882fee98f
--- /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_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_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..1da0940c4e
--- /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..68a322d98e
--- /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";
+
+let cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+
+// 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_task(async function setup() {
+ // 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
+ cm.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 = cm.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_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..ed2587b400
--- /dev/null
+++ b/netwerk/cookie/test/browser/head.js
@@ -0,0 +1,201 @@
+const { PermissionTestUtils } = ChromeUtils.import(
+ "resource://testing-common/PermissionTestUtils.jsm"
+);
+
+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..e2275d050c
--- /dev/null
+++ b/netwerk/cookie/test/browser/oversize.sjs
@@ -0,0 +1,9 @@
+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/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..283df46dcd
--- /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..3d31aeb1c6
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/cookie.sjs
@@ -0,0 +1,124 @@
+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") }));
+
+ 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);
+ };
+ };
+
+ 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..41b0065d41
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/cookiesHelper.js
@@ -0,0 +1,67 @@
+const ALLOWED = 0;
+const BLOCKED = 1;
+
+async function cleanupData() {
+ await new Promise(resolve => {
+ const chromeScript = SpecialPowers.loadChromeScript(_ => {
+ // eslint-disable-next-line no-undef
+ addMessageListener("go", __ => {
+ const { Services } = ChromeUtils.import(
+ "resource://gre/modules/Services.jsm"
+ );
+ Services.clearData.deleteData(
+ Services.clearData.CLEAR_COOKIES |
+ Services.clearData.CLEAR_ALL_CACHES |
+ Services.clearData.CLEAR_DOM_STORAGES,
+ ___ => {
+ // eslint-disable-next-line no-undef
+ 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.ini b/netwerk/cookie/test/mochitest/mochitest.ini
new file mode 100644
index 0000000000..26179d6fea
--- /dev/null
+++ b/netwerk/cookie/test/mochitest/mochitest.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+scheme=https
+support-files =
+ cookie.sjs
+ cookiesHelper.js
+
+[test_document_cookie.html]
+[test_fetch.html]
+[test_image.html]
+[test_script.html]
+[test_sharedWorker.html]
+[test_worker.html]
+[test_xhr.html]
+[test_metaTag.html]
+[test_xmlDocument.html]
+support-files = empty.html
+[test_document_cookie_notification.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..9d58c06695
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_baseDomain_publicsuffix.js
@@ -0,0 +1,107 @@
+"use strict";
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+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..4bccac6d1d
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_bug1155169.js
@@ -0,0 +1,93 @@
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const URI = Services.io.newURI("http://example.org/");
+
+const cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+
+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: "added",
+ isSession: true,
+ isSecure: false,
+ isHttpOnly: false,
+ });
+
+ // Update cookie with isHttpOnly=true.
+ setCookie("foo=bar; HttpOnly", {
+ type: "changed",
+ isSession: true,
+ isSecure: false,
+ isHttpOnly: true,
+ });
+
+ // Update cookie with isSecure=true.
+ setCookie("foo=bar; Secure", {
+ type: "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: "changed",
+ isSession: false,
+ isSecure: false,
+ isHttpOnly: false,
+ });
+
+ // Reset cookie.
+ setCookie("foo=bar", {
+ type: "changed",
+ isSession: true,
+ isSecure: false,
+ isHttpOnly: false,
+ });
+}
+
+function setCookie(value, expected) {
+ function setCookieInternal(valueInternal, expectedInternal = null) {
+ function observer(subject, topic, data) {
+ if (!expectedInternal) {
+ do_throw("no notification expected");
+ return;
+ }
+
+ // Check we saw the right notification.
+ Assert.equal(data, expectedInternal.type);
+
+ // Check cookie details.
+ let cookie = subject.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,
+ });
+
+ cs.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..46c9bf4a9f
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_bug1321912.js
@@ -0,0 +1,101 @@
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+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
+const cookies = Services.cookies.sessionCookies;
+
+Assert.equal(conn.schemaVersion, 12);
+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..3aab640773
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_bug643051.js
@@ -0,0 +1,41 @@
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { CookieXPCShellUtils } = ChromeUtils.import(
+ "resource://testing-common/CookieXPCShellUtils.jsm"
+);
+
+CookieXPCShellUtils.init(this);
+CookieXPCShellUtils.createServer({ hosts: ["example.net"] });
+
+add_task(async () => {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ let cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+
+ 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";
+ cs.setCookieStringFromHttp(uri, set, channel);
+
+ let actual = cs.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);
+});
diff --git a/netwerk/cookie/test/unit/test_eviction.js b/netwerk/cookie/test/unit/test_eviction.js
new file mode 100644
index 0000000000..30634d7818
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_eviction.js
@@ -0,0 +1,212 @@
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+const BASE_HOST = "example.org";
+const BASE_HOSTNAMES = ["example.org", "example.co.uk"];
+const SUBDOMAINS = ["", "pub.", "www.", "other."];
+
+const { CookieXPCShellUtils } = ChromeUtils.import(
+ "resource://testing-common/CookieXPCShellUtils.jsm"
+);
+
+CookieXPCShellUtils.init(this);
+CookieXPCShellUtils.createServer({ hosts: ["example.org"] });
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "cm",
+ "@mozilla.org/cookiemanager;1",
+ "nsICookieManager"
+);
+
+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
+ );
+
+ cm.removeAll();
+});
+
+// Verify that the given cookie names exist, and are ordered from least to most recently accessed
+function verifyCookies(names, uri) {
+ Assert.equal(cm.countCookiesFromHost(uri.host), names.length);
+ let actual_cookies = [];
+ for (let cookie of cm.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,
+ });
+
+ const cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+ cs.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..d807cb548c
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_getCookieSince.js
@@ -0,0 +1,72 @@
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+const cm = cs.QueryInterface(Ci.nsICookieManager);
+
+function setCookie(name, url) {
+ let value = `${name}=${Math.random()}; Path=/; Max-Age=1000; sameSite=none`;
+ info(`Setting cookie ${value} for ${url.spec}`);
+
+ let channel = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ cs.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(cm.cookies.length, 4, "Cookie check");
+
+ const cookies = cm.getCookiesSince(0);
+ Assert.equal(cookies.length, 4, "We retrieve all the 4 cookies");
+ checkSorting(cookies);
+
+ let someCookies = cm.getCookiesSince(cookies[0].creationTime + 1);
+ Assert.equal(someCookies.length, 3, "We retrieve some cookies");
+ checkSorting(someCookies);
+
+ someCookies = cm.getCookiesSince(cookies[1].creationTime + 1);
+ Assert.equal(someCookies.length, 2, "We retrieve some cookies");
+ checkSorting(someCookies);
+
+ someCookies = cm.getCookiesSince(cookies[2].creationTime + 1);
+ Assert.equal(someCookies.length, 1, "We retrieve some cookies");
+ checkSorting(someCookies);
+
+ someCookies = cm.getCookiesSince(cookies[3].creationTime + 1);
+ Assert.equal(someCookies.length, 0, "We retrieve some 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..9b9f10613f
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_parser_0001.js
@@ -0,0 +1,33 @@
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+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 cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+
+ let uri = NetUtil.newURI("http://example.org/");
+ let channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ let set = "foo=bar";
+ cs.setCookieStringFromHttp(uri, set, channel);
+
+ let expected = "foo=bar";
+ let actual = cs.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..acf68d6b78
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_parser_0019.js
@@ -0,0 +1,33 @@
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+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 cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+
+ 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=/";
+ cs.setCookieStringFromHttp(uri, set, channel);
+
+ let expected = "foo=b";
+ let actual = cs.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..b8f833b7da
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_rawSameSite.js
@@ -0,0 +1,126 @@
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+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 cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+
+ 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");
+ });
+
+ cs.setCookieStringFromHttp(uri, test.cookie, channel);
+
+ await promise;
+
+ conn = storage.openDatabase(dbFile);
+ Assert.equal(conn.schemaVersion, 12);
+
+ 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..e0ab8f6385
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_schemeMap.js
@@ -0,0 +1,287 @@
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ Services,
+ "cookiemgr",
+ "@mozilla.org/cookiemanager;1",
+ "nsICookieManager"
+);
+
+function inChildProcess() {
+ return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+}
+
+const { CookieXPCShellUtils } = ChromeUtils.import(
+ "resource://testing-common/CookieXPCShellUtils.jsm"
+);
+
+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
+ );
+ }
+
+ let cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+
+ 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,
+ });
+
+ cs.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,
+ });
+
+ cs.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();
+
+ 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
+ );
+ }
+
+ let cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+
+ info("Let's set a cookie from HTTP example.org");
+
+ let uri = NetUtil.newURI("https://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,
+ });
+
+ cs.setCookieStringFromHttp(uri, "a=b; sameSite=lax", channel);
+
+ let cookies = Services.cookies.getCookieStringFromHttp(uri, channel);
+ Assert.equal(cookies, "a=b", "Cookies match");
+
+ uri = NetUtil.newURI("http://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,
+ });
+
+ cookies = Services.cookies.getCookieStringFromHttp(uri, channel);
+ if (schemefulComparison) {
+ Assert.equal(cookies, "", "No cookie for different scheme!");
+ } else {
+ Assert.equal(cookies, "a=b", "Cookie even for different scheme!");
+ }
+
+ cookies = await CookieXPCShellUtils.getCookieStringFromDocument(uri.spec);
+ if (schemefulComparison) {
+ Assert.equal(cookies, "", "No cookie for different scheme!");
+ } else {
+ Assert.equal(cookies, "a=b", "Cookie even for different scheme!");
+ }
+
+ Services.cookies.removeAll();
+ });
+});
+
+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 without scheme");
+ Services.cookiemgr.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();
+});
+
+[
+ {
+ prefValue: true,
+ consoleMessage: `Cookie “a” has been treated as cross-site against “http://example.org/” because the scheme does not match.`,
+ },
+ {
+ prefValue: false,
+ consoleMessage: `Cookie “a” will be soon treated as cross-site cookie against “http://example.org/” because the scheme does not match.`,
+ },
+].forEach(test => {
+ add_task(async () => {
+ do_get_profile();
+
+ maybeInitializeCookieXPCShellUtils();
+
+ // Allow all cookies if the pref service is available in this process.
+ if (!inChildProcess()) {
+ Services.prefs.setBoolPref(
+ "network.cookie.sameSite.schemeful",
+ test.prefValue
+ );
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+ }
+
+ let cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+
+ info("Let's set a cookie from HTTPS example.org");
+
+ let uri = NetUtil.newURI("https://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,
+ });
+
+ cs.setCookieStringFromHttp(uri, "a=b; sameSite=lax", channel);
+
+ // 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(test.consoleMessage)) {
+ Services.console.unregisterListener(listener);
+ resolve();
+ }
+ },
+ };
+
+ Services.console.registerListener(listener);
+ });
+
+ const contentPage = await CookieXPCShellUtils.loadContentPage(
+ "http://example.org/"
+ );
+ await contentPage.close();
+
+ await consolePromise;
+ Services.cookies.removeAll();
+ });
+});
diff --git a/netwerk/cookie/test/unit/xpcshell.ini b/netwerk/cookie/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..3320111637
--- /dev/null
+++ b/netwerk/cookie/test/unit/xpcshell.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+head =
+
+[test_baseDomain_publicsuffix.js]
+[test_bug643051.js]
+[test_bug1155169.js]
+[test_bug1321912.js]
+[test_parser_0001.js]
+[test_parser_0019.js]
+[test_eviction.js]
+[test_rawSameSite.js]
+[test_getCookieSince.js]
+[test_schemeMap.js]
diff --git a/netwerk/dns/ChildDNSService.cpp b/netwerk/dns/ChildDNSService.cpp
new file mode 100644
index 0000000000..ebfb4aee26
--- /dev/null
+++ b/netwerk/dns/ChildDNSService.cpp
@@ -0,0 +1,450 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "DNSResolverInfo.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// ChildDNSService
+//-----------------------------------------------------------------------------
+
+static StaticRefPtr<ChildDNSService> gChildDNSService;
+static const char kPrefNameDisablePrefetch[] = "network.dns.disablePrefetch";
+
+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_IsMainThread()) {
+ return nullptr;
+ }
+ gChildDNSService = new ChildDNSService();
+ ClearOnShutdown(&gChildDNSService);
+ }
+
+ return do_AddRef(gChildDNSService);
+}
+
+NS_IMPL_ISUPPORTS(ChildDNSService, nsIDNSService, nsPIDNSService, nsIObserver)
+
+ChildDNSService::ChildDNSService()
+ : mFirstTime(true),
+ mDisablePrefetch(false),
+ mPendingRequestsLock("DNSPendingRequestsLock") {
+ 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, uint16_t aType,
+ const OriginAttributes& aOriginAttributes, uint32_t aFlags,
+ uintptr_t aListenerAddr, nsACString& aHashKey) {
+ aHashKey.Assign(aHost);
+ aHashKey.Assign(aTrrServer);
+ 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, uint32_t flags,
+ nsIDNSResolverInfo* aResolver, nsIDNSListener* listener,
+ nsIEventTarget* target_, const OriginAttributes& aOriginAttributes,
+ nsICancelable** result) {
+ if (XRE_IsContentProcess()) {
+ NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE);
+ }
+
+ bool resolveDNSInSocketProcess = false;
+ if (XRE_IsParentProcess() && nsIOService::UseSocketProcess()) {
+ resolveDNSInSocketProcess = true;
+ if (type != nsIDNSService::RESOLVE_TYPE_DEFAULT &&
+ (mTRRServiceParent->Mode() != MODE_TRRFIRST &&
+ mTRRServiceParent->Mode() != MODE_TRRONLY)) {
+ 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, DNSResolverInfo::URL(aResolver), type,
+ aOriginAttributes, flags, listener, target);
+ RefPtr<DNSRequestActor> dnsReq;
+ if (resolveDNSInSocketProcess) {
+ dnsReq = new DNSRequestParent(sender);
+ } else {
+ dnsReq = new DNSRequestChild(sender);
+ }
+
+ {
+ MutexAutoLock lock(mPendingRequestsLock);
+ nsCString key;
+ GetDNSRecordHashKey(hostname, DNSResolverInfo::URL(aResolver), type,
+ aOriginAttributes, flags, originalListenerAddr, key);
+ auto entry = mPendingRequests.LookupForAdd(key);
+ if (entry) {
+ entry.Data()->AppendElement(sender);
+ } else {
+ entry.OrInsert([&]() {
+ auto* hashEntry = new nsTArray<RefPtr<DNSRequestSender>>();
+ hashEntry->AppendElement(sender);
+ return hashEntry;
+ });
+ }
+ }
+
+ sender->StartRequest();
+
+ sender.forget(result);
+ return NS_OK;
+}
+
+nsresult ChildDNSService::CancelAsyncResolveInternal(
+ const nsACString& aHostname, uint16_t aType, uint32_t aFlags,
+ nsIDNSResolverInfo* aResolver, 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, DNSResolverInfo::URL(aResolver), 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, uint32_t flags,
+ nsIDNSResolverInfo* aResolver,
+ nsIDNSListener* listener, nsIEventTarget* target_,
+ JS::HandleValue 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, aResolver, listener,
+ target_, attrs, result);
+}
+
+NS_IMETHODIMP
+ChildDNSService::AsyncResolveNative(
+ const nsACString& hostname, nsIDNSService::ResolveType aType,
+ uint32_t flags, nsIDNSResolverInfo* aResolver, nsIDNSListener* listener,
+ nsIEventTarget* target_, const OriginAttributes& aOriginAttributes,
+ nsICancelable** result) {
+ return AsyncResolveInternal(hostname, aType, flags, aResolver, listener,
+ target_, aOriginAttributes, result);
+}
+
+NS_IMETHODIMP
+ChildDNSService::NewTRRResolverInfo(const nsACString& aTrrURL,
+ nsIDNSResolverInfo** aResolver) {
+ RefPtr<DNSResolverInfo> res = new DNSResolverInfo(aTrrURL);
+ res.forget(aResolver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSService::CancelAsyncResolve(const nsACString& aHostname,
+ nsIDNSService::ResolveType aType,
+ uint32_t aFlags,
+ nsIDNSResolverInfo* aResolver,
+ nsIDNSListener* aListener, nsresult aReason,
+ JS::HandleValue 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, aResolver,
+ aListener, aReason, attrs);
+}
+
+NS_IMETHODIMP
+ChildDNSService::CancelAsyncResolveNative(
+ const nsACString& aHostname, nsIDNSService::ResolveType aType,
+ uint32_t aFlags, nsIDNSResolverInfo* aResolver, nsIDNSListener* aListener,
+ nsresult aReason, const OriginAttributes& aOriginAttributes) {
+ return CancelAsyncResolveInternal(aHostname, aType, aFlags, aResolver,
+ aListener, aReason, aOriginAttributes);
+}
+
+NS_IMETHODIMP
+ChildDNSService::Resolve(const nsACString& hostname, uint32_t flags,
+ JS::HandleValue 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, uint32_t 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::GetCurrentTrrURI(nsACString& aURI) {
+ if (!mTRRServiceParent) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mTRRServiceParent->GetTrrURI(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChildDNSService::GetCurrentTrrMode(uint32_t* aMode) {
+ if (!mTRRServiceParent) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aMode = mTRRServiceParent->Mode();
+ 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.
+ uint32_t 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->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() {
+ // Disable prefetching either by explicit preference or if a manual proxy
+ // is configured
+ bool disablePrefetch = false;
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ prefs->GetBoolPref(kPrefNameDisablePrefetch, &disablePrefetch);
+ }
+
+ if (mFirstTime) {
+ mFirstTime = false;
+ if (prefs) {
+ prefs->AddObserver(kPrefNameDisablePrefetch, this, false);
+
+ // Monitor these to see if there is a change in proxy configuration
+ // If a manual proxy is in use, disable prefetch implicitly
+ prefs->AddObserver("network.proxy.type", this, false);
+ }
+ }
+
+ mDisablePrefetch =
+ disablePrefetch || (StaticPrefs::network_proxy_type() ==
+ nsIProtocolProxyService::PROXYCONFIG_MANUAL);
+
+ 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) {
+ // we are only getting called if a preference has changed.
+ NS_ASSERTION(strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0,
+ "unexpected observe call");
+
+ // Reread prefs
+ Init();
+ 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..4788b6c11d
--- /dev/null
+++ b/netwerk/dns/ChildDNSService.h
@@ -0,0 +1,69 @@
+/* -*- 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 "nsPIDNSService.h"
+#include "nsIObserver.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 nsPIDNSService, public nsIObserver {
+ public:
+ // AsyncResolve (and CancelAsyncResolve) can be called off-main
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSPIDNSSERVICE
+ NS_DECL_NSIDNSSERVICE
+ NS_DECL_NSIOBSERVER
+
+ ChildDNSService();
+
+ static already_AddRefed<ChildDNSService> GetSingleton();
+
+ void NotifyRequestDone(DNSRequestSender* aDnsRequest);
+
+ private:
+ virtual ~ChildDNSService() = default;
+
+ void MOZ_ALWAYS_INLINE GetDNSRecordHashKey(
+ const nsACString& aHost, const nsACString& aTrrServer, uint16_t aType,
+ const OriginAttributes& aOriginAttributes, uint32_t aFlags,
+ uintptr_t aListenerAddr, nsACString& aHashKey);
+ nsresult AsyncResolveInternal(const nsACString& hostname, uint16_t type,
+ uint32_t flags, nsIDNSResolverInfo* aResolver,
+ nsIDNSListener* listener,
+ nsIEventTarget* target_,
+ const OriginAttributes& aOriginAttributes,
+ nsICancelable** result);
+ nsresult CancelAsyncResolveInternal(
+ const nsACString& aHostname, uint16_t aType, uint32_t aFlags,
+ nsIDNSResolverInfo* aResolver, nsIDNSListener* aListener,
+ nsresult aReason, const OriginAttributes& aOriginAttributes);
+
+ bool mFirstTime;
+ bool mDisablePrefetch;
+
+ // We need to remember pending dns requests to be able to cancel them.
+ nsClassHashtable<nsCStringHashKey, nsTArray<RefPtr<DNSRequestSender>>>
+ mPendingRequests;
+ Mutex mPendingRequestsLock;
+ RefPtr<TRRServiceParent> mTRRServiceParent;
+};
+
+} // 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..c9d3e46ca3
--- /dev/null
+++ b/netwerk/dns/DNS.cpp
@@ -0,0 +1,382 @@
+/* -*- 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
+}
+
+// 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;
+}
+
+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()) {
+ return false;
+ }
+
+ nsAutoCString host;
+ nsContentUtils::ASCIIToLower(aAsciiHost, host);
+
+ return host.EqualsLiteral("localhost") ||
+ StringEndsWith(host, ".localhost"_ns);
+}
+
+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); }
+
+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);
+ if (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).
+ return true;
+ }
+ return false;
+}
+
+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 PL_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,
+ unsigned int aTRR, nsTArray<NetAddr>&& addresses)
+ : mHostName(host),
+ mCanonicalName(cname),
+ mFromTRR(aTRR),
+ mAddresses(std::move(addresses)) {}
+
+AddrInfo::AddrInfo(const nsACString& host, unsigned int aTRR,
+ nsTArray<NetAddr>&& addresses, uint32_t aTTL)
+ : ttl(aTTL),
+ mHostName(host),
+ mCanonicalName(),
+ mFromTRR(aTRR),
+ mAddresses(std::move(addresses)) {}
+
+// deep copy constructor
+AddrInfo::AddrInfo(const AddrInfo* src) {
+ mHostName = src->mHostName;
+ mCanonicalName = src->mCanonicalName;
+ ttl = src->ttl;
+ mFromTRR = src->mFromTRR;
+ 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..0763980d64
--- /dev/null
+++ b/netwerk/dns/DNS.h
@@ -0,0 +1,247 @@
+/* -*- 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 "plstr.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/LinkedList.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);
+
+ 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;
+};
+
+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,
+ unsigned int aTRR, nsTArray<NetAddr>&& addresses);
+
+ // Creates a basic AddrInfo object (initialize only the host and TRR status).
+ explicit AddrInfo(const nsACString& host, unsigned int aTRR,
+ nsTArray<NetAddr>&& addresses, uint32_t aTTL = NO_TTL_DATA);
+
+ explicit AddrInfo(const AddrInfo* src); // copy
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ unsigned int IsTRR() { return mFromTRR; }
+
+ 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;
+
+ unsigned int mFromTRR = 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);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // DNS_h_
diff --git a/netwerk/dns/DNSByTypeRecord.h b/netwerk/dns/DNSByTypeRecord.h
new file mode 100644
index 0000000000..2008b6d213
--- /dev/null
+++ b/netwerk/dns/DNSByTypeRecord.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 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;
+};
+
+} // namespace net
+} // namespace mozilla
+
+namespace mozilla {
+namespace ipc {
+
+template <>
+struct IPDLParamTraits<mozilla::net::IPCTypeRecord> {
+ typedef mozilla::net::IPCTypeRecord paramType;
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.mData);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mData)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::Nothing> {
+ typedef mozilla::Nothing paramType;
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ bool isSome = false;
+ WriteIPDLParam(aMsg, aActor, isSome);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ bool isSome;
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &isSome)) {
+ return false;
+ }
+ *aResult = Nothing();
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SVCB> {
+ typedef mozilla::net::SVCB paramType;
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.mSvcFieldPriority);
+ WriteIPDLParam(aMsg, aActor, aParam.mSvcDomainName);
+ WriteIPDLParam(aMsg, aActor, aParam.mSvcFieldValue);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSvcFieldPriority)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSvcDomainName)) {
+ return false;
+ }
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSvcFieldValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcParamAlpn> {
+ typedef mozilla::net::SvcParamAlpn paramType;
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.mValue);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcParamNoDefaultAlpn> {
+ typedef mozilla::net::SvcParamNoDefaultAlpn paramType;
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {}
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcParamPort> {
+ typedef mozilla::net::SvcParamPort paramType;
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.mValue);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcParamIpv4Hint> {
+ typedef mozilla::net::SvcParamIpv4Hint paramType;
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.mValue);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcParamEchConfig> {
+ typedef mozilla::net::SvcParamEchConfig paramType;
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.mValue);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcParamIpv6Hint> {
+ typedef mozilla::net::SvcParamIpv6Hint paramType;
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.mValue);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcParamODoHConfig> {
+ typedef mozilla::net::SvcParamODoHConfig paramType;
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.mValue);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ if (!ReadIPDLParam(aMsg, aIter, aActor, &aResult->mValue)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct IPDLParamTraits<mozilla::net::SvcFieldValue> {
+ typedef mozilla::net::SvcFieldValue paramType;
+ static void Write(IPC::Message* aMsg, IProtocol* aActor,
+ const paramType& aParam) {
+ WriteIPDLParam(aMsg, aActor, aParam.mValue);
+ }
+
+ static bool Read(const IPC::Message* aMsg, PickleIterator* aIter,
+ IProtocol* aActor, paramType* aResult) {
+ if (!ReadIPDLParam(aMsg, aIter, 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/DNSPacket.cpp b/netwerk/dns/DNSPacket.cpp
new file mode 100644
index 0000000000..460fe92636
--- /dev/null
+++ b/netwerk/dns/DNSPacket.cpp
@@ -0,0 +1,975 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+namespace mozilla {
+namespace net {
+
+extern mozilla::LazyLogModule gHostResolverLog;
+#undef LOG
+#undef LOG_ENABLED
+#define LOG(args) MOZ_LOG(gHostResolverLog, mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHostResolverLog, mozilla::LogLevel::Debug)
+
+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/draft-ietf-dnsop-extended-error-16#section-4
+// This is a list of errors for which we should not fallback to Do53.
+// These are normally DNSSEC failures or explicit filtering performed by the
+// recursive resolver.
+bool hardFail(uint16_t code) {
+ const uint16_t noFallbackErrors[] = {
+ 4, // Forged answer (malware filtering)
+ 6, // DNSSEC Boggus
+ 7, // Signature expired
+ 8, // Signature not yet valid
+ 9, // DNSKEY Missing
+ 10, // RRSIG missing
+ 11, // No ZONE Key Bit set
+ 12, // NSEC Missing
+ 17, // Filtered
+ };
+
+ for (const auto& err : noFallbackErrors) {
+ if (code == err) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// static
+nsresult DNSPacket::ParseSvcParam(unsigned int svcbIndex, uint16_t key,
+ SvcFieldValue& field, uint16_t length) {
+ 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(mResponse, 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 = mResponse[svcbIndex++];
+ length -= 1;
+ if (alpnIdLength > length) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ alpnArray.AppendElement(
+ nsCString((const char*)&mResponse[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(mResponse, 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(mResponse, svcbIndex));
+ ipv4array.AppendElement(addr);
+ length -= 4;
+ svcbIndex += 4;
+ }
+ break;
+ }
+ case SvcParamKeyEchConfig: {
+ field.mValue = AsVariant(SvcParamEchConfig{
+ .mValue = nsCString((const char*)(&mResponse[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] = mResponse[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*)(&mResponse[svcbIndex]), length)});
+ break;
+ }
+ default: {
+ // Unespected type. We'll just ignore it.
+ return NS_OK;
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult DNSPacket::PassQName(unsigned int& index) {
+ 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>(mResponse[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'.
+nsresult DNSPacket::GetQname(nsACString& aQname, unsigned int& aIndex) {
+ 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 >= mBodySize) {
+ LOG(("TRR: bad Qname packet\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ clength = static_cast<uint8_t>(mResponse[cindex]);
+ if ((clength & 0xc0) == 0xc0) {
+ // name pointer, get the new offset (14 bits)
+ if ((cindex + 1) >= mBodySize) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ // extract the new index position for the next label
+ uint16_t newpos = (clength & 0x3f) << 8 | mResponse[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) > mBodySize) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ aQname.Append((const char*)(&mResponse[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;
+
+// static
+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
+
+ aBody += '\0'; // ARCOUNT
+ aBody += aDisableECS ? 1 : '\0'; // 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 (aDisableECS) {
+ // 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';
+
+ aBody += '\0'; // upper 8 bit RDLEN
+ aBody += 8; // RDLEN | u_int16_t | length of all RDATA |
+
+ // RDATA | octet stream | {attribute,value} pairs |
+ // The RDATA is just the ECS option setting zero subnet prefix
+
+ 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
+ }
+ return NS_OK;
+}
+
+//
+// DohDecode() collects the TTL and the IP addresses in the response
+//
+// static
+nsresult DNSPacket::Decode(
+ nsCString& aHost, enum TrrType aType, nsCString& aCname, bool aAllowRFC1918,
+ nsHostRecord::TRRSkippedReason& aReason, DOHresp& aResp,
+ TypeRecordResultType& aTypeResult,
+ nsClassHashtable<nsCStringHashKey, DOHresp>& aAdditionalRecords,
+ uint32_t& aTTL) {
+ // 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(), mBodySize));
+
+ aCname.Truncate();
+
+ if (mBodySize < 12 || mResponse[0] || mResponse[1]) {
+ LOG(("TRR bad incoming DOH, eject!\n"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ uint8_t rcode = mResponse[3] & 0x0F;
+ LOG(("TRR Decode %s RCODE %d\n", aHost.get(), rcode));
+ if (rcode) {
+ if (aReason == nsHostRecord::TRR_UNSET) {
+ aReason = nsHostRecord::TRR_RCODE_FAIL;
+ }
+ }
+
+ uint16_t questionRecords = get16bit(mResponse, 4); // qdcount
+ // iterate over the single(?) host name in question
+ while (questionRecords) {
+ do {
+ if (mBodySize < (index + 1)) {
+ LOG(("TRR Decode 1 index: %u size: %u", index, mBodySize));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ length = static_cast<uint8_t>(mResponse[index]);
+ if (length) {
+ if (host.Length()) {
+ host.Append(".");
+ }
+ if (mBodySize < (index + 1 + length)) {
+ LOG(("TRR Decode 2 index: %u size: %u len: %u", index, mBodySize,
+ length));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ host.Append(((char*)mResponse) + index + 1, length);
+ }
+ index += 1 + length; // skip length byte + label
+ } while (length);
+ if (mBodySize < (index + 4)) {
+ LOG(("TRR Decode 3 index: %u size: %u", index, mBodySize));
+ 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(mResponse, 6);
+
+ LOG(("TRR Decode: %d answer records (%u bytes body) %s index=%u\n",
+ answerRecords, mBodySize, host.get(), index));
+
+ while (answerRecords) {
+ nsAutoCString qname;
+ rv = GetQname(qname, index);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ // 16 bit TYPE
+ if (mBodySize < (index + 2)) {
+ LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index + 2));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ uint16_t TYPE = get16bit(mResponse, index);
+
+ 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));
+ return NS_ERROR_UNEXPECTED;
+ }
+ index += 2;
+
+ // 16 bit class
+ if (mBodySize < (index + 2)) {
+ LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index + 2));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ uint16_t CLASS = get16bit(mResponse, 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 (mBodySize < (index + 4)) {
+ LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ uint32_t TTL = get32bit(mResponse, index);
+ index += 4;
+
+ // 16 bit RDLENGTH
+ if (mBodySize < (index + 2)) {
+ LOG(("TRR: Dohdecode:%d fail at index %d\n", __LINE__, index));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ uint16_t RDLENGTH = get16bit(mResponse, index);
+ index += 2;
+
+ if (mBodySize < (index + RDLENGTH)) {
+ LOG(("TRR: Dohdecode:%d fail RDLENGTH=%d at index %d\n", __LINE__,
+ RDLENGTH, index));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ // 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() == '.')) &&
+ qname.Compare(aHost.BeginReading(), true, qname.Length()) == 0;
+
+ 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, mResponse, 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, mResponse, 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);
+ 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 = mResponse[txtIndex++];
+ available--;
+ if (characterStringLen > available) {
+ LOG(("DNSPacket::DohDecode MALFORMED TXT RECORD\n"));
+ break;
+ }
+ txt.Append((const char*)(&mResponse[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;
+ int32_t lastSvcParamKey = -1;
+
+ unsigned int svcbIndex = index;
+ CheckedInt<uint16_t> available = RDLENGTH;
+
+ // Should have at least 2 bytes for the priority and one for the
+ // qname length.
+ if (available.value() < 3) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ parsed.mSvcFieldPriority = get16bit(mResponse, svcbIndex);
+ svcbIndex += 2;
+
+ rv = GetQname(parsed.mSvcDomainName, svcbIndex);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (parsed.mSvcDomainName.IsEmpty()) {
+ if (parsed.mSvcFieldPriority == 0) {
+ // For AliasMode SVCB RRs, a TargetName of "." indicates that the
+ // service is not available or does not exist.
+ continue;
+ }
+
+ // For ServiceMode SVCB RRs, if TargetName has the value ".",
+ // then the owner name of this record MUST be used as
+ // the effective TargetName.
+ parsed.mSvcDomainName = qname;
+ }
+
+ available -= (svcbIndex - index);
+ 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(mResponse, 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(mResponse, svcbIndex);
+ svcbIndex += 2;
+
+ available -= 4 + len;
+ if (!available.isValid()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = ParseSvcParam(svcbIndex, key, value, len);
+ 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>()) {
+ parsed.mHasIPHints = true;
+ }
+ if (value.mValue.is<SvcParamEchConfig>()) {
+ parsed.mHasEchConfig = true;
+ parsed.mEchConfig = value.mValue.as<SvcParamEchConfig>().mValue;
+ }
+ if (value.mValue.is<SvcParamODoHConfig>()) {
+ parsed.mODoHConfig = value.mValue.as<SvcParamODoHConfig>().mValue;
+ }
+ parsed.mSvcFieldValue.AppendElement(value);
+ }
+
+ // 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;
+ ToLowerCase(aCname);
+ LOG(("DNSPacket::DohDecode HTTPSSVC AliasForm host %s => %s\n",
+ host.get(), aCname.get()));
+ break;
+ }
+
+ if (aType != TRRTYPE_HTTPSSVC) {
+ // Ignore the entry that we just parsed if we didn't ask for it.
+ break;
+ }
+
+ 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, mBodySize));
+ answerRecords--;
+ }
+
+ // NSCOUNT
+ uint16_t nsRecords = get16bit(mResponse, 8);
+ LOG(("TRR Decode: %d ns records (%u bytes body)\n", nsRecords, mBodySize));
+ while (nsRecords) {
+ rv = PassQName(index);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (mBodySize < (index + 8)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ index += 2; // type
+ index += 2; // class
+ index += 4; // ttl
+
+ // 16 bit RDLENGTH
+ if (mBodySize < (index + 2)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ uint16_t RDLENGTH = get16bit(mResponse, index);
+ index += 2;
+ if (mBodySize < (index + RDLENGTH)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ index += RDLENGTH;
+ LOG(("done with nsRecord now %u of %u\n", index, mBodySize));
+ nsRecords--;
+ }
+
+ // additional resource records
+ uint16_t arRecords = get16bit(mResponse, 10);
+ LOG(("TRR Decode: %d additional resource records (%u bytes body)\n",
+ arRecords, mBodySize));
+
+ while (arRecords) {
+ nsAutoCString qname;
+ rv = GetQname(qname, index);
+ if (NS_FAILED(rv)) {
+ LOG(("Bad qname for additional record"));
+ return rv;
+ }
+
+ if (mBodySize < (index + 8)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ uint16_t type = get16bit(mResponse, index);
+ index += 2;
+ // The next two bytes encode class
+ // (or udpPayloadSize when type is TRRTYPE_OPT)
+ uint16_t cls = get16bit(mResponse, index);
+ index += 2;
+ // The next 4 bytes encode TTL
+ // (or extRCode + ednsVersion + flags when type is TRRTYPE_OPT)
+ uint32_t ttl = get32bit(mResponse, index);
+ index += 4;
+ // cls and ttl are unused when type is TRRTYPE_OPT
+
+ // 16 bit RDLENGTH
+ if (mBodySize < (index + 2)) {
+ LOG(("Record too small"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ uint16_t rdlength = get16bit(mResponse, index);
+ index += 2;
+ if (mBodySize < (index + rdlength)) {
+ LOG(("rdlength too big"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ auto parseRecord = [&]() {
+ LOG(("Parsing additional record type: %u", type));
+ auto& entry = aAdditionalRecords.GetOrInsert(qname);
+ if (!entry) {
+ entry.reset(new DOHresp());
+ }
+
+ 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, mResponse, 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, mResponse, 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(mResponse, index + offset);
+ LOG(("optCode: %u", optCode));
+ offset += 2;
+ if (offset + 2 > rdlength) {
+ break;
+ }
+ uint16_t optLen = get16bit(mResponse, 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(mResponse, index + offset);
+
+ LOG((
+ "Extended error code: %u message: %s", extendedError,
+ nsAutoCString((char*)mResponse + 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, mBodySize));
+ arRecords--;
+ }
+
+ if (index != mBodySize) {
+ LOG(("DohDecode failed to parse entire response body, %u out of %u bytes\n",
+ index, mBodySize));
+ // failed to parse 100%, do not continue
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ 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 && hardFail(extendedError)) {
+ return NS_ERROR_DEFINITIVE_UNKNOWN_HOST;
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ // 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;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/DNSPacket.h b/netwerk/dns/DNSPacket.h
new file mode 100644
index 0000000000..93caed20d0
--- /dev/null
+++ b/netwerk/dns/DNSPacket.h
@@ -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/. */
+
+#ifndef mozilla_net_DNSPacket_h__
+#define mozilla_net_DNSPacket_h__
+
+#include "mozilla/Result.h"
+#include "nsHostResolver.h"
+
+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 = UINT32_MAX;
+};
+
+// 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:
+ // 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
+ static 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.
+ nsresult Decode(
+ nsCString& aHost, enum TrrType aType, nsCString& aCname,
+ bool aAllowRFC1918, nsHostRecord::TRRSkippedReason& reason,
+ DOHresp& aResp, TypeRecordResultType& aTypeResult,
+ nsClassHashtable<nsCStringHashKey, DOHresp>& aAdditionalRecords,
+ uint32_t& aTTL);
+
+ private:
+ // 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;
+
+ nsresult PassQName(unsigned int& index);
+ nsresult GetQname(nsACString& aQname, unsigned int& aIndex);
+ nsresult ParseSvcParam(unsigned int svcbIndex, uint16_t key,
+ SvcFieldValue& field, uint16_t length);
+
+ // The response buffer.
+ unsigned char mResponse[MAX_SIZE]{};
+ unsigned int mBodySize = 0;
+};
+
+} // 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..a46eb1e4e2
--- /dev/null
+++ b/netwerk/dns/DNSRequestBase.h
@@ -0,0 +1,148 @@
+/* -*- 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 uint16_t& type,
+ const OriginAttributes& originAttributes,
+ const uint32_t& 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,
+ const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes,
+ const uint32_t& aFlags, nsIDNSListener* aListener,
+ nsIEventTarget* target);
+
+ void OnRecvCancelDNSRequest(const nsCString& hostName,
+ const nsCString& trrServer, const uint16_t& type,
+ const OriginAttributes& originAttributes,
+ const uint32_t& 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;
+ nsCString mHost;
+ nsCString mTrrServer;
+ uint16_t mType;
+ const OriginAttributes mOriginAttributes;
+ uint16_t mFlags;
+};
+
+// 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();
+
+ void DoAsyncResolve(const nsACString& hostname, const nsACString& trrServer,
+ uint16_t type, const OriginAttributes& originAttributes,
+ uint32_t flags);
+
+ void OnRecvCancelDNSRequest(const nsCString& hostName,
+ const nsCString& trrServer, const uint16_t& type,
+ const OriginAttributes& originAttributes,
+ const uint32_t& 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;
+
+ uint32_t mFlags;
+};
+
+// 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..b25a9ee03e
--- /dev/null
+++ b/netwerk/dns/DNSRequestChild.cpp
@@ -0,0 +1,536 @@
+/* -*- 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, uint16_t flags);
+
+ private:
+ virtual ~ChildDNSRecord() = default;
+
+ nsCString mCanonicalName;
+ nsTArray<NetAddr> mAddresses;
+ uint32_t mCurrent; // addr iterator
+ uint16_t mFlags;
+ double mTrrFetchDuration;
+ double mTrrFetchDurationNetworkOnly;
+ bool mIsTRR;
+ uint32_t mEffectiveTRRMode;
+};
+
+NS_IMPL_ISUPPORTS(ChildDNSRecord, nsIDNSRecord, nsIDNSAddrRecord)
+
+ChildDNSRecord::ChildDNSRecord(const DNSRecord& reply, uint16_t flags)
+ : mCurrent(0), mFlags(flags) {
+ mCanonicalName = reply.canonicalName();
+ mTrrFetchDuration = reply.trrFetchDuration();
+ mTrrFetchDurationNetworkOnly = reply.trrFetchDurationNetworkOnly();
+ mIsTRR = reply.isTRR();
+ 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();
+}
+
+//-----------------------------------------------------------------------------
+// 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::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(uint32_t* aMode) {
+ *aMode = mEffectiveTRRMode;
+ 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);
+
+ private:
+ virtual ~ChildDNSByTypeRecord() = default;
+
+ TypeRecordResultType mResults = AsVariant(mozilla::Nothing());
+ bool mAllRecordsExcluded = false;
+};
+
+NS_IMPL_ISUPPORTS(ChildDNSByTypeRecord, nsIDNSByTypeRecord, nsIDNSRecord,
+ nsIDNSTXTRecord, nsIDNSHTTPSSVCRecord)
+
+ChildDNSByTypeRecord::ChildDNSByTypeRecord(const TypeRecordResultType& reply,
+ const nsACString& aHost)
+ : DNSHTTPSSVCRecordBase(aHost), mAllRecordsExcluded(false) {
+ mResults = reply;
+}
+
+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;
+}
+
+//-----------------------------------------------------------------------------
+// DNSRequestSender
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(DNSRequestSender, nsICancelable)
+
+DNSRequestSender::DNSRequestSender(
+ const nsACString& aHost, const nsACString& aTrrServer,
+ const uint16_t& aType, const OriginAttributes& aOriginAttributes,
+ const uint32_t& aFlags, nsIDNSListener* aListener, nsIEventTarget* target)
+ : mListener(aListener),
+ mTarget(target),
+ mResultStatus(NS_OK),
+ mHost(aHost),
+ mTrrServer(aTrrServer),
+ mType(aType),
+ mOriginAttributes(aOriginAttributes),
+ mFlags(aFlags) {}
+
+void DNSRequestSender::OnRecvCancelDNSRequest(
+ const nsCString& hostName, const nsCString& trrServer, const uint16_t& type,
+ const OriginAttributes& originAttributes, const uint32_t& flags,
+ const nsresult& reason) {}
+
+NS_IMETHODIMP
+DNSRequestSender::Cancel(nsresult reason) {
+ if (!mIPCActor) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mIPCActor->CanSend()) {
+ // We can only do IPDL on the main thread
+ nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
+ "net::CancelDNSRequestEvent",
+ [actor(mIPCActor), host(mHost), trrServer(mTrrServer), type(mType),
+ originAttributes(mOriginAttributes), flags(mFlags), reason]() {
+ if (!actor->CanSend()) {
+ return;
+ }
+
+ if (DNSRequestChild* child = actor->AsDNSRequestChild()) {
+ Unused << child->SendCancelDNSRequest(
+ host, trrServer, type, originAttributes, flags, reason);
+ } else if (DNSRequestParent* parent = actor->AsDNSRequestParent()) {
+ Unused << parent->SendCancelDNSRequest(
+ host, trrServer, type, originAttributes, flags, reason);
+ }
+ });
+ SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget());
+ }
+ return NS_OK;
+}
+
+void DNSRequestSender::StartRequest() {
+ // we can only do IPDL on the main thread
+ if (!NS_IsMainThread()) {
+ SchedulerGroup::Dispatch(
+ TaskCategory::Other,
+ NewRunnableMethod("net::DNSRequestSender::StartRequest", this,
+ &DNSRequestSender::StartRequest));
+ return;
+ }
+
+ if (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, 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;
+ }
+
+ socketProcessChild->SendPDNSRequestConstructor(
+ child, mHost, mTrrServer, 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->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);
+ 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 uint16_t& type,
+ const OriginAttributes& originAttributes, const uint32_t& flags,
+ const nsresult& reason) {
+ mDNSRequest->OnRecvCancelDNSRequest(hostName, trrServer, 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..c360b8d58b
--- /dev/null
+++ b/netwerk/dns/DNSRequestChild.h
@@ -0,0 +1,41 @@
+/* -*- 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 uint16_t& type, const OriginAttributes& originAttributes,
+ const uint32_t& 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..0159745a6e
--- /dev/null
+++ b/netwerk/dns/DNSRequestParent.cpp
@@ -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 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "DNSResolverInfo.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+DNSRequestHandler::DNSRequestHandler() : mFlags(0) {}
+
+//-----------------------------------------------------------------------------
+// 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,
+ uint16_t type,
+ const OriginAttributes& originAttributes,
+ uint32_t flags) {
+ nsresult rv;
+ mFlags = flags;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIEventTarget> main = GetMainThreadEventTarget();
+ nsCOMPtr<nsICancelable> unused;
+ RefPtr<DNSResolverInfo> res;
+ if (!trrServer.IsEmpty()) {
+ res = new DNSResolverInfo(trrServer);
+ }
+ rv = dns->AsyncResolveNative(
+ hostname, static_cast<nsIDNSService::ResolveType>(type), flags, res,
+ 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 uint16_t& type, const OriginAttributes& originAttributes,
+ const uint32_t& flags, const nsresult& reason) {
+ nsresult rv;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<DNSResolverInfo> res;
+ if (!aTrrServer.IsEmpty()) {
+ res = new DNSResolverInfo(aTrrServer);
+ }
+ rv = dns->CancelAsyncResolveNative(
+ hostName, static_cast<nsIDNSService::ResolveType>(type), flags, res,
+ 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);
+ 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);
+
+ uint32_t effectiveTRRMode = 0;
+ rec->GetEffectiveTRRMode(&effectiveTRRMode);
+
+ SendLookupCompletedHelper(
+ mIPCActor, DNSRequestResponse(DNSRecord(cname, array, trrFetchDuration,
+ trrFetchDurationNetworkOnly,
+ isTRR, effectiveTRRMode)));
+ } 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 uint16_t& type,
+ const OriginAttributes& originAttributes, const uint32_t& flags,
+ const nsresult& reason) {
+ mDNSRequest->OnRecvCancelDNSRequest(hostName, trrServer, 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..59e6df8235
--- /dev/null
+++ b/netwerk/dns/DNSRequestParent.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_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 uint16_t& type, const OriginAttributes& originAttributes,
+ const uint32_t& 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/DNSResolverInfo.cpp b/netwerk/dns/DNSResolverInfo.cpp
new file mode 100644
index 0000000000..41e3bc7bb7
--- /dev/null
+++ b/netwerk/dns/DNSResolverInfo.cpp
@@ -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 "DNSResolverInfo.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(DNSResolverInfo, nsIDNSResolverInfo)
+
+NS_IMETHODIMP
+DNSResolverInfo::GetURL(nsACString& aURL) {
+ aURL = mURL;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/DNSResolverInfo.h b/netwerk/dns/DNSResolverInfo.h
new file mode 100644
index 0000000000..dd4c9b1c85
--- /dev/null
+++ b/netwerk/dns/DNSResolverInfo.h
@@ -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/. */
+
+#ifndef mozilla_net_DNSResolverInfo_h__
+#define mozilla_net_DNSResolverInfo_h__
+
+#include "nsIDNSResolverInfo.h"
+
+namespace mozilla {
+namespace net {
+
+class DNSResolverInfo : public nsIDNSResolverInfo {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSRESOLVERINFO
+ public:
+ explicit DNSResolverInfo(const nsACString& aURL) : mURL(aURL){};
+ static nsCString URL(nsIDNSResolverInfo* aResolver) {
+ nsCString url;
+ if (aResolver) {
+ MOZ_ALWAYS_SUCCEEDS(aResolver->GetURL(url));
+ }
+ return url;
+ }
+
+ private:
+ virtual ~DNSResolverInfo() = default;
+ nsCString mURL;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_DNSResolverInfo_h__
diff --git a/netwerk/dns/GetAddrInfo.cpp b/netwerk/dns/GetAddrInfo.cpp
new file mode 100644
index 0000000000..b586499b19
--- /dev/null
+++ b/netwerk/dns/GetAddrInfo.cpp
@@ -0,0 +1,465 @@
+/* -*- 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"
+
+namespace mozilla {
+namespace net {
+
+static StaticRefPtr<NativeDNSResolverOverride> gOverrideService;
+
+static 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=%X. reqFamily = %X\n",
+ aHost.BeginReading(), status, reqFamily);
+ return NS_ERROR_FAILURE;
+ } else if (status != NOERROR) {
+ LOG_WARNING("DnsQuery_A failed with status %X.\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 %u results", aCanonHost.BeginReading(),
+ addresses.Length());
+ if (addresses.IsEmpty()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ RefPtr<AddrInfo> ai(
+ new AddrInfo(aCanonHost, canonName, 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
+
+ PRAddrInfo* prai =
+ PR_GetAddrInfoByName(aCanonHost.BeginReading(), aAddressFamily, prFlags);
+
+ if (!prai) {
+ 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()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ ai.forget(aAddrInfo);
+
+ 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);
+ nsTArray<PRNetAddr>* overrides = overrideService->mOverrides.GetValue(aHost);
+ if (!overrides) {
+ return false;
+ }
+ nsCString* cname = nullptr;
+ if (aFlags & nsHostResolver::RES_CANON_NAME) {
+ cname = overrideService->mCnames.GetValue(aHost);
+ }
+
+ RefPtr<AddrInfo> ai;
+
+ nsTArray<NetAddr> addresses;
+ for (const auto& ip : *overrides) {
+ if (aAddressFamily != AF_UNSPEC && ip.raw.family != aAddressFamily) {
+ continue;
+ }
+ NetAddr addr(&ip);
+ addresses.AppendElement(addr);
+ }
+
+ if (!cname) {
+ ai = new AddrInfo(aHost, 0, std::move(addresses));
+ } else {
+ ai = new AddrInfo(aHost, *cname, 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)) {
+ return NS_OK;
+ }
+
+ nsAutoCString 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;
+}
+
+// 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) {
+ PRNetAddr tempAddr;
+ // Unfortunately, PR_StringToNetAddr does not properly initialize
+ // the output buffer in the case of IPv6 input. See bug 223145.
+ memset(&tempAddr, 0, sizeof(PRNetAddr));
+
+ if (PR_StringToNetAddr(nsCString(aIPLiteral).get(), &tempAddr) !=
+ PR_SUCCESS) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ AutoWriteLock lock(mLock);
+ auto& overrides = mOverrides.GetOrInsert(aHost);
+ overrides.AppendElement(tempAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP NativeDNSResolverOverride::SetCnameOverride(
+ const nsACString& aHost, const nsACString& aCNAME) {
+ if (aCNAME.IsEmpty()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ AutoWriteLock lock(mLock);
+ mCnames.Put(aHost, nsCString(aCNAME));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP NativeDNSResolverOverride::ClearHostOverride(
+ const nsACString& aHost) {
+ AutoWriteLock lock(mLock);
+ mCnames.Remove(aHost);
+ auto overrides = mOverrides.GetAndRemove(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;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/GetAddrInfo.h b/netwerk/dns/GetAddrInfo.h
new file mode 100644
index 0000000000..e7dfdfdeec
--- /dev/null
+++ b/netwerk/dns/GetAddrInfo.h
@@ -0,0 +1,87 @@
+/* -*- 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 "nsDataHashtable.h"
+#include "mozilla/RWLock.h"
+#include "nsTArray.h"
+#include "prio.h"
+
+#if defined(XP_WIN)
+# define DNSQUERY_AVAILABLE 1
+#else
+# undef DNSQUERY_AVAILABLE
+#endif
+
+namespace mozilla {
+namespace net {
+
+class AddrInfo;
+
+/**
+ * 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();
+
+class NativeDNSResolverOverride : public nsINativeDNSResolverOverride {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINATIVEDNSRESOLVEROVERRIDE
+ public:
+ NativeDNSResolverOverride() : mLock("NativeDNSResolverOverride") {}
+
+ static already_AddRefed<nsINativeDNSResolverOverride> GetSingleton();
+
+ private:
+ virtual ~NativeDNSResolverOverride() = default;
+ mozilla::RWLock mLock;
+
+ nsDataHashtable<nsCStringHashKey, nsTArray<PRNetAddr>> mOverrides;
+ nsDataHashtable<nsCStringHashKey, nsCString> mCnames;
+
+ friend bool FindAddrOverride(const nsACString& aHost, uint16_t aAddressFamily,
+ uint16_t aFlags, AddrInfo** aAddrInfo);
+};
+
+} // 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..9eb477d8df
--- /dev/null
+++ b/netwerk/dns/HTTPSSVC.cpp
@@ -0,0 +1,433 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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"
+
+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;
+}
+
+Maybe<Tuple<nsCString, bool>> SVCB::GetAlpn(bool aNoHttp2,
+ bool aNoHttp3) const {
+ Maybe<Tuple<nsCString, bool>> alpn;
+ for (const auto& value : mSvcFieldValue) {
+ if (value.mValue.is<SvcParamAlpn>()) {
+ nsTArray<nsCString> alpnList;
+ alpnList.AppendElements(value.mValue.as<SvcParamAlpn>().mValue);
+ if (!alpnList.IsEmpty()) {
+ alpn.emplace();
+ alpn = Some(SelectAlpnFromAlpnList(alpnList, aNoHttp2, aNoHttp3));
+ }
+ return alpn;
+ }
+ }
+
+ return Nothing();
+}
+
+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);
+ }
+ }
+}
+
+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<Tuple<nsCString, bool>> SVCBRecord::GetAlpn() { return mAlpn; }
+
+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 CheckAlpnIsUsable(const nsACString& aTargetName,
+ const nsACString& aAlpn, bool aIsHttp3,
+ uint32_t& aExcludedCount) {
+ if (aAlpn.IsEmpty()) {
+ return false;
+ }
+
+ if (aIsHttp3 && gHttpHandler->IsHttp3Excluded(aTargetName)) {
+ aExcludedCount++;
+ return false;
+ }
+
+ return true;
+}
+
+already_AddRefed<nsISVCBRecord>
+DNSHTTPSSVCRecordBase::GetServiceModeRecordInternal(
+ bool aNoHttp2, bool aNoHttp3, const nsTArray<SVCB>& aRecords,
+ bool& aRecordsAllExcluded, bool aCheckHttp3ExcludedList) {
+ nsCOMPtr<nsISVCBRecord> selectedRecord;
+ uint32_t recordHasNoDefaultAlpnCount = 0;
+ uint32_t recordExcludedCount = 0;
+ aRecordsAllExcluded = false;
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ bool RRSetHasEchConfig = false;
+ uint32_t h3ExcludedCount = 0;
+
+ for (const SVCB& record : aRecords) {
+ if (record.mSvcFieldPriority == 0) {
+ // In ServiceMode, the SvcPriority should never be 0.
+ return nullptr;
+ }
+
+ if (record.NoDefaultAlpn()) {
+ ++recordHasNoDefaultAlpnCount;
+ }
+
+ RRSetHasEchConfig |= record.mHasEchConfig;
+
+ bool excluded = false;
+ if (NS_SUCCEEDED(dns->IsSVCDomainNameFailed(mHost, record.mSvcDomainName,
+ &excluded)) &&
+ excluded) {
+ // Skip if the domain name of this record was failed to connect before.
+ ++recordExcludedCount;
+ continue;
+ }
+
+ Maybe<uint16_t> port = record.GetPort();
+ if (port && *port == 0) {
+ // Found an unsafe port, skip this record.
+ continue;
+ }
+
+ Maybe<Tuple<nsCString, bool>> alpn = record.GetAlpn(aNoHttp2, aNoHttp3);
+ if (alpn) {
+ if (!CheckAlpnIsUsable(record.mSvcDomainName, Get<0>(*alpn),
+ aCheckHttp3ExcludedList && Get<1>(*alpn),
+ h3ExcludedCount)) {
+ continue;
+ }
+ }
+
+ if (gHttpHandler->EchConfigEnabled() && RRSetHasEchConfig &&
+ !record.mHasEchConfig) {
+ // Don't use this record if this record has no echConfig, but others have.
+ continue;
+ }
+
+ if (!selectedRecord) {
+ selectedRecord = new SVCBRecord(record, std::move(port), std::move(alpn));
+ }
+ }
+
+ // If all records indicate "no-default-alpn", we should not use this RRSet.
+ if (recordHasNoDefaultAlpnCount == aRecords.Length()) {
+ return nullptr;
+ }
+
+ if (recordExcludedCount == aRecords.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 == aRecords.Length() && aCheckHttp3ExcludedList) {
+ return GetServiceModeRecordInternal(aNoHttp2, aNoHttp3, aRecords,
+ aRecordsAllExcluded, false);
+ }
+
+ 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;
+ for (const SVCB& record : aRecords) {
+ if (record.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.mHasEchConfig;
+ if (!(*aAllRecordsHaveEchConfig)) {
+ aResult.Clear();
+ return;
+ }
+
+ Maybe<uint16_t> port = record.GetPort();
+ if (port && *port == 0) {
+ // Found an unsafe port, skip this record.
+ continue;
+ }
+
+ Maybe<Tuple<nsCString, bool>> alpn = record.GetAlpn(aNoHttp2, aNoHttp3);
+ if (alpn) {
+ if (!CheckAlpnIsUsable(record.mSvcDomainName, Get<0>(*alpn),
+ aCheckHttp3ExcludedList && Get<1>(*alpn),
+ h3ExcludedCount)) {
+ continue;
+ }
+ }
+
+ RefPtr<nsISVCBRecord> svcbRecord =
+ new SVCBRecord(record, std::move(port), std::move(alpn));
+ 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 == aRecords.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..354f779b34
--- /dev/null
+++ b/netwerk/dns/HTTPSSVC.h
@@ -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/. */
+
+#ifndef HTTPSSVC_h__
+#define HTTPSSVC_h__
+
+#include "nsIDNSByTypeRecord.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/Variant.h"
+#include "mozilla/Maybe.h"
+
+namespace mozilla {
+namespace net {
+
+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;
+ Maybe<Tuple<nsCString, bool>> GetAlpn(bool aNoHttp2, bool aNoHttp3) const;
+ void GetIPHints(CopyableTArray<mozilla::net::NetAddr>& aAddresses) const;
+ uint16_t mSvcFieldPriority = 0;
+ nsCString mSvcDomainName;
+ nsCString mEchConfig;
+ nsCString mODoHConfig;
+ bool mHasIPHints = false;
+ bool mHasEchConfig = false;
+ CopyableTArray<SvcFieldValue> mSvcFieldValue;
+};
+
+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<uint16_t>&& aPort,
+ Maybe<Tuple<nsCString, bool>>&& aAlpn)
+ : mData(data), mPort(std::move(aPort)), mAlpn(std::move(aAlpn)) {}
+
+ private:
+ virtual ~SVCBRecord() = default;
+ SVCB mData;
+ Maybe<uint16_t> mPort;
+ Maybe<Tuple<nsCString, bool>> 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/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..658329693a
--- /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 }
+typedef std::pair<char16_t, char16_t> BlocklistRange;
+
+// 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..0e6c76dc5c
--- /dev/null
+++ b/netwerk/dns/NativeDNSResolverOverrideChild.cpp
@@ -0,0 +1,41 @@
+/* -*- 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::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..5b24cb3dcd
--- /dev/null
+++ b/netwerk/dns/NativeDNSResolverOverrideChild.h
@@ -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/. */
+
+#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 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..75fab125df
--- /dev/null
+++ b/netwerk/dns/NativeDNSResolverOverrideParent.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 "NativeDNSResolverOverrideParent.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "nsIOService.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) {
+ PRNetAddr tempAddr;
+ // Unfortunately, PR_StringToNetAddr does not properly initialize
+ // the output buffer in the case of IPv6 input. See bug 223145.
+ memset(&tempAddr, 0, sizeof(PRNetAddr));
+
+ if (PR_StringToNetAddr(nsCString(aIPLiteral).get(), &tempAddr) !=
+ PR_SUCCESS) {
+ 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::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..f6429c7c54
--- /dev/null
+++ b/netwerk/dns/PDNSRequest.ipdl
@@ -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/. */
+
+include protocol PNecko;
+include protocol PSocketProcess;
+
+include PDNSRequestParams;
+
+include "mozilla/net/NeckoMessageUtils.h";
+
+using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+
+namespace mozilla {
+namespace net {
+
+async refcounted 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,
+ uint16_t type, OriginAttributes originAttributes,
+ uint32_t 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..1f52e7f39b
--- /dev/null
+++ b/netwerk/dns/PDNSRequestParams.ipdlh
@@ -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 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 NetAddr from "mozilla/net/DNS.h";
+using mozilla::net::IPCTypeRecord from "mozilla/net/DNSByTypeRecord.h";
+
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// DNS IPDL structs
+//-----------------------------------------------------------------------------
+
+struct DNSRecord
+{
+ nsCString canonicalName;
+ NetAddr[] addrs;
+ double trrFetchDuration;
+ double trrFetchDurationNetworkOnly;
+ bool isTRR;
+ uint32_t effectiveTRRMode;
+};
+
+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..1737e61dd2
--- /dev/null
+++ b/netwerk/dns/PNativeDNSResolverOverride.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 {
+
+async refcounted protocol PNativeDNSResolverOverride
+{
+ manager PSocketProcess;
+
+child:
+ async __delete__();
+ async AddIPOverride(nsCString aHost, nsCString aIPLiteral);
+ 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..02509b76ec
--- /dev/null
+++ b/netwerk/dns/PTRRService.ipdl
@@ -0,0 +1,27 @@
+/* -*- 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 PSMIPCTypes;
+
+namespace mozilla {
+namespace net {
+
+async refcounted protocol PTRRService
+{
+ manager PSocketProcess;
+
+child:
+ async __delete__();
+ async UpdatePlatformDNSInformation(nsCString[] aSuffixList);
+ async UpdateParentalControlEnabled(bool aEnabled);
+ async ClearDNSCache(bool aTrrToo);
+ async SetDetectedTrrURI(nsCString aURI);
+};
+
+} //namespace net
+} //namespace mozilla
diff --git a/netwerk/dns/PublicSuffixList.jsm b/netwerk/dns/PublicSuffixList.jsm
new file mode 100644
index 0000000000..c6988c0b8b
--- /dev/null
+++ b/netwerk/dns/PublicSuffixList.jsm
@@ -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/. */
+"use strict";
+
+const { RemoteSettings } = ChromeUtils.import(
+ "resource://services-settings/remote-settings.js"
+);
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const FileUtils = ChromeUtils.import("resource://gre/modules/FileUtils.jsm")
+ .FileUtils;
+
+const EXPORTED_SYMBOLS = ["PublicSuffixList"];
+
+const RECORD_ID = "tld-dafsa";
+const SIGNAL = "public-suffix-list-updated";
+
+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.download(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.delete(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.download(changed[0]);
+ } catch (err) {
+ Cu.reportError(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..dde39fd25e
--- /dev/null
+++ b/netwerk/dns/TRR.cpp
@@ -0,0 +1,965 @@
+/* -*- 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 "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsHttpHandler.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIIOService.h"
+#include "nsIInputStream.h"
+#include "nsISupportsBase.h"
+#include "nsISupportsUtils.h"
+#include "nsITimedChannel.h"
+#include "nsIUploadChannel2.h"
+#include "nsIURIMutator.h"
+#include "nsNetUtil.h"
+#include "nsStringStream.h"
+#include "nsThreadUtils.h"
+#include "nsURLHelper.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/SyncRunnable.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace net {
+
+#undef LOG
+#undef LOG_ENABLED
+extern mozilla::LazyLogModule gHostResolverLog;
+#define LOG(args) MOZ_LOG(gHostResolverLog, mozilla::LogLevel::Debug, args)
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHostResolverLog, mozilla::LogLevel::Debug)
+
+NS_IMPL_ISUPPORTS(TRR, nsIHttpPushListener, nsIInterfaceRequestor,
+ nsIStreamListener, nsIRunnable)
+
+NS_IMETHODIMP
+TRR::Notify(nsITimer* aTimer) {
+ if (aTimer == mTimeout) {
+ mTimeout = nullptr;
+ Cancel();
+ } else {
+ MOZ_CRASH("Unknown timer");
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRR::Run() {
+ MOZ_ASSERT_IF(XRE_IsParentProcess() && gTRRService,
+ NS_IsMainThread() || gTRRService->IsOnTRRThread());
+ MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
+
+ if ((gTRRService == nullptr) || NS_FAILED(SendHTTPRequest())) {
+ RecordReason(nsHostRecord::TRR_SEND_FAILED);
+ FailData(NS_ERROR_FAILURE);
+ // The dtor will now be run
+ }
+ return NS_OK;
+}
+
+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;
+ }
+}
+
+nsresult TRR::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 = GetMainThreadEventTarget();
+ if (main) {
+ // Forward to the main thread synchronously.
+ SyncRunnable::DispatchToThread(
+ main, new SyncRunnable(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);
+}
+
+nsresult TRR::SendHTTPRequest() {
+ // This is essentially the "run" method - created from nsHostResolver
+
+ 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 (((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 (UseDefaultServer() &&
+ gTRRService->IsTemporarilyBlocked(mHost, mOriginSuffix, mPB, true)) {
+ if (mType == TRRTYPE_A) {
+ // count only blocklist for A records to avoid double counts
+ Telemetry::Accumulate(Telemetry::DNS_TRR_BLACKLISTED2,
+ TRRService::AutoDetectedKey(), true);
+ }
+
+ RecordReason(nsHostRecord::TRR_HOST_BLOCKED_TEMPORARY);
+ // not really an error but no TRR is issued
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (gTRRService->IsExcludedFromTRR(mHost)) {
+ RecordReason(nsHostRecord::TRR_EXCLUDED);
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ if (UseDefaultServer() && (mType == TRRTYPE_A)) {
+ Telemetry::Accumulate(Telemetry::DNS_TRR_BLACKLISTED2,
+ TRRService::AutoDetectedKey(), false);
+ }
+ }
+
+ bool useGet = StaticPrefs::network_trr_useGET();
+ nsAutoCString body;
+ nsCOMPtr<nsIURI> dnsURI;
+ bool disableECS = StaticPrefs::network_trr_disable_ECS();
+ nsresult rv;
+
+ LOG(("TRR::SendHTTPRequest resolve %s type %u\n", mHost.get(), mType));
+
+ if (useGet) {
+ nsAutoCString tmp;
+ rv = DNSPacket::EncodeRequest(tmp, mHost, mType, disableECS);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* For GET requests, the outgoing packet needs to be Base64url-encoded and
+ then appended to the end of the URI. */
+ rv = Base64URLEncode(tmp.Length(),
+ reinterpret_cast<const unsigned char*>(tmp.get()),
+ Base64URLEncodePaddingPolicy::Omit, body);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uri;
+ if (UseDefaultServer()) {
+ gTRRService->GetURI(uri);
+ } else {
+ uri = mRec->mTrrServer;
+ }
+
+ rv = NS_NewURI(getter_AddRefs(dnsURI), uri);
+ if (NS_FAILED(rv)) {
+ LOG(("TRR:SendHTTPRequest: NewURI failed!\n"));
+ return 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(body);
+
+ rv = NS_MutateURI(dnsURI).SetQuery(query).Finalize(dnsURI);
+ LOG(("TRR::SendHTTPRequest GET dns=%s\n", body.get()));
+ } else {
+ rv = DNSPacket::EncodeRequest(body, mHost, mType, disableECS);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uri;
+ if (UseDefaultServer()) {
+ gTRRService->GetURI(uri);
+ } else {
+ uri = mRec->mTrrServer;
+ }
+ rv = NS_NewURI(getter_AddRefs(dnsURI), uri);
+ }
+ if (NS_FAILED(rv)) {
+ LOG(("TRR:SendHTTPRequest: NewURI failed!\n"));
+ return rv;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = CreateChannelHelper(dnsURI, getter_AddRefs(channel));
+ if (NS_FAILED(rv) || !channel) {
+ LOG(("TRR:SendHTTPRequest: NewChannel failed!\n"));
+ return rv;
+ }
+
+ channel->SetLoadFlags(
+ nsIRequest::LOAD_ANONYMOUS | nsIRequest::INHIBIT_CACHING |
+ nsIRequest::LOAD_BYPASS_CACHE | nsIChannel::LOAD_BYPASS_URL_CLASSIFIER);
+ 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);
+
+ rv = httpChannel->SetRequestHeader("Accept"_ns, "application/dns-message"_ns,
+ false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString cred;
+ if (UseDefaultServer()) {
+ gTRRService->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 (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,
+ "application/dns-message"_ns,
+ streamLength, "POST"_ns, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = SetupTRRServiceChannelInternal(httpChannel, useGet);
+ 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->mTRRUsed = true;
+ }
+
+ NS_NewTimerWithCallback(getter_AddRefs(mTimeout), this,
+ gTRRService->GetRequestTimeout(),
+ nsITimer::TYPE_ONE_SHOT);
+
+ mChannel = channel;
+ return NS_OK;
+}
+
+// static
+nsresult TRR::SetupTRRServiceChannelInternal(nsIHttpChannel* aChannel,
+ bool aUseGet) {
+ 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("application/dns-message"_ns))) {
+ 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);
+ }
+
+ PRNetAddr tempAddr;
+ if (NS_FAILED(DohDecodeQuery(query, mHost, mType)) ||
+ (PR_StringToNetAddr(mHost.get(), &tempAddr) == PR_SUCCESS)) { // 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 (gTRRService->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 = 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(nsHostRecord::TRR_IS_OFFLINE);
+ }
+
+ switch (status) {
+ case NS_ERROR_UNKNOWN_HOST:
+ RecordReason(nsHostRecord::TRR_CHANNEL_DNS_FAIL);
+ break;
+ case NS_ERROR_OFFLINE:
+ RecordReason(nsHostRecord::TRR_IS_OFFLINE);
+ break;
+ case NS_ERROR_NET_RESET:
+ RecordReason(nsHostRecord::TRR_NET_RESET);
+ break;
+ case NS_ERROR_NET_TIMEOUT:
+ RecordReason(nsHostRecord::TRR_NET_TIMEOUT);
+ break;
+ case NS_ERROR_PROXY_CONNECTION_REFUSED:
+ RecordReason(nsHostRecord::TRR_NET_REFUSED);
+ break;
+ case NS_ERROR_NET_INTERRUPT:
+ RecordReason(nsHostRecord::TRR_NET_INTERRUPT);
+ break;
+ case NS_ERROR_NET_INADEQUATE_SECURITY:
+ RecordReason(nsHostRecord::TRR_NET_INADEQ_SEQURITY);
+ break;
+ default:
+ RecordReason(nsHostRecord::TRR_UNKNOWN_CHANNEL_FAILURE);
+ }
+ }
+
+ return NS_OK;
+}
+
+void TRR::SaveAdditionalRecords(
+ const nsClassHashtable<nsCStringHashKey, DOHresp>& aRecords) {
+ if (!mRec) {
+ return;
+ }
+ nsresult rv;
+ for (auto iter = aRecords.ConstIter(); !iter.Done(); iter.Next()) {
+ if (iter.Data() && iter.Data()->mAddresses.IsEmpty()) {
+ // no point in adding empty records.
+ continue;
+ }
+ RefPtr<nsHostRecord> hostRecord;
+ rv = mHostResolver->GetHostRecord(
+ iter.Key(), 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(iter.Key()).get()));
+ continue;
+ }
+ RefPtr<AddrInfo> ai(new AddrInfo(iter.Key(), TRRTYPE_A,
+ std::move(iter.Data()->mAddresses),
+ iter.Data()->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->mResolving++;
+ hostRecord->mEffectiveTRRMode = mRec->mEffectiveTRRMode;
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(hostRecord);
+ addrRec->mTrrStart = TimeStamp::Now();
+ LOG(("Completing lookup for additional: %s", nsCString(iter.Key()).get()));
+ (void)mHostResolver->CompleteLookup(hostRecord, NS_OK, ai, mPB,
+ mOriginSuffix, AddrHostRecord::TRR_OK);
+ }
+}
+
+void TRR::StoreIPHintAsDNSRecord(const struct SVCB& aSVCBRecord) {
+ LOG(("TRR::StoreIPHintAsDNSRecord [%p] [%s]", this,
+ aSVCBRecord.mSvcDomainName.get()));
+ CopyableTArray<NetAddr> addresses;
+ aSVCBRecord.GetIPHints(addresses);
+ if (addresses.IsEmpty()) {
+ return;
+ }
+
+ RefPtr<nsHostRecord> hostRecord;
+ nsresult rv = mHostResolver->GetHostRecord(
+ aSVCBRecord.mSvcDomainName, EmptyCString(),
+ nsIDNSService::RESOLVE_TYPE_DEFAULT,
+ mRec->flags | nsHostResolver::RES_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);
+
+ uint32_t ttl = AddrInfo::NO_TTL_DATA;
+ RefPtr<AddrInfo> ai(new AddrInfo(aSVCBRecord.mSvcDomainName, TRRTYPE_A,
+ std::move(addresses), ttl));
+
+ // 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 = mRec->mEffectiveTRRMode;
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(hostRecord);
+ addrRec->mTrrStart = TimeStamp::Now();
+
+ (void)mHostResolver->CompleteLookup(hostRecord, NS_OK, ai, mPB, mOriginSuffix,
+ AddrHostRecord::TRR_OK);
+}
+
+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, 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;
+ }
+ (void)mHostResolver->CompleteLookup(mRec, NS_OK, ai, mPB, mOriginSuffix,
+ mTRRSkippedReason);
+ mHostResolver = nullptr;
+ mRec = nullptr;
+ } else {
+ (void)mHostResolver->CompleteLookupByType(mRec, NS_OK, mResult, 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(nsHostRecord::TRR_FAILED);
+
+ if (mType == TRRTYPE_TXT || mType == TRRTYPE_HTTPSSVC) {
+ TypeRecordResultType empty(Nothing{});
+ (void)mHostResolver->CompleteLookupByType(mRec, error, empty, 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, mType, std::move(noAddresses));
+
+ (void)mHostResolver->CompleteLookup(mRec, error, ai, mPB, mOriginSuffix,
+ mTRRSkippedReason);
+ }
+
+ mHostResolver = nullptr;
+ mRec = nullptr;
+ return NS_OK;
+}
+
+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 = mPacket.Decode(
+ cname, mType, mCname, StaticPrefs::network_trr_allow_rfc1918(),
+ mTRRSkippedReason, mDNS, mResult, additionalRecords, mTTL);
+ if (NS_FAILED(rv)) {
+ LOG(("TRR::On200Response DohDecode %x\n", (int)rv));
+ }
+ }
+
+ // restore mCname as DohDecode() change it
+ mCname = cname;
+ if (NS_SUCCEEDED(rv) && !mDNS.mAddresses.IsEmpty()) {
+ ReturnData(aChannel);
+ 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 (!gTRRService) {
+ return NS_ERROR_FAILURE;
+ }
+ return gTRRService->DispatchTRRRequest(trr);
+}
+
+nsresult TRR::On200Response(nsIChannel* aChannel) {
+ // decode body and create an AddrInfo struct for the response
+ nsClassHashtable<nsCStringHashKey, DOHresp> additionalRecords;
+ nsresult rv = mPacket.Decode(
+ mHost, mType, mCname, StaticPrefs::network_trr_allow_rfc1918(),
+ mTRRSkippedReason, mDNS, mResult, additionalRecords, mTTL);
+
+ if (NS_FAILED(rv)) {
+ LOG(("TRR::On200Response DohDecode %x\n", (int)rv));
+ RecordReason(nsHostRecord::TRR_DECODE_FAILED);
+ return rv;
+ }
+ 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);
+}
+
+static void 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()));
+}
+
+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);
+
+ {
+ // Cancel the timer since we don't need it anymore.
+ nsCOMPtr<nsITimer> timer;
+ mTimeout.swap(timer);
+ if (timer) {
+ timer->Cancel();
+ }
+ }
+
+ if (UseDefaultServer()) {
+ // Bad content is still considered "okay" if the HTTP response is okay
+ gTRRService->TRRIsOkay(NS_SUCCEEDED(aStatusCode) ? TRRService::OKAY_NORMAL
+ : TRRService::OKAY_BAD);
+ }
+
+ 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.LowerCaseEqualsLiteral("application/dns-message")) {
+ 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(nsHostRecord::TRR_OK);
+ RecordProcessingTime(channel);
+ return rv;
+ }
+ } else {
+ RecordReason(nsHostRecord::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 =
+ mPacket.OnDataAvailable(aRequest, aInputStream, aOffset, aCount);
+ if (NS_FAILED(rv)) {
+ LOG(("TRR::OnDataAvailable:%d fail\n", __LINE__));
+ mFailed = true;
+ return rv;
+ }
+ return NS_OK;
+}
+
+class ProxyCancel : public Runnable {
+ public:
+ explicit ProxyCancel(TRR* aTRR) : Runnable("proxyTrrCancel"), mTRR(aTRR) {}
+
+ NS_IMETHOD Run() override {
+ mTRR->Cancel();
+ mTRR = nullptr;
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<TRR> mTRR;
+};
+
+void TRR::Cancel() {
+ RefPtr<TRRServiceChannel> trrServiceChannel = do_QueryObject(mChannel);
+ if (trrServiceChannel && !XRE_IsSocketProcess()) {
+ if (gTRRService) {
+ nsCOMPtr<nsIThread> thread = gTRRService->TRRThread();
+ if (thread && !thread->IsOnCurrentThread()) {
+ nsCOMPtr<nsIRunnable> r = new ProxyCancel(this);
+ thread->Dispatch(r.forget());
+ return;
+ }
+ }
+ } else {
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(new ProxyCancel(this));
+ return;
+ }
+ }
+
+ if (mChannel) {
+ RecordReason(nsHostRecord::TRR_TIMEOUT);
+ LOG(("TRR: %p canceling Channel %p %s %d\n", this, mChannel.get(),
+ mHost.get(), mType));
+ mChannel->Cancel(NS_ERROR_ABORT);
+ if (UseDefaultServer()) {
+ gTRRService->TRRIsOkay(TRRService::OKAY_TIMEOUT);
+ }
+ }
+}
+
+bool TRR::UseDefaultServer() { return !mRec || mRec->mTrrServer.IsEmpty(); }
+
+#undef LOG
+
+// namespace
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/TRR.h b/netwerk/dns/TRR.h
new file mode 100644
index 0000000000..489cca08b8
--- /dev/null
+++ b/netwerk/dns/TRR.h
@@ -0,0 +1,161 @@
+/* -*- 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 "nsHostResolver.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "DNSPacket.h"
+
+namespace mozilla {
+namespace net {
+
+class TRRService;
+class TRRServiceChannel;
+extern TRRService* gTRRService;
+
+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)
+ : 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
+ explicit 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
+ explicit TRR(AHostResolver* aResolver, bool aPB)
+ : mozilla::Runnable("TRR"),
+ mHostResolver(aResolver),
+ mType(TRRTYPE_A),
+ mPB(aPB) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess(),
+ "TRR must be in parent or socket process");
+ }
+
+ // to verify a domain
+ explicit TRR(AHostResolver* aResolver, nsACString& aHost, enum TrrType aType,
+ const nsACString& aOriginSuffix, bool aPB)
+ : mozilla::Runnable("TRR"),
+ mHost(aHost),
+ mRec(nullptr),
+ mHostResolver(aResolver),
+ mType(aType),
+ mPB(aPB),
+ mOriginSuffix(aOriginSuffix) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess(),
+ "TRR must be in parent or socket process");
+ }
+
+ NS_IMETHOD Run() override;
+ void Cancel();
+ enum TrrType Type() { return mType; }
+ nsCString mHost;
+ RefPtr<nsHostRecord> mRec;
+ RefPtr<AHostResolver> mHostResolver;
+
+ private:
+ ~TRR() = default;
+ 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 blacklisted, 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 UseDefaultServer();
+ void SaveAdditionalRecords(
+ const nsClassHashtable<nsCStringHashKey, DOHresp>& aRecords);
+
+ nsresult CreateChannelHelper(nsIURI* aUri, nsIChannel** aResult);
+
+ friend class TRRServiceChannel;
+ static nsresult SetupTRRServiceChannelInternal(nsIHttpChannel* aChannel,
+ bool aUseGet);
+
+ void StoreIPHintAsDNSRecord(const struct SVCB& aSVCBRecord);
+
+ nsCOMPtr<nsIChannel> mChannel;
+ enum TrrType mType;
+ DNSPacket mPacket;
+ bool mFailed = false;
+ bool mPB;
+ DOHresp mDNS;
+ nsCOMPtr<nsITimer> mTimeout;
+ nsCString mCname;
+ uint32_t mCnameLoop = kCnameChaseMax; // loop detection counter
+
+ uint32_t mTTL = UINT32_MAX;
+ TypeRecordResultType mResult = mozilla::AsVariant(Nothing());
+
+ nsHostRecord::TRRSkippedReason mTRRSkippedReason = nsHostRecord::TRR_UNSET;
+ void RecordReason(nsHostRecord::TRRSkippedReason reason) {
+ if (mTRRSkippedReason == nsHostRecord::TRR_UNSET) {
+ mTRRSkippedReason = reason;
+ }
+ }
+
+ // keep a copy of the originSuffix for the cases where mRec == nullptr */
+ const nsCString mOriginSuffix;
+};
+
+} // 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..989780aa33
--- /dev/null
+++ b/netwerk/dns/TRRQuery.cpp
@@ -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/. */
+
+#include "TRRQuery.h"
+#include "TRR.h"
+
+namespace mozilla {
+namespace net {
+
+#undef LOG
+extern mozilla::LazyLogModule gHostResolverLog;
+#define LOG(args) MOZ_LOG(gHostResolverLog, mozilla::LogLevel::Debug, args)
+
+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() {
+ MutexAutoLock trrlock(mTrrLock);
+ if (mTrrA) {
+ mTrrA->Cancel();
+ mTrrA = nullptr;
+ }
+ if (mTrrAAAA) {
+ mTrrAAAA->Cancel();
+ mTrrAAAA = nullptr;
+ }
+ if (mTrrByType) {
+ mTrrByType->Cancel();
+ mTrrByType = nullptr;
+ }
+}
+
+nsresult TRRQuery::DispatchLookup(TRR* pushedTRR) {
+ mTrrStart = TimeStamp::Now();
+
+ RefPtr<AddrHostRecord> addrRec;
+ RefPtr<TypeHostRecord> typeRec;
+
+ if (mRecord->IsAddrRecord()) {
+ addrRec = do_QueryObject(mRecord);
+ MOZ_ASSERT(addrRec);
+ } else {
+ typeRec = do_QueryObject(mRecord);
+ MOZ_ASSERT(typeRec);
+ }
+
+ mTrrStart = TimeStamp::Now();
+ bool madeQuery = false;
+
+ if (addrRec) {
+ mTrrAUsed = INIT;
+ mTrrAAAAUsed = INIT;
+
+ // If asking for AF_UNSPEC, issue both A and AAAA.
+ // If asking for AF_INET6 or AF_INET, do only that single type
+ enum TrrType rectype = (mRecord->af == AF_INET6) ? TRRTYPE_AAAA : TRRTYPE_A;
+
+ if (pushedTRR) {
+ rectype = pushedTRR->Type();
+ }
+ bool sendAgain;
+
+ do {
+ sendAgain = false;
+ if ((TRRTYPE_AAAA == rectype) && gTRRService &&
+ (gTRRService->DisableIPv6() ||
+ (StaticPrefs::network_trr_skip_AAAA_when_not_supported() &&
+ mHostResolver->GetNCS() &&
+ mHostResolver->GetNCS()->GetIPv6() ==
+ nsINetworkConnectivityService::NOT_AVAILABLE))) {
+ break;
+ }
+ LOG(("TRR Resolve %s type %d\n", addrRec->host.get(), (int)rectype));
+ RefPtr<TRR> trr;
+ trr = pushedTRR ? pushedTRR : new TRR(this, mRecord, rectype);
+ if (pushedTRR || NS_SUCCEEDED(gTRRService->DispatchTRRRequest(trr))) {
+ MutexAutoLock trrlock(mTrrLock);
+ 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);
+ }
+ madeQuery = true;
+ if (!pushedTRR && (mRecord->af == AF_UNSPEC) &&
+ (rectype == TRRTYPE_A)) {
+ rectype = TRRTYPE_AAAA;
+ sendAgain = true;
+ }
+ }
+ } while (sendAgain);
+ } else {
+ typeRec->mStart = TimeStamp::Now();
+ 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;
+ trr = pushedTRR ? pushedTRR : new TRR(this, mRecord, rectype);
+ RefPtr<TRR> trrRequest = trr;
+ if (pushedTRR || NS_SUCCEEDED(gTRRService->DispatchTRRRequest(trr))) {
+ MutexAutoLock trrlock(mTrrLock);
+ MOZ_ASSERT(!mTrrByType);
+ mTrrByType = trr;
+ madeQuery = true;
+ }
+ }
+
+ return madeQuery ? NS_OK : NS_ERROR_UNKNOWN_HOST;
+}
+
+AHostResolver::LookupStatus TRRQuery::CompleteLookup(
+ nsHostRecord* rec, nsresult status, AddrInfo* aNewRRSet, bool pb,
+ const nsACString& aOriginsuffix, nsHostRecord::TRRSkippedReason aReason) {
+ if (rec != mRecord) {
+ return mHostResolver->CompleteLookup(rec, status, aNewRRSet, pb,
+ aOriginsuffix, aReason);
+ }
+
+ RefPtr<AddrInfo> newRRSet(aNewRRSet);
+ bool pendingARequest = false;
+ bool pendingAAAARequest = false;
+ {
+ MutexAutoLock trrlock(mTrrLock);
+ if (newRRSet->IsTRR() == TRRTYPE_A) {
+ MOZ_ASSERT(mTrrA);
+ mTRRAFailReason = aReason;
+ mTrrA = nullptr;
+ mTrrAUsed = NS_SUCCEEDED(status) ? OK : FAILED;
+ } else if (newRRSet->IsTRR() == TRRTYPE_AAAA) {
+ MOZ_ASSERT(mTrrAAAA);
+ mTRRAAAAFailReason = aReason;
+ mTrrAAAA = nullptr;
+ mTrrAAAAUsed = NS_SUCCEEDED(status) ? OK : FAILED;
+ } else {
+ MOZ_ASSERT(0);
+ }
+ if (mTrrA) {
+ pendingARequest = true;
+ }
+ if (mTrrAAAA) {
+ pendingAAAARequest = true;
+ }
+ }
+
+ 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;
+ }
+ }
+
+ if (pendingARequest ||
+ pendingAAAARequest) { // There are other outstanding requests
+ mFirstTRRresult = status;
+ if (NS_FAILED(status)) {
+ return LOOKUP_OK; // wait for outstanding
+ }
+
+ // There's another TRR complete pending. Wait for it and keep
+ // this RRset around until then.
+ MOZ_ASSERT(!mFirstTRR && newRRSet);
+ mFirstTRR.swap(newRRSet); // autoPtr.swap()
+ MOZ_ASSERT(mFirstTRR && !newRRSet);
+
+ if (StaticPrefs::network_trr_wait_for_A_and_AAAA()) {
+ LOG(("CompleteLookup: waiting for all responses!\n"));
+ return LOOKUP_OK;
+ }
+
+ if (pendingARequest && !StaticPrefs::network_trr_early_AAAA()) {
+ // This is an early AAAA with a pending A response. Allowed
+ // only by pref.
+ LOG(("CompleteLookup: avoiding early use of TRR AAAA!\n"));
+ return LOOKUP_OK;
+ }
+
+ // we can do some callbacks with this partial result which requires
+ // a deep copy
+ newRRSet = mFirstTRR;
+
+ // Increment mResolving so we wait for the next resolve too.
+ rec->mResolving++;
+ } else {
+ // no more outstanding TRRs
+ // If mFirstTRR is set, merge those addresses into current set!
+ if (mFirstTRR) {
+ if (NS_SUCCEEDED(status)) {
+ LOG(("Merging responses"));
+ newRRSet = merge_rrset(newRRSet, mFirstTRR);
+ } else {
+ LOG(("Will use previous response"));
+ newRRSet.swap(mFirstTRR); // transfers
+ // We must use the status of the first response, otherwise we'll
+ // pass an error result to the consumers.
+ status = mFirstTRRresult;
+ }
+ mFirstTRR = nullptr;
+ } else {
+ if (NS_FAILED(status) && status != NS_ERROR_DEFINITIVE_UNKNOWN_HOST &&
+ mFirstTRRresult == NS_ERROR_DEFINITIVE_UNKNOWN_HOST) {
+ status = NS_ERROR_DEFINITIVE_UNKNOWN_HOST;
+ }
+ }
+
+ if (mTRRSuccess && mHostResolver->GetNCS() &&
+ (mHostResolver->GetNCS()->GetNAT64() ==
+ nsINetworkConnectivityService::OK) &&
+ newRRSet) {
+ newRRSet = mHostResolver->GetNCS()->MapNAT64IPs(newRRSet);
+ }
+ }
+
+ if (mTrrAUsed == OK) {
+ AccumulateCategoricalKeyed(
+ TRRService::AutoDetectedKey(),
+ Telemetry::LABELS_DNS_LOOKUP_DISPOSITION2::trrAOK);
+ } else if (mTrrAUsed == FAILED) {
+ AccumulateCategoricalKeyed(
+ TRRService::AutoDetectedKey(),
+ Telemetry::LABELS_DNS_LOOKUP_DISPOSITION2::trrAFail);
+ }
+
+ if (mTrrAAAAUsed == OK) {
+ AccumulateCategoricalKeyed(
+ TRRService::AutoDetectedKey(),
+ Telemetry::LABELS_DNS_LOOKUP_DISPOSITION2::trrAAAAOK);
+ } else if (mTrrAAAAUsed == FAILED) {
+ AccumulateCategoricalKeyed(
+ TRRService::AutoDetectedKey(),
+ Telemetry::LABELS_DNS_LOOKUP_DISPOSITION2::trrAAAAFail);
+ }
+
+ return mHostResolver->CompleteLookup(rec, status, newRRSet, pb, aOriginsuffix,
+ aReason);
+}
+
+AHostResolver::LookupStatus TRRQuery::CompleteLookupByType(
+ nsHostRecord* rec, nsresult status,
+ mozilla::net::TypeRecordResultType& aResult, uint32_t aTtl, bool pb) {
+ if (rec == mRecord) {
+ MutexAutoLock trrlock(mTrrLock);
+ mTrrByType = nullptr;
+ }
+ return mHostResolver->CompleteLookupByType(rec, status, aResult, aTtl, pb);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/TRRQuery.h b/netwerk/dns/TRRQuery.h
new file mode 100644
index 0000000000..d99d16d5fc
--- /dev/null
+++ b/netwerk/dns/TRRQuery.h
@@ -0,0 +1,90 @@
+/* -*- 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
+
+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();
+
+ enum TRRState { INIT, STARTED, OK, FAILED };
+ TRRState mTrrAUsed = INIT;
+ TRRState mTrrAAAAUsed = INIT;
+
+ AddrHostRecord::TRRSkippedReason mTRRAFailReason = AddrHostRecord::TRR_UNSET;
+ AddrHostRecord::TRRSkippedReason mTRRAAAAFailReason =
+ AddrHostRecord::TRR_UNSET;
+
+ virtual LookupStatus CompleteLookup(
+ nsHostRecord*, nsresult, mozilla::net::AddrInfo*, bool pb,
+ const nsACString& aOriginsuffix,
+ nsHostRecord::TRRSkippedReason aReason) override;
+ virtual LookupStatus CompleteLookupByType(
+ nsHostRecord*, nsresult, mozilla::net::TypeRecordResultType& aResult,
+ uint32_t aTtl, bool pb) override;
+ virtual nsresult GetHostRecord(const nsACString& host,
+ const nsACString& aTrrServer, uint16_t type,
+ uint16_t 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:
+ ~TRRQuery() = default;
+ RefPtr<nsHostResolver> mHostResolver;
+ RefPtr<nsHostRecord> mRecord;
+
+ Mutex mTrrLock; // lock when accessing the mTrrA[AAA] pointers
+ RefPtr<mozilla::net::TRR> mTrrA;
+ RefPtr<mozilla::net::TRR> mTrrAAAA;
+ RefPtr<mozilla::net::TRR> mTrrByType;
+
+ uint8_t mTRRSuccess = 0; // number of successful TRR responses
+
+ mozilla::TimeDuration mTrrDuration;
+ mozilla::TimeStamp mTrrStart;
+
+ RefPtr<mozilla::net::AddrInfo> mFirstTRR; // partial TRR storage
+ nsresult mFirstTRRresult = 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..601069dbb2
--- /dev/null
+++ b/netwerk/dns/TRRService.cpp
@@ -0,0 +1,944 @@
+/* -*- 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 "nsAppDirectoryServiceDefs.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceUtils.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/Tokenizer.h"
+#include "mozilla/net/rust_helper.h"
+
+#if defined(XP_WIN) && !defined(__MINGW32__)
+# include <shlobj_core.h> // for SHGetSpecialFolderPathA
+#endif // XP_WIN
+
+static const char kOpenCaptivePortalLoginEvent[] = "captive-portal-login";
+static const char kClearPrivateData[] = "clear-private-data";
+static const char kPurge[] = "browser:purge-session-history";
+static const char kDisableIpv6Pref[] = "network.dns.disableIPv6";
+static const char kRolloutURIPref[] = "doh-rollout.uri";
+static const char kRolloutModePref[] = "doh-rollout.mode";
+
+#define TRR_PREF_PREFIX "network.trr."
+#define TRR_PREF(x) TRR_PREF_PREFIX x
+
+namespace mozilla {
+namespace net {
+
+#undef LOG
+extern mozilla::LazyLogModule gHostResolverLog;
+#define LOG(args) MOZ_LOG(gHostResolverLog, mozilla::LogLevel::Debug, args)
+
+TRRService* gTRRService = nullptr;
+StaticRefPtr<nsIThread> sTRRBackgroundThread;
+static Atomic<TRRService*> sTRRServicePtr;
+
+NS_IMPL_ISUPPORTS(TRRService, nsIObserver, nsISupportsWeakReference)
+
+TRRService::TRRService()
+ : mInitialized(false),
+ mBlocklistDurationSeconds(60),
+ mLock("trrservice"),
+ mConfirmationNS("example.com"_ns),
+ mCaptiveIsPassed(false),
+ mTRRBLStorage("DataMutex::TRRBlocklist"),
+ mConfirmationState(CONFIRM_INIT),
+ mRetryConfirmInterval(125),
+ mTRRFailures(0),
+ mParentalControlEnabled(false) {
+ MOZ_ASSERT(NS_IsMainThread(), "wrong thread");
+}
+
+// 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;
+}
+
+constexpr auto kTRRIsAutoDetectedKey = "(auto-detected)"_ns;
+constexpr auto kTRRNotAutoDetectedKey = "(default)"_ns;
+// static
+const nsCString& TRRService::AutoDetectedKey() {
+ if (gTRRService && gTRRService->IsUsingAutoDetectedURL()) {
+ return kTRRIsAutoDetectedKey.AsString();
+ }
+
+ return kTRRNotAutoDetectedKey.AsString();
+}
+
+static void RemoveTRRBlocklistFile() {
+ MOZ_ASSERT(NS_IsMainThread(), "Getting the profile dir on the main thread");
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv =
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ rv = file->AppendNative("TRRBlacklist.txt"_ns);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Dispatch an async task that removes the blocklist file from the profile.
+ rv = NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction("RemoveTRRBlocklistFile::Remove",
+ [file] { file->Remove(false); }),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ Preferences::SetBool("network.trr.blocklist_cleanup_done", true);
+}
+
+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(kDisableIpv6Pref, this, true);
+ prefBranch->AddObserver(kRolloutURIPref, this, true);
+ prefBranch->AddObserver(kRolloutModePref, this, true);
+ }
+
+ gTRRService = this;
+ sTRRServicePtr = this;
+
+ ReadPrefs(nullptr);
+
+ if (XRE_IsParentProcess()) {
+ mCaptiveIsPassed = CheckCaptivePortalIsPassed();
+
+ mParentalControlEnabled = GetParentalControlEnabledInternal();
+
+ nsCOMPtr<nsINetworkLinkService> nls =
+ do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID);
+ if (nls) {
+ nsTArray<nsCString> suffixList;
+ nls->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;
+
+ if (!StaticPrefs::network_trr_blocklist_cleanup_done()) {
+ // Dispatch an idle task to the main thread that gets the profile dir
+ // then attempts to delete the blocklist file on a background thread.
+ Unused << NS_DispatchToMainThreadQueue(
+ NS_NewCancelableRunnableFunction("RemoveTRRBlocklistFile::GetDir",
+ [] { RemoveTRRBlocklistFile(); }),
+ EventQueuePriority::Idle);
+ }
+ }
+
+ 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) {
+ // If the user has set a custom URI then we don't want to override that.
+ if (mURIPrefHasUserValue) {
+ return;
+ }
+
+ mURISetByDetection = MaybeSetPrivateURI(aURI);
+}
+
+bool TRRService::Enabled(nsIRequest::TRRMode aMode) {
+ if (mMode == MODE_TRROFF) {
+ return false;
+ }
+ if (mConfirmationState == CONFIRM_INIT &&
+ (!StaticPrefs::network_trr_wait_for_portal() || mCaptiveIsPassed ||
+ (mMode == MODE_TRRONLY || aMode == nsIRequest::TRR_ONLY_MODE))) {
+ LOG(("TRRService::Enabled => CONFIRM_TRYING\n"));
+ mConfirmationState = CONFIRM_TRYING;
+ }
+
+ if (mConfirmationState == CONFIRM_TRYING) {
+ LOG(("TRRService::Enabled MaybeConfirm()\n"));
+ MaybeConfirm();
+ }
+
+ if (mConfirmationState != CONFIRM_OK) {
+ LOG(("TRRService::Enabled mConfirmationState=%d mCaptiveIsPassed=%d\n",
+ (int)mConfirmationState, (int)mCaptiveIsPassed));
+ }
+
+ return (mConfirmationState == CONFIRM_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);
+ ProcessURITemplate(newURI);
+
+ {
+ MutexAutoLock 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;
+ }
+ mPrivateURI = newURI;
+ }
+
+ // 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)) {
+ uint32_t 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, kRolloutURIPref)) {
+ OnTRRURIChange();
+ }
+ if (!name || !strcmp(name, TRR_PREF("credentials"))) {
+ MutexAutoLock lock(mLock);
+ Preferences::GetCString(TRR_PREF("credentials"), mPrivateCred);
+ }
+ if (!name || !strcmp(name, TRR_PREF("confirmationNS"))) {
+ MutexAutoLock lock(mLock);
+ nsAutoCString old(mConfirmationNS);
+ Preferences::GetCString(TRR_PREF("confirmationNS"), mConfirmationNS);
+ if (name && !old.IsEmpty() && !mConfirmationNS.Equals(old) &&
+ (mConfirmationState > CONFIRM_TRYING) &&
+ (mMode == MODE_TRRFIRST || mMode == MODE_TRRONLY)) {
+ LOG(("TRR::ReadPrefs: restart confirmationNS state\n"));
+ mConfirmationState = CONFIRM_TRYING;
+ MaybeConfirm_locked();
+ }
+ }
+ if (!name || !strcmp(name, TRR_PREF("bootstrapAddress"))) {
+ MutexAutoLock lock(mLock);
+ Preferences::GetCString(TRR_PREF("bootstrapAddress"), mBootstrapAddr);
+ clearEntireCache = true;
+ }
+ if (!name || !strcmp(name, TRR_PREF("blacklist-duration"))) {
+ // prefs is given in number of seconds
+ uint32_t secs;
+ if (NS_SUCCEEDED(
+ Preferences::GetUint(TRR_PREF("blacklist-duration"), &secs))) {
+ mBlocklistDurationSeconds = secs;
+ }
+ }
+ if (!name || !strcmp(name, kDisableIpv6Pref)) {
+ bool tmp;
+ if (NS_SUCCEEDED(Preferences::GetBool(kDisableIpv6Pref, &tmp))) {
+ mDisableIPv6 = tmp;
+ }
+ }
+ if (!name || !strcmp(name, TRR_PREF("excluded-domains")) ||
+ !strcmp(name, TRR_PREF("builtin-excluded-domains"))) {
+ MutexAutoLock lock(mLock);
+ mExcludedDomains.Clear();
+
+ auto parseExcludedDomains = [this](const char* aPrefName) {
+ nsAutoCString excludedDomains;
+ 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.PutEntry(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) {
+ MutexAutoLock lock(mLock);
+ for (const auto& item : aArray) {
+ LOG(("Adding %s from /etc/hosts to excluded domains", item.get()));
+ mEtcHostsDomains.PutEntry(item);
+ }
+}
+
+void TRRService::ReadEtcHostsFile() {
+ if (!StaticPrefs::network_trr_exclude_etc_hosts()) {
+ return;
+ }
+
+ auto readHostsTask = []() {
+ MOZ_ASSERT(!NS_IsMainThread(), "Must not run on the main thread");
+#if defined(XP_WIN) && !defined(__MINGW32__)
+ // 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");
+#elif defined(__MINGW32__)
+ nsAutoCString path("C:\\windows\\system32\\drivers\\etc\\hosts"_ns);
+#else
+ nsAutoCString path("/etc/hosts"_ns);
+#endif
+
+ LOG(("Reading hosts file at %s", path.get()));
+ rust_parse_etc_hosts(&path, [](const nsTArray<nsCString>* aArray) -> bool {
+ RefPtr<TRRService> service(sTRRServicePtr);
+ if (service && aArray) {
+ service->AddEtcHosts(*aArray);
+ }
+ return !!service;
+ });
+ };
+
+ Unused << NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction("Read /etc/hosts file", readHostsTask),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+}
+
+nsresult TRRService::GetURI(nsACString& result) {
+ MutexAutoLock lock(mLock);
+ result = mPrivateURI;
+ return NS_OK;
+}
+
+nsresult TRRService::GetCredentials(nsCString& result) {
+ MutexAutoLock lock(mLock);
+ result = mPrivateCred;
+ return NS_OK;
+}
+
+uint32_t TRRService::GetRequestTimeout() {
+ if (mMode == MODE_TRRONLY) {
+ return StaticPrefs::network_trr_request_timeout_mode_trronly_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"));
+ gTRRService = nullptr;
+}
+
+nsresult TRRService::DispatchTRRRequest(TRR* aTrrRequest) {
+ return DispatchTRRRequestInternal(aTrrRequest, true);
+}
+
+nsresult TRRService::DispatchTRRRequestInternal(TRR* aTrrRequest,
+ bool aWithLock) {
+ NS_ENSURE_ARG_POINTER(aTrrRequest);
+ if (!StaticPrefs::network_trr_fetch_off_main_thread() ||
+ XRE_IsSocketProcess()) {
+ return NS_DispatchToMainThread(aTrrRequest);
+ }
+
+ RefPtr<TRR> trr = aTrrRequest;
+ nsCOMPtr<nsIThread> thread = aWithLock ? TRRThread() : TRRThread_locked();
+ if (!thread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return thread->Dispatch(trr.forget());
+}
+
+already_AddRefed<nsIThread> TRRService::TRRThread() {
+ MutexAutoLock lock(mLock);
+ return TRRThread_locked();
+}
+
+already_AddRefed<nsIThread> TRRService::TRRThread_locked() {
+ RefPtr<nsIThread> thread = sTRRBackgroundThread;
+ return thread.forget();
+}
+
+bool TRRService::IsOnTRRThread() {
+ nsCOMPtr<nsIThread> thread;
+ {
+ MutexAutoLock 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)) {
+ ReadPrefs(NS_ConvertUTF16toUTF8(aData).get());
+
+ MutexAutoLock lock(mLock);
+ if (((mConfirmationState == CONFIRM_INIT) && !mBootstrapAddr.IsEmpty() &&
+ (mMode == MODE_TRRONLY)) ||
+ (mConfirmationState == CONFIRM_FAILED)) {
+ mConfirmationState = CONFIRM_TRYING;
+ MaybeConfirm_locked();
+ }
+
+ } else if (!strcmp(aTopic, kOpenCaptivePortalLoginEvent)) {
+ // We are in a captive portal
+ LOG(("TRRservice in captive portal\n"));
+ mCaptiveIsPassed = false;
+ } else if (!strcmp(aTopic, NS_CAPTIVE_PORTAL_CONNECTIVITY)) {
+ nsAutoCString data = NS_ConvertUTF16toUTF8(aData);
+ LOG(("TRRservice captive portal was %s\n", data.get()));
+
+ // We should avoid doing calling MaybeConfirm in response to a pref change
+ // unless the service is in a TRR=enabled mode.
+ if (mMode == MODE_TRRFIRST || mMode == MODE_TRRONLY) {
+ if (!mCaptiveIsPassed) {
+ if (mConfirmationState != CONFIRM_OK) {
+ mConfirmationState = CONFIRM_TRYING;
+ MaybeConfirm();
+ }
+ } else {
+ LOG(("TRRservice CP clear when already up!\n"));
+ }
+ 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) && mURISetByDetection) {
+ // If the URI was set via SetDetectedTrrURI we need to restore it to the
+ // default pref when a network link change occurs.
+ CheckURIPrefs();
+ }
+ } else if (!strcmp(aTopic, "xpcom-shutdown-threads")) {
+ if (sTRRBackgroundThread) {
+ nsCOMPtr<nsIThread> thread;
+ {
+ MutexAutoLock lock(mLock);
+ 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()) {
+ return;
+ }
+
+ MutexAutoLock lock(mLock);
+ mDNSSuffixDomains.Clear();
+ for (const auto& item : aSuffixList) {
+ LOG(("TRRService adding %s to suffix list", item.get()));
+ mDNSSuffixDomains.PutEntry(item);
+ }
+}
+
+void TRRService::MaybeConfirm() {
+ MutexAutoLock lock(mLock);
+ MaybeConfirm_locked();
+}
+
+void TRRService::MaybeConfirm_locked() {
+ mLock.AssertCurrentThreadOwns();
+ if (mMode == MODE_TRROFF || mConfirmer ||
+ mConfirmationState != CONFIRM_TRYING) {
+ LOG(
+ ("TRRService:MaybeConfirm mode=%d, mConfirmer=%p "
+ "mConfirmationState=%d\n",
+ (int)mMode, (void*)mConfirmer, (int)mConfirmationState));
+ return;
+ }
+
+ if (mConfirmationNS.Equals("skip") || mMode == MODE_TRRONLY) {
+ LOG(("TRRService starting confirmation test %s SKIPPED\n",
+ mPrivateURI.get()));
+ mConfirmationState = CONFIRM_OK;
+ } else {
+ LOG(("TRRService starting confirmation test %s %s\n", mPrivateURI.get(),
+ mConfirmationNS.get()));
+ mConfirmer = new TRR(this, mConfirmationNS, TRRTYPE_NS, ""_ns, false);
+ DispatchTRRRequestInternal(mConfirmer, false);
+ }
+}
+
+bool TRRService::MaybeBootstrap(const nsACString& aPossible,
+ nsACString& aResult) {
+ MutexAutoLock lock(mLock);
+ if (mMode == MODE_TRROFF || mBootstrapAddr.IsEmpty()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> url;
+ nsresult rv =
+ NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .Apply(NS_MutatorMethod(&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) {
+ if (!Enabled(nsIRequest::TRR_DEFAULT_MODE)) {
+ return true;
+ }
+
+ auto bl = mTRRBLStorage.Lock();
+ if (bl->IsEmpty()) {
+ return false;
+ }
+
+ // use a unified casing for the hashkey
+ nsAutoCString hashkey(aHost + aOriginSuffix);
+ if (int32_t* val = bl->GetValue(hashkey)) {
+ int32_t until = *val + mBlocklistDurationSeconds;
+ int32_t expire = NowInSeconds();
+ if (until > expire) {
+ LOG(("Host [%s] is TRR blocklisted\n", nsCString(aHost).get()));
+ return true;
+ }
+
+ // the blocklisted entry has expired
+ bl->Remove(hashkey);
+ }
+ 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 (mMode == 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.
+ MutexAutoLock lock(mLock);
+
+ return IsExcludedFromTRR_unlocked(aHost);
+}
+
+bool TRRService::IsExcludedFromTRR_unlocked(const nsACString& aHost) {
+ if (!NS_IsMainThread()) {
+ mLock.AssertCurrentThreadOwns();
+ }
+
+ 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.GetEntry(subdomain)) {
+ LOG(("Subdomain [%s] of host [%s] Is Excluded From TRR via pref\n",
+ subdomain.BeginReading(), aHost.BeginReading()));
+ return true;
+ }
+ if (mDNSSuffixDomains.GetEntry(subdomain)) {
+ LOG(("Subdomain [%s] of host [%s] Is Excluded From TRR via pref\n",
+ subdomain.BeginReading(), aHost.BeginReading()));
+ return true;
+ }
+ if (mEtcHostsDomains.GetEntry(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) {
+ LOG(("TRR blocklist %s\n", nsCString(aHost).get()));
+ nsAutoCString hashkey(aHost + aOriginSuffix);
+
+ // this overwrites any existing entry
+ {
+ auto bl = mTRRBLStorage.Lock();
+ bl->Put(hashkey, NowInSeconds());
+ }
+
+ if (aParentsToo) {
+ // 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);
+ DispatchTRRRequest(trr);
+ }
+ }
+}
+
+NS_IMETHODIMP
+TRRService::Notify(nsITimer* aTimer) {
+ if (aTimer == mRetryConfirmTimer) {
+ mRetryConfirmTimer = nullptr;
+ if (mConfirmationState == CONFIRM_FAILED) {
+ LOG(("TRRService retry NS of %s\n", mConfirmationNS.get()));
+ mConfirmationState = CONFIRM_TRYING;
+ MaybeConfirm();
+ }
+ } else {
+ MOZ_CRASH("Unknown timer");
+ }
+
+ return NS_OK;
+}
+
+void TRRService::TRRIsOkay(enum TrrOkay aReason) {
+ MOZ_ASSERT_IF(XRE_IsParentProcess(), NS_IsMainThread() || IsOnTRRThread());
+ MOZ_ASSERT_IF(XRE_IsSocketProcess(), NS_IsMainThread());
+
+ Telemetry::AccumulateCategoricalKeyed(
+ AutoDetectedKey(),
+ aReason == OKAY_NORMAL
+ ? Telemetry::LABELS_DNS_TRR_SUCCESS2::Fine
+ : (aReason == OKAY_TIMEOUT
+ ? Telemetry::LABELS_DNS_TRR_SUCCESS2::Timeout
+ : Telemetry::LABELS_DNS_TRR_SUCCESS2::Bad));
+ if (aReason == OKAY_NORMAL) {
+ mTRRFailures = 0;
+ } else if ((mMode == MODE_TRRFIRST) && (mConfirmationState == CONFIRM_OK)) {
+ // only count failures while in OK state
+ uint32_t fails = ++mTRRFailures;
+ if (fails >= StaticPrefs::network_trr_max_fails()) {
+ LOG(("TRRService goes FAILED after %u failures in a row\n", fails));
+ mConfirmationState = CONFIRM_FAILED;
+ // Fire off a timer and start re-trying the NS domain again
+ NS_NewTimerWithCallback(getter_AddRefs(mRetryConfirmTimer), this,
+ mRetryConfirmInterval, nsITimer::TYPE_ONE_SHOT);
+ mTRRFailures = 0; // clear it again
+ }
+ }
+}
+
+AHostResolver::LookupStatus TRRService::CompleteLookup(
+ nsHostRecord* rec, nsresult status, AddrInfo* aNewRRSet, bool pb,
+ const nsACString& aOriginSuffix, nsHostRecord::TRRSkippedReason aReason) {
+ // 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->IsTRR() == TRRTYPE_NS);
+
+#ifdef DEBUG
+ {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(!mConfirmer || (mConfirmationState == CONFIRM_TRYING));
+ }
+#endif
+ if (mConfirmationState == CONFIRM_TRYING) {
+ {
+ MutexAutoLock lock(mLock);
+ MOZ_ASSERT(mConfirmer);
+ mConfirmationState = NS_SUCCEEDED(status) ? CONFIRM_OK : CONFIRM_FAILED;
+ LOG(("TRRService finishing confirmation test %s %d %X\n",
+ mPrivateURI.get(), (int)mConfirmationState, (unsigned int)status));
+ mConfirmer = nullptr;
+
+ if (mConfirmationState == CONFIRM_OK) {
+ // A fresh confirmation means previous blocked entries might not
+ // be valid anymore.
+ auto bl = mTRRBLStorage.Lock();
+ bl->Clear();
+ }
+ }
+ if (mConfirmationState == CONFIRM_FAILED) {
+ // retry failed NS confirmation
+ NS_NewTimerWithCallback(getter_AddRefs(mRetryConfirmTimer), this,
+ mRetryConfirmInterval, nsITimer::TYPE_ONE_SHOT);
+ if (mRetryConfirmInterval < 64000) {
+ // double the interval up to this point
+ mRetryConfirmInterval *= 2;
+ }
+ } else {
+ if (mMode != MODE_TRRONLY) {
+ // don't accumulate trronly data here since trronly failures are
+ // handled above by trying again, so counting the successes here would
+ // skew the numbers
+ Telemetry::Accumulate(Telemetry::DNS_TRR_NS_VERFIFIED2,
+ TRRService::AutoDetectedKey(),
+ (mConfirmationState == CONFIRM_OK));
+ }
+ mRetryConfirmInterval = StaticPrefs::network_trr_retry_timeout_ms();
+ }
+ return LOOKUP_OK;
+ }
+
+ // when called without a host record, this is a domain name check response.
+ 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;
+}
+
+AHostResolver::LookupStatus TRRService::CompleteLookupByType(
+ nsHostRecord*, nsresult, mozilla::net::TypeRecordResultType& aResult,
+ uint32_t aTtl, bool aPb) {
+ return LOOKUP_OK;
+}
+
+#undef LOG
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/TRRService.h b/netwerk/dns/TRRService.h
new file mode 100644
index 0000000000..0a34696a03
--- /dev/null
+++ b/netwerk/dns/TRRService.h
@@ -0,0 +1,152 @@
+/* -*- 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"
+
+class nsDNSService;
+class nsIPrefBranch;
+class nsINetworkLinkService;
+class nsIObserverService;
+
+namespace mozilla {
+namespace net {
+
+class TRRServiceChild;
+class TRRServiceParent;
+
+class TRRService : public TRRServiceBase,
+ public nsIObserver,
+ public nsITimerCallback,
+ public nsSupportsWeakReference,
+ public AHostResolver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+
+ TRRService();
+ nsresult Init();
+ nsresult Start();
+ bool Enabled(nsIRequest::TRRMode aMode = nsIRequest::TRR_FIRST_MODE);
+ bool IsConfirmed() { return mConfirmationState == CONFIRM_OK; }
+
+ bool DisableIPv6() { return mDisableIPv6; }
+ nsresult GetURI(nsACString& result);
+ nsresult GetCredentials(nsCString& result);
+ uint32_t GetRequestTimeout();
+
+ LookupStatus CompleteLookup(nsHostRecord*, nsresult, mozilla::net::AddrInfo*,
+ bool pb, const nsACString& aOriginSuffix,
+ nsHostRecord::TRRSkippedReason aReason) override;
+ LookupStatus CompleteLookupByType(nsHostRecord*, nsresult,
+ mozilla::net::TypeRecordResultType&,
+ 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);
+ enum TrrOkay { OKAY_NORMAL = 0, OKAY_TIMEOUT = 1, OKAY_BAD = 2 };
+ void TRRIsOkay(enum TrrOkay aReason);
+ bool ParentalControlEnabled() const { return mParentalControlEnabled; }
+
+ nsresult DispatchTRRRequest(TRR* aTrrRequest);
+ already_AddRefed<nsIThread> TRRThread();
+ bool IsOnTRRThread();
+
+ bool IsUsingAutoDetectedURL() { return mURISetByDetection; }
+ static const nsCString& AutoDetectedKey();
+
+ 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);
+ void MaybeConfirm();
+ void MaybeConfirm_locked();
+ 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();
+
+ // 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;
+ Atomic<uint32_t, Relaxed> mBlocklistDurationSeconds;
+
+ Mutex mLock;
+
+ nsCString mPrivateCred; // main thread only
+ nsCString mConfirmationNS;
+ nsCString mBootstrapAddr;
+
+ Atomic<bool, Relaxed>
+ mCaptiveIsPassed; // set when captive portal check is passed
+ Atomic<bool, Relaxed> mDisableIPv6; // don't even try
+
+ // 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<nsDataHashtable<nsCStringHashKey, int32_t>> mTRRBLStorage;
+
+ // A set of domains that we should not use TRR for.
+ nsTHashtable<nsCStringHashKey> mExcludedDomains;
+ nsTHashtable<nsCStringHashKey> mDNSSuffixDomains;
+ nsTHashtable<nsCStringHashKey> mEtcHostsDomains;
+
+ enum ConfirmationState {
+ CONFIRM_INIT = 0,
+ CONFIRM_TRYING = 1,
+ CONFIRM_OK = 2,
+ CONFIRM_FAILED = 3
+ };
+ Atomic<ConfirmationState, Relaxed> mConfirmationState;
+ RefPtr<TRR> mConfirmer;
+ nsCOMPtr<nsITimer> mRetryConfirmTimer;
+ uint32_t mRetryConfirmInterval; // milliseconds until retry
+ Atomic<uint32_t, Relaxed> mTRRFailures;
+ bool mParentalControlEnabled;
+};
+
+extern TRRService* gTRRService;
+
+} // 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..122af1103e
--- /dev/null
+++ b/netwerk/dns/TRRServiceBase.cpp
@@ -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/. */
+
+#include "TRRServiceBase.h"
+
+#include "mozilla/Preferences.h"
+#include "nsIOService.h"
+
+namespace mozilla {
+namespace net {
+
+#undef LOG
+extern mozilla::LazyLogModule gHostResolverLog;
+#define LOG(args) MOZ_LOG(gHostResolverLog, mozilla::LogLevel::Debug, args)
+
+TRRServiceBase::TRRServiceBase() : mMode(0), mURISetByDetection(false) {}
+
+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;
+
+ // The user has set a custom URI so it takes precedence.
+ if (mURIPrefHasUserValue) {
+ 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(mURIPref);
+}
+
+// static
+uint32_t ModeFromPrefs() {
+ // 0 - off, 1 - reserved, 2 - TRR first, 3 - TRR only, 4 - reserved,
+ // 5 - explicit off
+
+ auto processPrefValue = [](uint32_t value) -> uint32_t {
+ if (value == MODE_RESERVED1 || value == MODE_RESERVED4 ||
+ value > MODE_TRROFF) {
+ return MODE_TRROFF;
+ }
+ return value;
+ };
+
+ uint32_t tmp = MODE_NATIVEONLY;
+ if (NS_SUCCEEDED(Preferences::GetUint("network.trr.mode", &tmp))) {
+ tmp = processPrefValue(tmp);
+ }
+
+ if (tmp != MODE_NATIVEONLY) {
+ return tmp;
+ }
+
+ if (NS_SUCCEEDED(Preferences::GetUint(kRolloutModePref, &tmp))) {
+ return processPrefValue(tmp);
+ }
+
+ return MODE_NATIVEONLY;
+}
+
+void TRRServiceBase::OnTRRModeChange() {
+ uint32_t oldMode = mMode;
+ mMode = ModeFromPrefs();
+ if (mMode != oldMode) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, NS_NETWORK_TRR_MODE_CHANGED_TOPIC, nullptr);
+ }
+ }
+
+ static bool readHosts = false;
+ if ((mMode == MODE_TRRFIRST || mMode == MODE_TRRONLY) && !readHosts) {
+ readHosts = true;
+ ReadEtcHostsFile();
+ }
+}
+
+void TRRServiceBase::OnTRRURIChange() {
+ mURIPrefHasUserValue = Preferences::HasUserValue("network.trr.uri");
+ Preferences::GetCString("network.trr.uri", mURIPref);
+ Preferences::GetCString(kRolloutURIPref, mRolloutURIPref);
+
+ CheckURIPrefs();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/TRRServiceBase.h b/netwerk/dns/TRRServiceBase.h
new file mode 100644
index 0000000000..d552b26099
--- /dev/null
+++ b/netwerk/dns/TRRServiceBase.h
@@ -0,0 +1,51 @@
+/* -*- 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 "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+class TRRServiceBase {
+ public:
+ TRRServiceBase();
+ uint32_t Mode() { return mMode; }
+
+ protected:
+ ~TRRServiceBase() = default;
+ 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();
+
+ virtual void ReadEtcHostsFile() {}
+
+ nsCString mPrivateURI;
+ // Pref caches should only be used on the main thread.
+ bool mURIPrefHasUserValue = false;
+ nsCString mURIPref;
+ nsCString mRolloutURIPref;
+
+ Atomic<uint32_t, Relaxed> mMode;
+ Atomic<bool, Relaxed> mURISetByDetection;
+};
+
+} // 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..365f061a0d
--- /dev/null
+++ b/netwerk/dns/TRRServiceChild.cpp
@@ -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/. */
+
+#include "mozilla/net/TRRServiceChild.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIDNService.h"
+#include "nsIObserverService.h"
+#include "TRRService.h"
+
+namespace mozilla {
+namespace net {
+
+static StaticRefPtr<nsIDNSService> sDNSService;
+
+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);
+ MOZ_ASSERT(gTRRService);
+
+ gTRRService->mCaptiveIsPassed = aCaptiveIsPassed;
+ gTRRService->mParentalControlEnabled = aParentalControlEnabled;
+ gTRRService->RebuildSuffixList(std::move(aDNSSuffixList));
+}
+
+mozilla::ipc::IPCResult TRRServiceChild::RecvUpdatePlatformDNSInformation(
+ nsTArray<nsCString>&& aDNSSuffixList) {
+ gTRRService->RebuildSuffixList(std::move(aDNSSuffixList));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult TRRServiceChild::RecvUpdateParentalControlEnabled(
+ const bool& aEnabled) {
+ gTRRService->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) {
+ gTRRService->SetDetectedTrrURI(aURI);
+ 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..7334a5f3d3
--- /dev/null
+++ b/netwerk/dns/TRRServiceChild.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_TRRServiceChild_h
+#define mozilla_net_TRRServiceChild_h
+
+#include "mozilla/net/PTRRServiceChild.h"
+
+namespace mozilla {
+
+namespace ipc {
+class FileDescriptor;
+} // namespace ipc
+
+namespace net {
+
+class TRRServiceChild : public PTRRServiceChild {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(TRRServiceChild, override)
+
+ explicit TRRServiceChild() = default;
+
+ 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);
+
+ private:
+ virtual ~TRRServiceChild() = default;
+};
+
+} // 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..9002f239fe
--- /dev/null
+++ b/netwerk/dns/TRRServiceParent.cpp
@@ -0,0 +1,155 @@
+/* -*- 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 "nsICaptivePortalService.h"
+#include "nsIParentalControlsService.h"
+#include "nsINetworkLinkService.h"
+#include "nsIObserverService.h"
+#include "nsIOService.h"
+#include "nsNetCID.h"
+#include "TRRService.h"
+
+namespace mozilla {
+namespace net {
+
+static const char kRolloutURIPref[] = "doh-rollout.uri";
+static const char kRolloutModePref[] = "doh-rollout.mode";
+
+static const char* gTRRUriCallbackPrefs[] = {
+ "network.trr.uri", "network.trr.mode", kRolloutURIPref, kRolloutModePref,
+ nullptr,
+};
+
+NS_IMPL_ISUPPORTS(TRRServiceParent, nsIObserver, nsISupportsWeakReference)
+
+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);
+
+ Unused << socketParent->SendPTRRServiceConstructor(
+ this, captiveIsPassed, parentalControlEnabled, suffixList);
+}
+
+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;
+}
+
+bool TRRServiceParent::MaybeSetPrivateURI(const nsACString& aURI) {
+ nsAutoCString newURI(aURI);
+ ProcessURITemplate(newURI);
+
+ if (mPrivateURI.Equals(newURI)) {
+ return false;
+ }
+
+ mPrivateURI = newURI;
+
+ 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 (mURIPrefHasUserValue) {
+ return;
+ }
+
+ mURISetByDetection = MaybeSetPrivateURI(aURI);
+ RefPtr<TRRServiceParent> self = this;
+ nsCString uri(aURI);
+ gIOService->CallOrWaitForSocketProcess(
+ [self, uri]() { Unused << self->SendSetDetectedTrrURI(uri); });
+}
+
+void TRRServiceParent::GetTrrURI(nsACString& aURI) { 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, kRolloutURIPref)) {
+ OnTRRURIChange();
+ }
+
+ if (!aName || !strcmp(aName, "network.trr.mode") ||
+ !strcmp(aName, kRolloutModePref)) {
+ OnTRRModeChange();
+ }
+}
+
+void TRRServiceParent::ActorDestroy(ActorDestroyReason why) {
+ Preferences::UnregisterPrefixCallbacks(TRRServiceParent::PrefsChanged,
+ gTRRUriCallbackPrefs, this);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/TRRServiceParent.h b/netwerk/dns/TRRServiceParent.h
new file mode 100644
index 0000000000..4df599fe1b
--- /dev/null
+++ b/netwerk/dns/TRRServiceParent.h
@@ -0,0 +1,43 @@
+/* -*- 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_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ 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 GetTrrURI(nsACString& aURI);
+
+ private:
+ virtual ~TRRServiceParent() = default;
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+ void prefsChanged(const char* aName);
+};
+
+} // 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..038b492cb4
--- /dev/null
+++ b/netwerk/dns/effective_tld_names.dat
@@ -0,0 +1,13597 @@
+// This Source Code Form is subject to the terms of 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 : https://en.wikipedia.org/wiki/.ac
+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://en.wikipedia.org/wiki/.ae
+// see also: "Domain Name Eligibility Policy" at http://www.aeda.ae/eng/aepolicy.php
+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/nic-argentina/normativa-vigente
+ar
+com.ar
+edu.ar
+gob.ar
+gov.ar
+int.ar
+mil.ar
+musica.ar
+net.ar
+org.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://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
+
+// 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
+aprendemas.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
+
+// 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>
+cy
+ac.cy
+biz.cy
+com.cy
+ekloges.cy
+gov.cy
+ltd.cy
+name.cy
+net.cy
+org.cy
+parliament.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 : http://www.afnic.fr/
+// domaines descriptifs : https://www.afnic.fr/medias/documents/Cadre_legal/Afnic_Naming_Policy_12122016_VEN.pdf
+fr
+asso.fr
+com.fr
+gouv.fr
+nom.fr
+prd.fr
+tm.fr
+// domaines sectoriels : https://www.afnic.fr/en/products-and-services/the-fr-tld/sector-based-fr-domains-4.html
+aeroport.fr
+avocat.fr
+avoues.fr
+cci.fr
+chambagri.fr
+chirurgiens-dentistes.fr
+experts-comptables.fr
+geometre-expert.fr
+greta.fr
+huissier-justice.fr
+medecin.fr
+notaires.fr
+pharmacien.fr
+port.fr
+veterinaire.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
+
+// 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/
+il
+ac.il
+co.il
+gov.il
+idf.il
+k12.il
+muni.il
+net.il
+org.il
+
+// 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
+co.in
+firm.in
+net.in
+org.in
+gen.in
+ind.in
+nic.in
+ac.in
+edu.in
+res.in
+gov.in
+mil.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.html
+// 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
+edu.ky
+gov.ky
+com.ky
+org.ky
+net.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 : http://about.museum/naming/
+// http://index.museum/
+museum
+academy.museum
+agriculture.museum
+air.museum
+airguard.museum
+alabama.museum
+alaska.museum
+amber.museum
+ambulance.museum
+american.museum
+americana.museum
+americanantiques.museum
+americanart.museum
+amsterdam.museum
+and.museum
+annefrank.museum
+anthro.museum
+anthropology.museum
+antiques.museum
+aquarium.museum
+arboretum.museum
+archaeological.museum
+archaeology.museum
+architecture.museum
+art.museum
+artanddesign.museum
+artcenter.museum
+artdeco.museum
+arteducation.museum
+artgallery.museum
+arts.museum
+artsandcrafts.museum
+asmatart.museum
+assassination.museum
+assisi.museum
+association.museum
+astronomy.museum
+atlanta.museum
+austin.museum
+australia.museum
+automotive.museum
+aviation.museum
+axis.museum
+badajoz.museum
+baghdad.museum
+bahn.museum
+bale.museum
+baltimore.museum
+barcelona.museum
+baseball.museum
+basel.museum
+baths.museum
+bauern.museum
+beauxarts.museum
+beeldengeluid.museum
+bellevue.museum
+bergbau.museum
+berkeley.museum
+berlin.museum
+bern.museum
+bible.museum
+bilbao.museum
+bill.museum
+birdart.museum
+birthplace.museum
+bonn.museum
+boston.museum
+botanical.museum
+botanicalgarden.museum
+botanicgarden.museum
+botany.museum
+brandywinevalley.museum
+brasil.museum
+bristol.museum
+british.museum
+britishcolumbia.museum
+broadcast.museum
+brunel.museum
+brussel.museum
+brussels.museum
+bruxelles.museum
+building.museum
+burghof.museum
+bus.museum
+bushey.museum
+cadaques.museum
+california.museum
+cambridge.museum
+can.museum
+canada.museum
+capebreton.museum
+carrier.museum
+cartoonart.museum
+casadelamoneda.museum
+castle.museum
+castres.museum
+celtic.museum
+center.museum
+chattanooga.museum
+cheltenham.museum
+chesapeakebay.museum
+chicago.museum
+children.museum
+childrens.museum
+childrensgarden.museum
+chiropractic.museum
+chocolate.museum
+christiansburg.museum
+cincinnati.museum
+cinema.museum
+circus.museum
+civilisation.museum
+civilization.museum
+civilwar.museum
+clinton.museum
+clock.museum
+coal.museum
+coastaldefence.museum
+cody.museum
+coldwar.museum
+collection.museum
+colonialwilliamsburg.museum
+coloradoplateau.museum
+columbia.museum
+columbus.museum
+communication.museum
+communications.museum
+community.museum
+computer.museum
+computerhistory.museum
+comunicações.museum
+contemporary.museum
+contemporaryart.museum
+convent.museum
+copenhagen.museum
+corporation.museum
+correios-e-telecomunicações.museum
+corvette.museum
+costume.museum
+countryestate.museum
+county.museum
+crafts.museum
+cranbrook.museum
+creation.museum
+cultural.museum
+culturalcenter.museum
+culture.museum
+cyber.museum
+cymru.museum
+dali.museum
+dallas.museum
+database.museum
+ddr.museum
+decorativearts.museum
+delaware.museum
+delmenhorst.museum
+denmark.museum
+depot.museum
+design.museum
+detroit.museum
+dinosaur.museum
+discovery.museum
+dolls.museum
+donostia.museum
+durham.museum
+eastafrica.museum
+eastcoast.museum
+education.museum
+educational.museum
+egyptian.museum
+eisenbahn.museum
+elburg.museum
+elvendrell.museum
+embroidery.museum
+encyclopedic.museum
+england.museum
+entomology.museum
+environment.museum
+environmentalconservation.museum
+epilepsy.museum
+essex.museum
+estate.museum
+ethnology.museum
+exeter.museum
+exhibition.museum
+family.museum
+farm.museum
+farmequipment.museum
+farmers.museum
+farmstead.museum
+field.museum
+figueres.museum
+filatelia.museum
+film.museum
+fineart.museum
+finearts.museum
+finland.museum
+flanders.museum
+florida.museum
+force.museum
+fortmissoula.museum
+fortworth.museum
+foundation.museum
+francaise.museum
+frankfurt.museum
+franziskaner.museum
+freemasonry.museum
+freiburg.museum
+fribourg.museum
+frog.museum
+fundacio.museum
+furniture.museum
+gallery.museum
+garden.museum
+gateway.museum
+geelvinck.museum
+gemological.museum
+geology.museum
+georgia.museum
+giessen.museum
+glas.museum
+glass.museum
+gorge.museum
+grandrapids.museum
+graz.museum
+guernsey.museum
+halloffame.museum
+hamburg.museum
+handson.museum
+harvestcelebration.museum
+hawaii.museum
+health.museum
+heimatunduhren.museum
+hellas.museum
+helsinki.museum
+hembygdsforbund.museum
+heritage.museum
+histoire.museum
+historical.museum
+historicalsociety.museum
+historichouses.museum
+historisch.museum
+historisches.museum
+history.museum
+historyofscience.museum
+horology.museum
+house.museum
+humanities.museum
+illustration.museum
+imageandsound.museum
+indian.museum
+indiana.museum
+indianapolis.museum
+indianmarket.museum
+intelligence.museum
+interactive.museum
+iraq.museum
+iron.museum
+isleofman.museum
+jamison.museum
+jefferson.museum
+jerusalem.museum
+jewelry.museum
+jewish.museum
+jewishart.museum
+jfk.museum
+journalism.museum
+judaica.museum
+judygarland.museum
+juedisches.museum
+juif.museum
+karate.museum
+karikatur.museum
+kids.museum
+koebenhavn.museum
+koeln.museum
+kunst.museum
+kunstsammlung.museum
+kunstunddesign.museum
+labor.museum
+labour.museum
+lajolla.museum
+lancashire.museum
+landes.museum
+lans.museum
+läns.museum
+larsson.museum
+lewismiller.museum
+lincoln.museum
+linz.museum
+living.museum
+livinghistory.museum
+localhistory.museum
+london.museum
+losangeles.museum
+louvre.museum
+loyalist.museum
+lucerne.museum
+luxembourg.museum
+luzern.museum
+mad.museum
+madrid.museum
+mallorca.museum
+manchester.museum
+mansion.museum
+mansions.museum
+manx.museum
+marburg.museum
+maritime.museum
+maritimo.museum
+maryland.museum
+marylhurst.museum
+media.museum
+medical.museum
+medizinhistorisches.museum
+meeres.museum
+memorial.museum
+mesaverde.museum
+michigan.museum
+midatlantic.museum
+military.museum
+mill.museum
+miners.museum
+mining.museum
+minnesota.museum
+missile.museum
+missoula.museum
+modern.museum
+moma.museum
+money.museum
+monmouth.museum
+monticello.museum
+montreal.museum
+moscow.museum
+motorcycle.museum
+muenchen.museum
+muenster.museum
+mulhouse.museum
+muncie.museum
+museet.museum
+museumcenter.museum
+museumvereniging.museum
+music.museum
+national.museum
+nationalfirearms.museum
+nationalheritage.museum
+nativeamerican.museum
+naturalhistory.museum
+naturalhistorymuseum.museum
+naturalsciences.museum
+nature.museum
+naturhistorisches.museum
+natuurwetenschappen.museum
+naumburg.museum
+naval.museum
+nebraska.museum
+neues.museum
+newhampshire.museum
+newjersey.museum
+newmexico.museum
+newport.museum
+newspaper.museum
+newyork.museum
+niepce.museum
+norfolk.museum
+north.museum
+nrw.museum
+nyc.museum
+nyny.museum
+oceanographic.museum
+oceanographique.museum
+omaha.museum
+online.museum
+ontario.museum
+openair.museum
+oregon.museum
+oregontrail.museum
+otago.museum
+oxford.museum
+pacific.museum
+paderborn.museum
+palace.museum
+paleo.museum
+palmsprings.museum
+panama.museum
+paris.museum
+pasadena.museum
+pharmacy.museum
+philadelphia.museum
+philadelphiaarea.museum
+philately.museum
+phoenix.museum
+photography.museum
+pilots.museum
+pittsburgh.museum
+planetarium.museum
+plantation.museum
+plants.museum
+plaza.museum
+portal.museum
+portland.museum
+portlligat.museum
+posts-and-telecommunications.museum
+preservation.museum
+presidio.museum
+press.museum
+project.museum
+public.museum
+pubol.museum
+quebec.museum
+railroad.museum
+railway.museum
+research.museum
+resistance.museum
+riodejaneiro.museum
+rochester.museum
+rockart.museum
+roma.museum
+russia.museum
+saintlouis.museum
+salem.museum
+salvadordali.museum
+salzburg.museum
+sandiego.museum
+sanfrancisco.museum
+santabarbara.museum
+santacruz.museum
+santafe.museum
+saskatchewan.museum
+satx.museum
+savannahga.museum
+schlesisches.museum
+schoenbrunn.museum
+schokoladen.museum
+school.museum
+schweiz.museum
+science.museum
+scienceandhistory.museum
+scienceandindustry.museum
+sciencecenter.museum
+sciencecenters.museum
+science-fiction.museum
+sciencehistory.museum
+sciences.museum
+sciencesnaturelles.museum
+scotland.museum
+seaport.museum
+settlement.museum
+settlers.museum
+shell.museum
+sherbrooke.museum
+sibenik.museum
+silk.museum
+ski.museum
+skole.museum
+society.museum
+sologne.museum
+soundandvision.museum
+southcarolina.museum
+southwest.museum
+space.museum
+spy.museum
+square.museum
+stadt.museum
+stalbans.museum
+starnberg.museum
+state.museum
+stateofdelaware.museum
+station.museum
+steam.museum
+steiermark.museum
+stjohn.museum
+stockholm.museum
+stpetersburg.museum
+stuttgart.museum
+suisse.museum
+surgeonshall.museum
+surrey.museum
+svizzera.museum
+sweden.museum
+sydney.museum
+tank.museum
+tcm.museum
+technology.museum
+telekommunikation.museum
+television.museum
+texas.museum
+textile.museum
+theater.museum
+time.museum
+timekeeping.museum
+topology.museum
+torino.museum
+touch.museum
+town.museum
+transport.museum
+tree.museum
+trolley.museum
+trust.museum
+trustee.museum
+uhren.museum
+ulm.museum
+undersea.museum
+university.museum
+usa.museum
+usantiques.museum
+usarts.museum
+uscountryestate.museum
+usculture.museum
+usdecorativearts.museum
+usgarden.museum
+ushistory.museum
+ushuaia.museum
+uslivinghistory.museum
+utah.museum
+uvic.museum
+valley.museum
+vantaa.museum
+versailles.museum
+viking.museum
+village.museum
+virginia.museum
+virtual.museum
+virtuel.museum
+vlaanderen.museum
+volkenkunde.museum
+wales.museum
+wallonie.museum
+war.museum
+washingtondc.museum
+watchandclock.museum
+watch-and-clock.museum
+western.museum
+westfalen.museum
+whaling.museum
+wildlife.museum
+williamsburg.museum
+windmill.museum
+workshop.museum
+york.museum
+yorkshire.museum
+yosemite.museum
+youth.museum
+zoological.museum
+zoology.museum
+ירושלים.museum
+иком.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.net.my/
+my
+com.my
+net.my
+org.my
+gov.my
+edu.my
+mil.my
+name.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
+ic.gov.pl
+is.gov.pl
+us.gov.pl
+kmpsp.gov.pl
+kppsp.gov.pl
+kwpsp.gov.pl
+psp.gov.pl
+wskr.gov.pl
+kwp.gov.pl
+mw.gov.pl
+ug.gov.pl
+um.gov.pl
+umig.gov.pl
+ugim.gov.pl
+upow.gov.pl
+uw.gov.pl
+starostwo.gov.pl
+pa.gov.pl
+po.gov.pl
+psse.gov.pl
+pup.gov.pl
+rzgw.gov.pl
+sa.gov.pl
+so.gov.pl
+sr.gov.pl
+wsa.gov.pl
+sko.gov.pl
+uzs.gov.pl
+wiih.gov.pl
+winb.gov.pl
+pinb.gov.pl
+wios.gov.pl
+witd.gov.pl
+wzmiuw.gov.pl
+piw.gov.pl
+wiw.gov.pl
+griw.gov.pl
+wif.gov.pl
+oum.gov.pl
+sdn.gov.pl
+zp.gov.pl
+uppo.gov.pl
+mup.gov.pl
+wuoz.gov.pl
+konsulat.gov.pl
+oirm.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 : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.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 : http://online.dns.pt/dns/start_dns
+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 : http://www.afnic.re/obtenir/chartes/nommage-re/annexe-descriptifs
+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://www.nic.sh/registrar.html
+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
+net.ss
+org.ss
+
+// st : http://www.nic.st/html/policyrules/
+st
+co.st
+com.st
+consulado.st
+edu.st
+embaixada.st
+gov.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://en.wikipedia.org/wiki/.tf
+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 : https://en.wikipedia.org/wiki/.tn
+// http://whois.ati.tn/
+tn
+com.tn
+ens.tn
+fin.tn
+gov.tn
+ind.tn
+intl.tn
+nat.tn
+net.tn
+org.tn
+info.tn
+perso.tn
+tourism.tn
+edunet.tn
+rnrt.tn
+rns.tn
+rnu.tn
+mincom.tn
+agrinet.tn
+defense.tn
+turen.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
+krym.ua
+ks.ua
+kv.ua
+kyiv.ua
+lg.ua
+lt.ua
+lugansk.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
+vinnica.ua
+vinnytsia.ua
+vn.ua
+volyn.ua
+yalta.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.de.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
+ve
+arts.ve
+co.ve
+com.ve
+e12.ve
+edu.ve
+firm.ve
+gob.ve
+gov.ve
+info.ve
+int.ve
+mil.ve
+net.ve
+org.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.dot.vn/vnnic/vnnic/domainregistration.jsp
+vn
+com.vn
+net.vn
+org.vn
+edu.vn
+gov.vn
+int.vn
+ac.vn
+biz.vn
+info.vn
+name.vn
+pro.vn
+health.vn
+
+// vu : https://en.wikipedia.org/wiki/.vu
+// http://www.vunic.vu/
+vu
+com.vu
+edu.vu
+net.vu
+org.vu
+
+// wf : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.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 : http://www.afnic.fr/medias/documents/AFNIC-naming-policy2012.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 2021-02-07T16:47:40Z
+// This list is auto-generated, don't edit it manually.
+// aaa : 2015-02-26 American Automobile Association, Inc.
+aaa
+
+// aarp : 2015-05-21 AARP
+aarp
+
+// abarth : 2015-07-30 Fiat Chrysler Automobiles N.V.
+abarth
+
+// abb : 2014-10-24 ABB Ltd
+abb
+
+// abbott : 2014-07-24 Abbott Laboratories, Inc.
+abbott
+
+// abbvie : 2015-07-30 AbbVie Inc.
+abbvie
+
+// abc : 2015-07-30 Disney Enterprises, Inc.
+abc
+
+// able : 2015-06-25 Able Inc.
+able
+
+// abogado : 2014-04-24 Minds + Machines Group Limited
+abogado
+
+// abudhabi : 2015-07-30 Abu Dhabi Systems and Information Centre
+abudhabi
+
+// academy : 2013-11-07 Binky Moon, LLC
+academy
+
+// accenture : 2014-08-15 Accenture plc
+accenture
+
+// accountant : 2014-11-20 dot Accountant Limited
+accountant
+
+// accountants : 2014-03-20 Binky Moon, LLC
+accountants
+
+// aco : 2015-01-08 ACO Severin Ahlmann GmbH & Co. KG
+aco
+
+// actor : 2013-12-12 Dog Beach, LLC
+actor
+
+// adac : 2015-07-16 Allgemeiner Deutscher Automobil-Club e.V. (ADAC)
+adac
+
+// ads : 2014-12-04 Charleston Road Registry Inc.
+ads
+
+// adult : 2014-10-16 ICM Registry AD LLC
+adult
+
+// aeg : 2015-03-19 Aktiebolaget Electrolux
+aeg
+
+// aetna : 2015-05-21 Aetna Life Insurance Company
+aetna
+
+// afamilycompany : 2015-07-23 Johnson Shareholdings, Inc.
+afamilycompany
+
+// afl : 2014-10-02 Australian Football League
+afl
+
+// africa : 2014-03-24 ZA Central Registry NPC trading as Registry.Africa
+africa
+
+// agakhan : 2015-04-23 Fondation Aga Khan (Aga Khan Foundation)
+agakhan
+
+// agency : 2013-11-14 Binky Moon, LLC
+agency
+
+// aig : 2014-12-18 American International Group, Inc.
+aig
+
+// airbus : 2015-07-30 Airbus S.A.S.
+airbus
+
+// airforce : 2014-03-06 Dog Beach, LLC
+airforce
+
+// airtel : 2014-10-24 Bharti Airtel Limited
+airtel
+
+// akdn : 2015-04-23 Fondation Aga Khan (Aga Khan Foundation)
+akdn
+
+// alfaromeo : 2015-07-31 Fiat Chrysler Automobiles N.V.
+alfaromeo
+
+// alibaba : 2015-01-15 Alibaba Group Holding Limited
+alibaba
+
+// alipay : 2015-01-15 Alibaba Group Holding Limited
+alipay
+
+// allfinanz : 2014-07-03 Allfinanz Deutsche Vermögensberatung Aktiengesellschaft
+allfinanz
+
+// allstate : 2015-07-31 Allstate Fire and Casualty Insurance Company
+allstate
+
+// ally : 2015-06-18 Ally Financial Inc.
+ally
+
+// alsace : 2014-07-02 Region Grand Est
+alsace
+
+// alstom : 2015-07-30 ALSTOM
+alstom
+
+// amazon : 2019-12-19 Amazon Registry Services, Inc.
+amazon
+
+// americanexpress : 2015-07-31 American Express Travel Related Services Company, Inc.
+americanexpress
+
+// americanfamily : 2015-07-23 AmFam, Inc.
+americanfamily
+
+// amex : 2015-07-31 American Express Travel Related Services Company, Inc.
+amex
+
+// amfam : 2015-07-23 AmFam, Inc.
+amfam
+
+// amica : 2015-05-28 Amica Mutual Insurance Company
+amica
+
+// amsterdam : 2014-07-24 Gemeente Amsterdam
+amsterdam
+
+// analytics : 2014-12-18 Campus IP LLC
+analytics
+
+// android : 2014-08-07 Charleston Road Registry Inc.
+android
+
+// anquan : 2015-01-08 Beijing Qihu Keji Co., Ltd.
+anquan
+
+// anz : 2015-07-31 Australia and New Zealand Banking Group Limited
+anz
+
+// aol : 2015-09-17 Oath Inc.
+aol
+
+// apartments : 2014-12-11 Binky Moon, LLC
+apartments
+
+// app : 2015-05-14 Charleston Road Registry Inc.
+app
+
+// apple : 2015-05-14 Apple Inc.
+apple
+
+// aquarelle : 2014-07-24 Aquarelle.com
+aquarelle
+
+// arab : 2015-11-12 League of Arab States
+arab
+
+// aramco : 2014-11-20 Aramco Services Company
+aramco
+
+// archi : 2014-02-06 Afilias Limited
+archi
+
+// army : 2014-03-06 Dog Beach, LLC
+army
+
+// art : 2016-03-24 UK Creative Ideas Limited
+art
+
+// arte : 2014-12-11 Association Relative à la Télévision Européenne G.E.I.E.
+arte
+
+// asda : 2015-07-31 Wal-Mart Stores, Inc.
+asda
+
+// associates : 2014-03-06 Binky Moon, LLC
+associates
+
+// athleta : 2015-07-30 The Gap, Inc.
+athleta
+
+// attorney : 2014-03-20 Dog Beach, LLC
+attorney
+
+// auction : 2014-03-20 Dog Beach, LLC
+auction
+
+// audi : 2015-05-21 AUDI Aktiengesellschaft
+audi
+
+// audible : 2015-06-25 Amazon Registry Services, Inc.
+audible
+
+// audio : 2014-03-20 UNR Corp.
+audio
+
+// auspost : 2015-08-13 Australian Postal Corporation
+auspost
+
+// author : 2014-12-18 Amazon Registry Services, Inc.
+author
+
+// auto : 2014-11-13 XYZ.COM LLC
+auto
+
+// autos : 2014-01-09 XYZ.COM LLC
+autos
+
+// avianca : 2015-01-08 Avianca Holdings S.A.
+avianca
+
+// aws : 2015-06-25 AWS Registry LLC
+aws
+
+// axa : 2013-12-19 AXA Group Operations SAS
+axa
+
+// azure : 2014-12-18 Microsoft Corporation
+azure
+
+// baby : 2015-04-09 XYZ.COM LLC
+baby
+
+// baidu : 2015-01-08 Baidu, Inc.
+baidu
+
+// banamex : 2015-07-30 Citigroup Inc.
+banamex
+
+// bananarepublic : 2015-07-31 The Gap, Inc.
+bananarepublic
+
+// band : 2014-06-12 Dog Beach, LLC
+band
+
+// bank : 2014-09-25 fTLD Registry Services LLC
+bank
+
+// bar : 2013-12-12 Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable
+bar
+
+// barcelona : 2014-07-24 Municipi de Barcelona
+barcelona
+
+// barclaycard : 2014-11-20 Barclays Bank PLC
+barclaycard
+
+// barclays : 2014-11-20 Barclays Bank PLC
+barclays
+
+// barefoot : 2015-06-11 Gallo Vineyards, Inc.
+barefoot
+
+// bargains : 2013-11-14 Binky Moon, LLC
+bargains
+
+// baseball : 2015-10-29 MLB Advanced Media DH, LLC
+baseball
+
+// basketball : 2015-08-20 Fédération Internationale de Basketball (FIBA)
+basketball
+
+// bauhaus : 2014-04-17 Werkhaus GmbH
+bauhaus
+
+// bayern : 2014-01-23 Bayern Connect GmbH
+bayern
+
+// bbc : 2014-12-18 British Broadcasting Corporation
+bbc
+
+// bbt : 2015-07-23 BB&T Corporation
+bbt
+
+// bbva : 2014-10-02 BANCO BILBAO VIZCAYA ARGENTARIA, S.A.
+bbva
+
+// bcg : 2015-04-02 The Boston Consulting Group, Inc.
+bcg
+
+// bcn : 2014-07-24 Municipi de Barcelona
+bcn
+
+// beats : 2015-05-14 Beats Electronics, LLC
+beats
+
+// beauty : 2015-12-03 XYZ.COM LLC
+beauty
+
+// beer : 2014-01-09 Minds + Machines Group Limited
+beer
+
+// bentley : 2014-12-18 Bentley Motors Limited
+bentley
+
+// berlin : 2013-10-31 dotBERLIN GmbH & Co. KG
+berlin
+
+// best : 2013-12-19 BestTLD Pty Ltd
+best
+
+// bestbuy : 2015-07-31 BBY Solutions, Inc.
+bestbuy
+
+// bet : 2015-05-07 Afilias Limited
+bet
+
+// bharti : 2014-01-09 Bharti Enterprises (Holding) Private Limited
+bharti
+
+// bible : 2014-06-19 American Bible Society
+bible
+
+// bid : 2013-12-19 dot Bid Limited
+bid
+
+// bike : 2013-08-27 Binky Moon, LLC
+bike
+
+// bing : 2014-12-18 Microsoft Corporation
+bing
+
+// bingo : 2014-12-04 Binky Moon, LLC
+bingo
+
+// bio : 2014-03-06 Afilias Limited
+bio
+
+// black : 2014-01-16 Afilias Limited
+black
+
+// blackfriday : 2014-01-16 UNR Corp.
+blackfriday
+
+// blockbuster : 2015-07-30 Dish DBS Corporation
+blockbuster
+
+// blog : 2015-05-14 Knock Knock WHOIS There, LLC
+blog
+
+// bloomberg : 2014-07-17 Bloomberg IP Holdings LLC
+bloomberg
+
+// blue : 2013-11-07 Afilias Limited
+blue
+
+// bms : 2014-10-30 Bristol-Myers Squibb Company
+bms
+
+// bmw : 2014-01-09 Bayerische Motoren Werke Aktiengesellschaft
+bmw
+
+// bnpparibas : 2014-05-29 BNP Paribas
+bnpparibas
+
+// boats : 2014-12-04 XYZ.COM LLC
+boats
+
+// boehringer : 2015-07-09 Boehringer Ingelheim International GmbH
+boehringer
+
+// bofa : 2015-07-31 Bank of America Corporation
+bofa
+
+// bom : 2014-10-16 Núcleo de Informação e Coordenação do Ponto BR - NIC.br
+bom
+
+// bond : 2014-06-05 ShortDot SA
+bond
+
+// boo : 2014-01-30 Charleston Road Registry Inc.
+boo
+
+// book : 2015-08-27 Amazon Registry Services, Inc.
+book
+
+// booking : 2015-07-16 Booking.com B.V.
+booking
+
+// bosch : 2015-06-18 Robert Bosch GMBH
+bosch
+
+// bostik : 2015-05-28 Bostik SA
+bostik
+
+// boston : 2015-12-10 Boston TLD Management, LLC
+boston
+
+// bot : 2014-12-18 Amazon Registry Services, Inc.
+bot
+
+// boutique : 2013-11-14 Binky Moon, LLC
+boutique
+
+// box : 2015-11-12 Intercap Registry Inc.
+box
+
+// bradesco : 2014-12-18 Banco Bradesco S.A.
+bradesco
+
+// bridgestone : 2014-12-18 Bridgestone Corporation
+bridgestone
+
+// broadway : 2014-12-22 Celebrate Broadway, Inc.
+broadway
+
+// broker : 2014-12-11 Dotbroker Registry Limited
+broker
+
+// brother : 2015-01-29 Brother Industries, Ltd.
+brother
+
+// brussels : 2014-02-06 DNS.be vzw
+brussels
+
+// budapest : 2013-11-21 Minds + Machines Group Limited
+budapest
+
+// bugatti : 2015-07-23 Bugatti International SA
+bugatti
+
+// build : 2013-11-07 Plan Bee LLC
+build
+
+// builders : 2013-11-07 Binky Moon, LLC
+builders
+
+// business : 2013-11-07 Binky Moon, LLC
+business
+
+// buy : 2014-12-18 Amazon Registry Services, Inc.
+buy
+
+// buzz : 2013-10-02 DOTSTRATEGY CO.
+buzz
+
+// bzh : 2014-02-27 Association www.bzh
+bzh
+
+// cab : 2013-10-24 Binky Moon, LLC
+cab
+
+// cafe : 2015-02-11 Binky Moon, LLC
+cafe
+
+// cal : 2014-07-24 Charleston Road Registry Inc.
+cal
+
+// call : 2014-12-18 Amazon Registry Services, Inc.
+call
+
+// calvinklein : 2015-07-30 PVH gTLD Holdings LLC
+calvinklein
+
+// cam : 2016-04-21 AC Webconnecting Holding B.V.
+cam
+
+// camera : 2013-08-27 Binky Moon, LLC
+camera
+
+// camp : 2013-11-07 Binky Moon, LLC
+camp
+
+// cancerresearch : 2014-05-15 Australian Cancer Research Foundation
+cancerresearch
+
+// canon : 2014-09-12 Canon Inc.
+canon
+
+// capetown : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry
+capetown
+
+// capital : 2014-03-06 Binky Moon, LLC
+capital
+
+// capitalone : 2015-08-06 Capital One Financial Corporation
+capitalone
+
+// car : 2015-01-22 XYZ.COM LLC
+car
+
+// caravan : 2013-12-12 Caravan International, Inc.
+caravan
+
+// cards : 2013-12-05 Binky Moon, LLC
+cards
+
+// care : 2014-03-06 Binky Moon, LLC
+care
+
+// career : 2013-10-09 dotCareer LLC
+career
+
+// careers : 2013-10-02 Binky Moon, LLC
+careers
+
+// cars : 2014-11-13 XYZ.COM LLC
+cars
+
+// casa : 2013-11-21 Minds + Machines Group Limited
+casa
+
+// case : 2015-09-03 CNH Industrial N.V.
+case
+
+// caseih : 2015-09-03 CNH Industrial N.V.
+caseih
+
+// cash : 2014-03-06 Binky Moon, LLC
+cash
+
+// casino : 2014-12-18 Binky Moon, LLC
+casino
+
+// catering : 2013-12-05 Binky Moon, LLC
+catering
+
+// catholic : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication)
+catholic
+
+// cba : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA
+cba
+
+// cbn : 2014-08-22 The Christian Broadcasting Network, Inc.
+cbn
+
+// cbre : 2015-07-02 CBRE, Inc.
+cbre
+
+// cbs : 2015-08-06 CBS Domains Inc.
+cbs
+
+// center : 2013-11-07 Binky Moon, LLC
+center
+
+// ceo : 2013-11-07 CEOTLD Pty Ltd
+ceo
+
+// cern : 2014-06-05 European Organization for Nuclear Research ("CERN")
+cern
+
+// cfa : 2014-08-28 CFA Institute
+cfa
+
+// cfd : 2014-12-11 ShortDot SA
+cfd
+
+// chanel : 2015-04-09 Chanel International B.V.
+chanel
+
+// channel : 2014-05-08 Charleston Road Registry Inc.
+channel
+
+// charity : 2018-04-11 Binky Moon, LLC
+charity
+
+// chase : 2015-04-30 JPMorgan Chase Bank, National Association
+chase
+
+// chat : 2014-12-04 Binky Moon, LLC
+chat
+
+// cheap : 2013-11-14 Binky Moon, LLC
+cheap
+
+// chintai : 2015-06-11 CHINTAI Corporation
+chintai
+
+// christmas : 2013-11-21 UNR Corp.
+christmas
+
+// chrome : 2014-07-24 Charleston Road Registry Inc.
+chrome
+
+// church : 2014-02-06 Binky Moon, LLC
+church
+
+// cipriani : 2015-02-19 Hotel Cipriani Srl
+cipriani
+
+// circle : 2014-12-18 Amazon Registry Services, Inc.
+circle
+
+// cisco : 2014-12-22 Cisco Technology, Inc.
+cisco
+
+// citadel : 2015-07-23 Citadel Domain LLC
+citadel
+
+// citi : 2015-07-30 Citigroup Inc.
+citi
+
+// citic : 2014-01-09 CITIC Group Corporation
+citic
+
+// city : 2014-05-29 Binky Moon, LLC
+city
+
+// cityeats : 2014-12-11 Lifestyle Domain Holdings, Inc.
+cityeats
+
+// claims : 2014-03-20 Binky Moon, LLC
+claims
+
+// cleaning : 2013-12-05 Binky Moon, LLC
+cleaning
+
+// click : 2014-06-05 UNR Corp.
+click
+
+// clinic : 2014-03-20 Binky Moon, LLC
+clinic
+
+// clinique : 2015-10-01 The Estée Lauder Companies Inc.
+clinique
+
+// clothing : 2013-08-27 Binky Moon, LLC
+clothing
+
+// cloud : 2015-04-16 Aruba PEC S.p.A.
+cloud
+
+// club : 2013-11-08 .CLUB DOMAINS, LLC
+club
+
+// clubmed : 2015-06-25 Club Méditerranée S.A.
+clubmed
+
+// coach : 2014-10-09 Binky Moon, LLC
+coach
+
+// codes : 2013-10-31 Binky Moon, LLC
+codes
+
+// coffee : 2013-10-17 Binky Moon, LLC
+coffee
+
+// college : 2014-01-16 XYZ.COM LLC
+college
+
+// cologne : 2014-02-05 dotKoeln GmbH
+cologne
+
+// comcast : 2015-07-23 Comcast IP Holdings I, LLC
+comcast
+
+// commbank : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA
+commbank
+
+// community : 2013-12-05 Binky Moon, LLC
+community
+
+// company : 2013-11-07 Binky Moon, LLC
+company
+
+// compare : 2015-10-08 Registry Services, LLC
+compare
+
+// computer : 2013-10-24 Binky Moon, LLC
+computer
+
+// comsec : 2015-01-08 VeriSign, Inc.
+comsec
+
+// condos : 2013-12-05 Binky Moon, LLC
+condos
+
+// construction : 2013-09-16 Binky Moon, LLC
+construction
+
+// consulting : 2013-12-05 Dog Beach, LLC
+consulting
+
+// contact : 2015-01-08 Dog Beach, LLC
+contact
+
+// contractors : 2013-09-10 Binky Moon, LLC
+contractors
+
+// cooking : 2013-11-21 Minds + Machines Group Limited
+cooking
+
+// cookingchannel : 2015-07-02 Lifestyle Domain Holdings, Inc.
+cookingchannel
+
+// cool : 2013-11-14 Binky Moon, LLC
+cool
+
+// corsica : 2014-09-25 Collectivité de Corse
+corsica
+
+// country : 2013-12-19 DotCountry LLC
+country
+
+// coupon : 2015-02-26 Amazon Registry Services, Inc.
+coupon
+
+// coupons : 2015-03-26 Binky Moon, LLC
+coupons
+
+// courses : 2014-12-04 OPEN UNIVERSITIES AUSTRALIA PTY LTD
+courses
+
+// cpa : 2019-06-10 American Institute of Certified Public Accountants
+cpa
+
+// credit : 2014-03-20 Binky Moon, LLC
+credit
+
+// creditcard : 2014-03-20 Binky Moon, LLC
+creditcard
+
+// creditunion : 2015-01-22 DotCooperation LLC
+creditunion
+
+// cricket : 2014-10-09 dot Cricket Limited
+cricket
+
+// crown : 2014-10-24 Crown Equipment Corporation
+crown
+
+// crs : 2014-04-03 Federated Co-operatives Limited
+crs
+
+// cruise : 2015-12-10 Viking River Cruises (Bermuda) Ltd.
+cruise
+
+// cruises : 2013-12-05 Binky Moon, LLC
+cruises
+
+// csc : 2014-09-25 Alliance-One Services, Inc.
+csc
+
+// cuisinella : 2014-04-03 SCHMIDT GROUPE S.A.S.
+cuisinella
+
+// cymru : 2014-05-08 Nominet UK
+cymru
+
+// cyou : 2015-01-22 ShortDot SA
+cyou
+
+// dabur : 2014-02-06 Dabur India Limited
+dabur
+
+// dad : 2014-01-23 Charleston Road Registry Inc.
+dad
+
+// dance : 2013-10-24 Dog Beach, LLC
+dance
+
+// data : 2016-06-02 Dish DBS Corporation
+data
+
+// date : 2014-11-20 dot Date Limited
+date
+
+// dating : 2013-12-05 Binky Moon, LLC
+dating
+
+// datsun : 2014-03-27 NISSAN MOTOR CO., LTD.
+datsun
+
+// day : 2014-01-30 Charleston Road Registry Inc.
+day
+
+// dclk : 2014-11-20 Charleston Road Registry Inc.
+dclk
+
+// dds : 2015-05-07 Minds + Machines Group Limited
+dds
+
+// deal : 2015-06-25 Amazon Registry Services, Inc.
+deal
+
+// dealer : 2014-12-22 Intercap Registry Inc.
+dealer
+
+// deals : 2014-05-22 Binky Moon, LLC
+deals
+
+// degree : 2014-03-06 Dog Beach, LLC
+degree
+
+// delivery : 2014-09-11 Binky Moon, LLC
+delivery
+
+// dell : 2014-10-24 Dell Inc.
+dell
+
+// deloitte : 2015-07-31 Deloitte Touche Tohmatsu
+deloitte
+
+// delta : 2015-02-19 Delta Air Lines, Inc.
+delta
+
+// democrat : 2013-10-24 Dog Beach, LLC
+democrat
+
+// dental : 2014-03-20 Binky Moon, LLC
+dental
+
+// dentist : 2014-03-20 Dog Beach, LLC
+dentist
+
+// desi : 2013-11-14 Desi Networks LLC
+desi
+
+// design : 2014-11-07 Top Level Design, LLC
+design
+
+// dev : 2014-10-16 Charleston Road Registry Inc.
+dev
+
+// dhl : 2015-07-23 Deutsche Post AG
+dhl
+
+// diamonds : 2013-09-22 Binky Moon, LLC
+diamonds
+
+// diet : 2014-06-26 UNR Corp.
+diet
+
+// digital : 2014-03-06 Binky Moon, LLC
+digital
+
+// direct : 2014-04-10 Binky Moon, LLC
+direct
+
+// directory : 2013-09-20 Binky Moon, LLC
+directory
+
+// discount : 2014-03-06 Binky Moon, LLC
+discount
+
+// discover : 2015-07-23 Discover Financial Services
+discover
+
+// dish : 2015-07-30 Dish DBS Corporation
+dish
+
+// diy : 2015-11-05 Lifestyle Domain Holdings, Inc.
+diy
+
+// dnp : 2013-12-13 Dai Nippon Printing Co., Ltd.
+dnp
+
+// docs : 2014-10-16 Charleston Road Registry Inc.
+docs
+
+// doctor : 2016-06-02 Binky Moon, LLC
+doctor
+
+// dog : 2014-12-04 Binky Moon, LLC
+dog
+
+// domains : 2013-10-17 Binky Moon, LLC
+domains
+
+// dot : 2015-05-21 Dish DBS Corporation
+dot
+
+// download : 2014-11-20 dot Support Limited
+download
+
+// drive : 2015-03-05 Charleston Road Registry Inc.
+drive
+
+// dtv : 2015-06-04 Dish DBS Corporation
+dtv
+
+// dubai : 2015-01-01 Dubai Smart Government Department
+dubai
+
+// duck : 2015-07-23 Johnson Shareholdings, Inc.
+duck
+
+// dunlop : 2015-07-02 The Goodyear Tire & Rubber Company
+dunlop
+
+// dupont : 2015-06-25 E. I. du Pont de Nemours and Company
+dupont
+
+// durban : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry
+durban
+
+// dvag : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG
+dvag
+
+// dvr : 2016-05-26 DISH Technologies L.L.C.
+dvr
+
+// earth : 2014-12-04 Interlink Co., Ltd.
+earth
+
+// eat : 2014-01-23 Charleston Road Registry Inc.
+eat
+
+// eco : 2016-07-08 Big Room Inc.
+eco
+
+// edeka : 2014-12-18 EDEKA Verband kaufmännischer Genossenschaften e.V.
+edeka
+
+// education : 2013-11-07 Binky Moon, LLC
+education
+
+// email : 2013-10-31 Binky Moon, LLC
+email
+
+// emerck : 2014-04-03 Merck KGaA
+emerck
+
+// energy : 2014-09-11 Binky Moon, LLC
+energy
+
+// engineer : 2014-03-06 Dog Beach, LLC
+engineer
+
+// engineering : 2014-03-06 Binky Moon, LLC
+engineering
+
+// enterprises : 2013-09-20 Binky Moon, LLC
+enterprises
+
+// epson : 2014-12-04 Seiko Epson Corporation
+epson
+
+// equipment : 2013-08-27 Binky Moon, LLC
+equipment
+
+// ericsson : 2015-07-09 Telefonaktiebolaget L M Ericsson
+ericsson
+
+// erni : 2014-04-03 ERNI Group Holding AG
+erni
+
+// esq : 2014-05-08 Charleston Road Registry Inc.
+esq
+
+// estate : 2013-08-27 Binky Moon, LLC
+estate
+
+// etisalat : 2015-09-03 Emirates Telecommunications Corporation (trading as Etisalat)
+etisalat
+
+// eurovision : 2014-04-24 European Broadcasting Union (EBU)
+eurovision
+
+// eus : 2013-12-12 Puntueus Fundazioa
+eus
+
+// events : 2013-12-05 Binky Moon, LLC
+events
+
+// exchange : 2014-03-06 Binky Moon, LLC
+exchange
+
+// expert : 2013-11-21 Binky Moon, LLC
+expert
+
+// exposed : 2013-12-05 Binky Moon, LLC
+exposed
+
+// express : 2015-02-11 Binky Moon, LLC
+express
+
+// extraspace : 2015-05-14 Extra Space Storage LLC
+extraspace
+
+// fage : 2014-12-18 Fage International S.A.
+fage
+
+// fail : 2014-03-06 Binky Moon, LLC
+fail
+
+// fairwinds : 2014-11-13 FairWinds Partners, LLC
+fairwinds
+
+// faith : 2014-11-20 dot Faith Limited
+faith
+
+// family : 2015-04-02 Dog Beach, LLC
+family
+
+// fan : 2014-03-06 Dog Beach, LLC
+fan
+
+// fans : 2014-11-07 ZDNS International Limited
+fans
+
+// farm : 2013-11-07 Binky Moon, LLC
+farm
+
+// farmers : 2015-07-09 Farmers Insurance Exchange
+farmers
+
+// fashion : 2014-07-03 Minds + Machines Group Limited
+fashion
+
+// fast : 2014-12-18 Amazon Registry Services, Inc.
+fast
+
+// fedex : 2015-08-06 Federal Express Corporation
+fedex
+
+// feedback : 2013-12-19 Top Level Spectrum, Inc.
+feedback
+
+// ferrari : 2015-07-31 Fiat Chrysler Automobiles N.V.
+ferrari
+
+// ferrero : 2014-12-18 Ferrero Trading Lux S.A.
+ferrero
+
+// fiat : 2015-07-31 Fiat Chrysler Automobiles N.V.
+fiat
+
+// fidelity : 2015-07-30 Fidelity Brokerage Services LLC
+fidelity
+
+// fido : 2015-08-06 Rogers Communications Canada Inc.
+fido
+
+// film : 2015-01-08 Motion Picture Domain Registry Pty Ltd
+film
+
+// final : 2014-10-16 Núcleo de Informação e Coordenação do Ponto BR - NIC.br
+final
+
+// finance : 2014-03-20 Binky Moon, LLC
+finance
+
+// financial : 2014-03-06 Binky Moon, LLC
+financial
+
+// fire : 2015-06-25 Amazon Registry Services, Inc.
+fire
+
+// firestone : 2014-12-18 Bridgestone Licensing Services, Inc
+firestone
+
+// firmdale : 2014-03-27 Firmdale Holdings Limited
+firmdale
+
+// fish : 2013-12-12 Binky Moon, LLC
+fish
+
+// fishing : 2013-11-21 Minds + Machines Group Limited
+fishing
+
+// fit : 2014-11-07 Minds + Machines Group Limited
+fit
+
+// fitness : 2014-03-06 Binky Moon, LLC
+fitness
+
+// flickr : 2015-04-02 Flickr, Inc.
+flickr
+
+// flights : 2013-12-05 Binky Moon, LLC
+flights
+
+// flir : 2015-07-23 FLIR Systems, Inc.
+flir
+
+// florist : 2013-11-07 Binky Moon, LLC
+florist
+
+// flowers : 2014-10-09 UNR Corp.
+flowers
+
+// fly : 2014-05-08 Charleston Road Registry Inc.
+fly
+
+// foo : 2014-01-23 Charleston Road Registry Inc.
+foo
+
+// food : 2016-04-21 Lifestyle Domain Holdings, Inc.
+food
+
+// foodnetwork : 2015-07-02 Lifestyle Domain Holdings, Inc.
+foodnetwork
+
+// football : 2014-12-18 Binky Moon, LLC
+football
+
+// ford : 2014-11-13 Ford Motor Company
+ford
+
+// forex : 2014-12-11 Dotforex Registry Limited
+forex
+
+// forsale : 2014-05-22 Dog Beach, LLC
+forsale
+
+// forum : 2015-04-02 Fegistry, LLC
+forum
+
+// foundation : 2013-12-05 Binky Moon, LLC
+foundation
+
+// fox : 2015-09-11 FOX Registry, LLC
+fox
+
+// free : 2015-12-10 Amazon Registry Services, Inc.
+free
+
+// fresenius : 2015-07-30 Fresenius Immobilien-Verwaltungs-GmbH
+fresenius
+
+// frl : 2014-05-15 FRLregistry B.V.
+frl
+
+// frogans : 2013-12-19 OP3FT
+frogans
+
+// frontdoor : 2015-07-02 Lifestyle Domain Holdings, Inc.
+frontdoor
+
+// frontier : 2015-02-05 Frontier Communications Corporation
+frontier
+
+// ftr : 2015-07-16 Frontier Communications Corporation
+ftr
+
+// fujitsu : 2015-07-30 Fujitsu Limited
+fujitsu
+
+// fujixerox : 2015-07-23 Xerox DNHC LLC
+fujixerox
+
+// fun : 2016-01-14 DotSpace Inc.
+fun
+
+// fund : 2014-03-20 Binky Moon, LLC
+fund
+
+// furniture : 2014-03-20 Binky Moon, LLC
+furniture
+
+// futbol : 2013-09-20 Dog Beach, LLC
+futbol
+
+// fyi : 2015-04-02 Binky Moon, LLC
+fyi
+
+// gal : 2013-11-07 Asociación puntoGAL
+gal
+
+// gallery : 2013-09-13 Binky Moon, LLC
+gallery
+
+// gallo : 2015-06-11 Gallo Vineyards, Inc.
+gallo
+
+// gallup : 2015-02-19 Gallup, Inc.
+gallup
+
+// game : 2015-05-28 UNR Corp.
+game
+
+// games : 2015-05-28 Dog Beach, LLC
+games
+
+// gap : 2015-07-31 The Gap, Inc.
+gap
+
+// garden : 2014-06-26 Minds + Machines Group Limited
+garden
+
+// gay : 2019-05-23 Top Level Design, LLC
+gay
+
+// gbiz : 2014-07-17 Charleston Road Registry Inc.
+gbiz
+
+// gdn : 2014-07-31 Joint Stock Company "Navigation-information systems"
+gdn
+
+// gea : 2014-12-04 GEA Group Aktiengesellschaft
+gea
+
+// gent : 2014-01-23 COMBELL NV
+gent
+
+// genting : 2015-03-12 Resorts World Inc Pte. Ltd.
+genting
+
+// george : 2015-07-31 Wal-Mart Stores, Inc.
+george
+
+// ggee : 2014-01-09 GMO Internet, Inc.
+ggee
+
+// gift : 2013-10-17 DotGift, LLC
+gift
+
+// gifts : 2014-07-03 Binky Moon, LLC
+gifts
+
+// gives : 2014-03-06 Dog Beach, LLC
+gives
+
+// giving : 2014-11-13 Giving Limited
+giving
+
+// glade : 2015-07-23 Johnson Shareholdings, Inc.
+glade
+
+// glass : 2013-11-07 Binky Moon, LLC
+glass
+
+// gle : 2014-07-24 Charleston Road Registry Inc.
+gle
+
+// global : 2014-04-17 Dot Global Domain Registry Limited
+global
+
+// globo : 2013-12-19 Globo Comunicação e Participações S.A
+globo
+
+// gmail : 2014-05-01 Charleston Road Registry Inc.
+gmail
+
+// gmbh : 2016-01-29 Binky Moon, LLC
+gmbh
+
+// gmo : 2014-01-09 GMO Internet, Inc.
+gmo
+
+// gmx : 2014-04-24 1&1 Mail & Media GmbH
+gmx
+
+// godaddy : 2015-07-23 Go Daddy East, LLC
+godaddy
+
+// gold : 2015-01-22 Binky Moon, LLC
+gold
+
+// goldpoint : 2014-11-20 YODOBASHI CAMERA CO.,LTD.
+goldpoint
+
+// golf : 2014-12-18 Binky Moon, LLC
+golf
+
+// goo : 2014-12-18 NTT Resonant Inc.
+goo
+
+// goodyear : 2015-07-02 The Goodyear Tire & Rubber Company
+goodyear
+
+// goog : 2014-11-20 Charleston Road Registry Inc.
+goog
+
+// google : 2014-07-24 Charleston Road Registry Inc.
+google
+
+// gop : 2014-01-16 Republican State Leadership Committee, Inc.
+gop
+
+// got : 2014-12-18 Amazon Registry Services, Inc.
+got
+
+// grainger : 2015-05-07 Grainger Registry Services, LLC
+grainger
+
+// graphics : 2013-09-13 Binky Moon, LLC
+graphics
+
+// gratis : 2014-03-20 Binky Moon, LLC
+gratis
+
+// green : 2014-05-08 Afilias Limited
+green
+
+// gripe : 2014-03-06 Binky Moon, LLC
+gripe
+
+// grocery : 2016-06-16 Wal-Mart Stores, Inc.
+grocery
+
+// group : 2014-08-15 Binky Moon, LLC
+group
+
+// guardian : 2015-07-30 The Guardian Life Insurance Company of America
+guardian
+
+// gucci : 2014-11-13 Guccio Gucci S.p.a.
+gucci
+
+// guge : 2014-08-28 Charleston Road Registry Inc.
+guge
+
+// guide : 2013-09-13 Binky Moon, LLC
+guide
+
+// guitars : 2013-11-14 UNR Corp.
+guitars
+
+// guru : 2013-08-27 Binky Moon, LLC
+guru
+
+// hair : 2015-12-03 XYZ.COM LLC
+hair
+
+// hamburg : 2014-02-20 Hamburg Top-Level-Domain GmbH
+hamburg
+
+// hangout : 2014-11-13 Charleston Road Registry Inc.
+hangout
+
+// haus : 2013-12-05 Dog Beach, LLC
+haus
+
+// hbo : 2015-07-30 HBO Registry Services, Inc.
+hbo
+
+// hdfc : 2015-07-30 HOUSING DEVELOPMENT FINANCE CORPORATION LIMITED
+hdfc
+
+// hdfcbank : 2015-02-12 HDFC Bank Limited
+hdfcbank
+
+// health : 2015-02-11 DotHealth, LLC
+health
+
+// healthcare : 2014-06-12 Binky Moon, LLC
+healthcare
+
+// help : 2014-06-26 UNR Corp.
+help
+
+// helsinki : 2015-02-05 City of Helsinki
+helsinki
+
+// here : 2014-02-06 Charleston Road Registry Inc.
+here
+
+// hermes : 2014-07-10 HERMES INTERNATIONAL
+hermes
+
+// hgtv : 2015-07-02 Lifestyle Domain Holdings, Inc.
+hgtv
+
+// hiphop : 2014-03-06 UNR Corp.
+hiphop
+
+// hisamitsu : 2015-07-16 Hisamitsu Pharmaceutical Co.,Inc.
+hisamitsu
+
+// hitachi : 2014-10-31 Hitachi, Ltd.
+hitachi
+
+// hiv : 2014-03-13 UNR Corp.
+hiv
+
+// hkt : 2015-05-14 PCCW-HKT DataCom Services Limited
+hkt
+
+// hockey : 2015-03-19 Binky Moon, LLC
+hockey
+
+// holdings : 2013-08-27 Binky Moon, LLC
+holdings
+
+// holiday : 2013-11-07 Binky Moon, LLC
+holiday
+
+// homedepot : 2015-04-02 Home Depot Product Authority, LLC
+homedepot
+
+// homegoods : 2015-07-16 The TJX Companies, Inc.
+homegoods
+
+// homes : 2014-01-09 XYZ.COM LLC
+homes
+
+// homesense : 2015-07-16 The TJX Companies, Inc.
+homesense
+
+// honda : 2014-12-18 Honda Motor Co., Ltd.
+honda
+
+// horse : 2013-11-21 Minds + Machines Group Limited
+horse
+
+// hospital : 2016-10-20 Binky Moon, LLC
+hospital
+
+// host : 2014-04-17 DotHost Inc.
+host
+
+// hosting : 2014-05-29 UNR Corp.
+hosting
+
+// hot : 2015-08-27 Amazon Registry Services, Inc.
+hot
+
+// hoteles : 2015-03-05 Travel Reservations SRL
+hoteles
+
+// hotels : 2016-04-07 Booking.com B.V.
+hotels
+
+// hotmail : 2014-12-18 Microsoft Corporation
+hotmail
+
+// house : 2013-11-07 Binky Moon, LLC
+house
+
+// how : 2014-01-23 Charleston Road Registry Inc.
+how
+
+// hsbc : 2014-10-24 HSBC Global Services (UK) Limited
+hsbc
+
+// hughes : 2015-07-30 Hughes Satellite Systems Corporation
+hughes
+
+// hyatt : 2015-07-30 Hyatt GTLD, L.L.C.
+hyatt
+
+// hyundai : 2015-07-09 Hyundai Motor Company
+hyundai
+
+// ibm : 2014-07-31 International Business Machines Corporation
+ibm
+
+// icbc : 2015-02-19 Industrial and Commercial Bank of China Limited
+icbc
+
+// ice : 2014-10-30 IntercontinentalExchange, Inc.
+ice
+
+// icu : 2015-01-08 ShortDot SA
+icu
+
+// ieee : 2015-07-23 IEEE Global LLC
+ieee
+
+// ifm : 2014-01-30 ifm electronic gmbh
+ifm
+
+// ikano : 2015-07-09 Ikano S.A.
+ikano
+
+// imamat : 2015-08-06 Fondation Aga Khan (Aga Khan Foundation)
+imamat
+
+// imdb : 2015-06-25 Amazon Registry Services, Inc.
+imdb
+
+// immo : 2014-07-10 Binky Moon, LLC
+immo
+
+// immobilien : 2013-11-07 Dog Beach, LLC
+immobilien
+
+// inc : 2018-03-10 Intercap Registry Inc.
+inc
+
+// industries : 2013-12-05 Binky Moon, LLC
+industries
+
+// infiniti : 2014-03-27 NISSAN MOTOR CO., LTD.
+infiniti
+
+// ing : 2014-01-23 Charleston Road Registry Inc.
+ing
+
+// ink : 2013-12-05 Top Level Design, LLC
+ink
+
+// institute : 2013-11-07 Binky Moon, LLC
+institute
+
+// insurance : 2015-02-19 fTLD Registry Services LLC
+insurance
+
+// insure : 2014-03-20 Binky Moon, LLC
+insure
+
+// international : 2013-11-07 Binky Moon, LLC
+international
+
+// intuit : 2015-07-30 Intuit Administrative Services, Inc.
+intuit
+
+// investments : 2014-03-20 Binky Moon, LLC
+investments
+
+// ipiranga : 2014-08-28 Ipiranga Produtos de Petroleo S.A.
+ipiranga
+
+// irish : 2014-08-07 Binky Moon, LLC
+irish
+
+// ismaili : 2015-08-06 Fondation Aga Khan (Aga Khan Foundation)
+ismaili
+
+// ist : 2014-08-28 Istanbul Metropolitan Municipality
+ist
+
+// istanbul : 2014-08-28 Istanbul Metropolitan Municipality
+istanbul
+
+// itau : 2014-10-02 Itau Unibanco Holding S.A.
+itau
+
+// itv : 2015-07-09 ITV Services Limited
+itv
+
+// iveco : 2015-09-03 CNH Industrial N.V.
+iveco
+
+// jaguar : 2014-11-13 Jaguar Land Rover Ltd
+jaguar
+
+// java : 2014-06-19 Oracle Corporation
+java
+
+// jcb : 2014-11-20 JCB Co., Ltd.
+jcb
+
+// jeep : 2015-07-30 FCA US LLC.
+jeep
+
+// jetzt : 2014-01-09 Binky Moon, LLC
+jetzt
+
+// jewelry : 2015-03-05 Binky Moon, LLC
+jewelry
+
+// jio : 2015-04-02 Reliance Industries Limited
+jio
+
+// jll : 2015-04-02 Jones Lang LaSalle Incorporated
+jll
+
+// jmp : 2015-03-26 Matrix IP LLC
+jmp
+
+// jnj : 2015-06-18 Johnson & Johnson Services, Inc.
+jnj
+
+// joburg : 2014-03-24 ZA Central Registry NPC trading as ZA Central Registry
+joburg
+
+// jot : 2014-12-18 Amazon Registry Services, Inc.
+jot
+
+// joy : 2014-12-18 Amazon Registry Services, Inc.
+joy
+
+// jpmorgan : 2015-04-30 JPMorgan Chase Bank, National Association
+jpmorgan
+
+// jprs : 2014-09-18 Japan Registry Services Co., Ltd.
+jprs
+
+// juegos : 2014-03-20 UNR Corp.
+juegos
+
+// juniper : 2015-07-30 JUNIPER NETWORKS, INC.
+juniper
+
+// kaufen : 2013-11-07 Dog Beach, LLC
+kaufen
+
+// kddi : 2014-09-12 KDDI CORPORATION
+kddi
+
+// kerryhotels : 2015-04-30 Kerry Trading Co. Limited
+kerryhotels
+
+// kerrylogistics : 2015-04-09 Kerry Trading Co. Limited
+kerrylogistics
+
+// kerryproperties : 2015-04-09 Kerry Trading Co. Limited
+kerryproperties
+
+// kfh : 2014-12-04 Kuwait Finance House
+kfh
+
+// kia : 2015-07-09 KIA MOTORS CORPORATION
+kia
+
+// kim : 2013-09-23 Afilias Limited
+kim
+
+// kinder : 2014-11-07 Ferrero Trading Lux S.A.
+kinder
+
+// kindle : 2015-06-25 Amazon Registry Services, Inc.
+kindle
+
+// kitchen : 2013-09-20 Binky Moon, LLC
+kitchen
+
+// kiwi : 2013-09-20 DOT KIWI LIMITED
+kiwi
+
+// koeln : 2014-01-09 dotKoeln GmbH
+koeln
+
+// komatsu : 2015-01-08 Komatsu Ltd.
+komatsu
+
+// kosher : 2015-08-20 Kosher Marketing Assets LLC
+kosher
+
+// kpmg : 2015-04-23 KPMG International Cooperative (KPMG International Genossenschaft)
+kpmg
+
+// kpn : 2015-01-08 Koninklijke KPN N.V.
+kpn
+
+// krd : 2013-12-05 KRG Department of Information Technology
+krd
+
+// kred : 2013-12-19 KredTLD Pty Ltd
+kred
+
+// kuokgroup : 2015-04-09 Kerry Trading Co. Limited
+kuokgroup
+
+// kyoto : 2014-11-07 Academic Institution: Kyoto Jyoho Gakuen
+kyoto
+
+// lacaixa : 2014-01-09 Fundación Bancaria Caixa d’Estalvis i Pensions de Barcelona, “la Caixa”
+lacaixa
+
+// lamborghini : 2015-06-04 Automobili Lamborghini S.p.A.
+lamborghini
+
+// lamer : 2015-10-01 The Estée Lauder Companies Inc.
+lamer
+
+// lancaster : 2015-02-12 LANCASTER
+lancaster
+
+// lancia : 2015-07-31 Fiat Chrysler Automobiles N.V.
+lancia
+
+// land : 2013-09-10 Binky Moon, LLC
+land
+
+// landrover : 2014-11-13 Jaguar Land Rover Ltd
+landrover
+
+// lanxess : 2015-07-30 LANXESS Corporation
+lanxess
+
+// lasalle : 2015-04-02 Jones Lang LaSalle Incorporated
+lasalle
+
+// lat : 2014-10-16 ECOM-LAC Federaciòn de Latinoamèrica y el Caribe para Internet y el Comercio Electrònico
+lat
+
+// latino : 2015-07-30 Dish DBS Corporation
+latino
+
+// latrobe : 2014-06-16 La Trobe University
+latrobe
+
+// law : 2015-01-22 LW TLD Limited
+law
+
+// lawyer : 2014-03-20 Dog Beach, LLC
+lawyer
+
+// lds : 2014-03-20 IRI Domain Management, LLC
+lds
+
+// lease : 2014-03-06 Binky Moon, LLC
+lease
+
+// leclerc : 2014-08-07 A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc
+leclerc
+
+// lefrak : 2015-07-16 LeFrak Organization, Inc.
+lefrak
+
+// legal : 2014-10-16 Binky Moon, LLC
+legal
+
+// lego : 2015-07-16 LEGO Juris A/S
+lego
+
+// lexus : 2015-04-23 TOYOTA MOTOR CORPORATION
+lexus
+
+// lgbt : 2014-05-08 Afilias Limited
+lgbt
+
+// lidl : 2014-09-18 Schwarz Domains und Services GmbH & Co. KG
+lidl
+
+// life : 2014-02-06 Binky Moon, LLC
+life
+
+// lifeinsurance : 2015-01-15 American Council of Life Insurers
+lifeinsurance
+
+// lifestyle : 2014-12-11 Lifestyle Domain Holdings, Inc.
+lifestyle
+
+// lighting : 2013-08-27 Binky Moon, LLC
+lighting
+
+// like : 2014-12-18 Amazon Registry Services, Inc.
+like
+
+// lilly : 2015-07-31 Eli Lilly and Company
+lilly
+
+// limited : 2014-03-06 Binky Moon, LLC
+limited
+
+// limo : 2013-10-17 Binky Moon, LLC
+limo
+
+// lincoln : 2014-11-13 Ford Motor Company
+lincoln
+
+// linde : 2014-12-04 Linde Aktiengesellschaft
+linde
+
+// link : 2013-11-14 UNR Corp.
+link
+
+// lipsy : 2015-06-25 Lipsy Ltd
+lipsy
+
+// live : 2014-12-04 Dog Beach, LLC
+live
+
+// living : 2015-07-30 Lifestyle Domain Holdings, Inc.
+living
+
+// lixil : 2015-03-19 LIXIL Group Corporation
+lixil
+
+// llc : 2017-12-14 Afilias Limited
+llc
+
+// llp : 2019-08-26 UNR Corp.
+llp
+
+// loan : 2014-11-20 dot Loan Limited
+loan
+
+// loans : 2014-03-20 Binky Moon, LLC
+loans
+
+// locker : 2015-06-04 Dish DBS Corporation
+locker
+
+// locus : 2015-06-25 Locus Analytics LLC
+locus
+
+// loft : 2015-07-30 Annco, Inc.
+loft
+
+// lol : 2015-01-30 UNR Corp.
+lol
+
+// london : 2013-11-14 Dot London Domains Limited
+london
+
+// lotte : 2014-11-07 Lotte Holdings Co., Ltd.
+lotte
+
+// lotto : 2014-04-10 Afilias Limited
+lotto
+
+// love : 2014-12-22 Merchant Law Group LLP
+love
+
+// lpl : 2015-07-30 LPL Holdings, Inc.
+lpl
+
+// lplfinancial : 2015-07-30 LPL Holdings, Inc.
+lplfinancial
+
+// ltd : 2014-09-25 Binky Moon, LLC
+ltd
+
+// ltda : 2014-04-17 InterNetX, Corp
+ltda
+
+// lundbeck : 2015-08-06 H. Lundbeck A/S
+lundbeck
+
+// luxe : 2014-01-09 Minds + Machines Group Limited
+luxe
+
+// luxury : 2013-10-17 Luxury Partners, LLC
+luxury
+
+// macys : 2015-07-31 Macys, Inc.
+macys
+
+// madrid : 2014-05-01 Comunidad de Madrid
+madrid
+
+// maif : 2014-10-02 Mutuelle Assurance Instituteur France (MAIF)
+maif
+
+// maison : 2013-12-05 Binky Moon, LLC
+maison
+
+// makeup : 2015-01-15 XYZ.COM LLC
+makeup
+
+// man : 2014-12-04 MAN SE
+man
+
+// management : 2013-11-07 Binky Moon, LLC
+management
+
+// mango : 2013-10-24 PUNTO FA S.L.
+mango
+
+// map : 2016-06-09 Charleston Road Registry Inc.
+map
+
+// market : 2014-03-06 Dog Beach, LLC
+market
+
+// marketing : 2013-11-07 Binky Moon, LLC
+marketing
+
+// markets : 2014-12-11 Dotmarkets Registry Limited
+markets
+
+// marriott : 2014-10-09 Marriott Worldwide Corporation
+marriott
+
+// marshalls : 2015-07-16 The TJX Companies, Inc.
+marshalls
+
+// maserati : 2015-07-31 Fiat Chrysler Automobiles N.V.
+maserati
+
+// mattel : 2015-08-06 Mattel Sites, Inc.
+mattel
+
+// mba : 2015-04-02 Binky Moon, LLC
+mba
+
+// mckinsey : 2015-07-31 McKinsey Holdings, Inc.
+mckinsey
+
+// med : 2015-08-06 Medistry LLC
+med
+
+// media : 2014-03-06 Binky Moon, LLC
+media
+
+// meet : 2014-01-16 Charleston Road Registry Inc.
+meet
+
+// melbourne : 2014-05-29 The Crown in right of the State of Victoria, represented by its Department of State Development, Business and Innovation
+melbourne
+
+// meme : 2014-01-30 Charleston Road Registry Inc.
+meme
+
+// memorial : 2014-10-16 Dog Beach, LLC
+memorial
+
+// men : 2015-02-26 Exclusive Registry Limited
+men
+
+// menu : 2013-09-11 Dot Menu Registry, LLC
+menu
+
+// merckmsd : 2016-07-14 MSD Registry Holdings, Inc.
+merckmsd
+
+// miami : 2013-12-19 Minds + Machines Group Limited
+miami
+
+// microsoft : 2014-12-18 Microsoft Corporation
+microsoft
+
+// mini : 2014-01-09 Bayerische Motoren Werke Aktiengesellschaft
+mini
+
+// mint : 2015-07-30 Intuit Administrative Services, Inc.
+mint
+
+// mit : 2015-07-02 Massachusetts Institute of Technology
+mit
+
+// mitsubishi : 2015-07-23 Mitsubishi Corporation
+mitsubishi
+
+// mlb : 2015-05-21 MLB Advanced Media DH, LLC
+mlb
+
+// mls : 2015-04-23 The Canadian Real Estate Association
+mls
+
+// mma : 2014-11-07 MMA IARD
+mma
+
+// mobile : 2016-06-02 Dish DBS Corporation
+mobile
+
+// moda : 2013-11-07 Dog Beach, LLC
+moda
+
+// moe : 2013-11-13 Interlink Co., Ltd.
+moe
+
+// moi : 2014-12-18 Amazon Registry Services, Inc.
+moi
+
+// mom : 2015-04-16 UNR Corp.
+mom
+
+// monash : 2013-09-30 Monash University
+monash
+
+// money : 2014-10-16 Binky Moon, LLC
+money
+
+// monster : 2015-09-11 XYZ.COM LLC
+monster
+
+// mormon : 2013-12-05 IRI Domain Management, LLC
+mormon
+
+// mortgage : 2014-03-20 Dog Beach, LLC
+mortgage
+
+// moscow : 2013-12-19 Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID)
+moscow
+
+// moto : 2015-06-04 Motorola Trademark Holdings, LLC
+moto
+
+// motorcycles : 2014-01-09 XYZ.COM LLC
+motorcycles
+
+// mov : 2014-01-30 Charleston Road Registry Inc.
+mov
+
+// movie : 2015-02-05 Binky Moon, LLC
+movie
+
+// msd : 2015-07-23 MSD Registry Holdings, Inc.
+msd
+
+// mtn : 2014-12-04 MTN Dubai Limited
+mtn
+
+// mtr : 2015-03-12 MTR Corporation Limited
+mtr
+
+// mutual : 2015-04-02 Northwestern Mutual MU TLD Registry, LLC
+mutual
+
+// nab : 2015-08-20 National Australia Bank Limited
+nab
+
+// nagoya : 2013-10-24 GMO Registry, Inc.
+nagoya
+
+// nationwide : 2015-07-23 Nationwide Mutual Insurance Company
+nationwide
+
+// natura : 2015-03-12 NATURA COSMÉTICOS S.A.
+natura
+
+// navy : 2014-03-06 Dog Beach, LLC
+navy
+
+// nba : 2015-07-31 NBA REGISTRY, LLC
+nba
+
+// nec : 2015-01-08 NEC Corporation
+nec
+
+// netbank : 2014-06-26 COMMONWEALTH BANK OF AUSTRALIA
+netbank
+
+// netflix : 2015-06-18 Netflix, Inc.
+netflix
+
+// network : 2013-11-14 Binky Moon, LLC
+network
+
+// neustar : 2013-12-05 NeuStar, Inc.
+neustar
+
+// new : 2014-01-30 Charleston Road Registry Inc.
+new
+
+// newholland : 2015-09-03 CNH Industrial N.V.
+newholland
+
+// news : 2014-12-18 Dog Beach, LLC
+news
+
+// next : 2015-06-18 Next plc
+next
+
+// nextdirect : 2015-06-18 Next plc
+nextdirect
+
+// nexus : 2014-07-24 Charleston Road Registry Inc.
+nexus
+
+// nfl : 2015-07-23 NFL Reg Ops LLC
+nfl
+
+// ngo : 2014-03-06 Public Interest Registry
+ngo
+
+// nhk : 2014-02-13 Japan Broadcasting Corporation (NHK)
+nhk
+
+// nico : 2014-12-04 DWANGO Co., Ltd.
+nico
+
+// nike : 2015-07-23 NIKE, Inc.
+nike
+
+// nikon : 2015-05-21 NIKON CORPORATION
+nikon
+
+// ninja : 2013-11-07 Dog Beach, LLC
+ninja
+
+// nissan : 2014-03-27 NISSAN MOTOR CO., LTD.
+nissan
+
+// nissay : 2015-10-29 Nippon Life Insurance Company
+nissay
+
+// nokia : 2015-01-08 Nokia Corporation
+nokia
+
+// northwesternmutual : 2015-06-18 Northwestern Mutual Registry, LLC
+northwesternmutual
+
+// norton : 2014-12-04 NortonLifeLock Inc.
+norton
+
+// now : 2015-06-25 Amazon Registry Services, Inc.
+now
+
+// nowruz : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+nowruz
+
+// nowtv : 2015-05-14 Starbucks (HK) Limited
+nowtv
+
+// nra : 2014-05-22 NRA Holdings Company, INC.
+nra
+
+// nrw : 2013-11-21 Minds + Machines GmbH
+nrw
+
+// ntt : 2014-10-31 NIPPON TELEGRAPH AND TELEPHONE CORPORATION
+ntt
+
+// nyc : 2014-01-23 The City of New York by and through the New York City Department of Information Technology & Telecommunications
+nyc
+
+// obi : 2014-09-25 OBI Group Holding SE & Co. KGaA
+obi
+
+// observer : 2015-04-30 Dog Beach, LLC
+observer
+
+// off : 2015-07-23 Johnson Shareholdings, Inc.
+off
+
+// office : 2015-03-12 Microsoft Corporation
+office
+
+// okinawa : 2013-12-05 BRregistry, Inc.
+okinawa
+
+// olayan : 2015-05-14 Crescent Holding GmbH
+olayan
+
+// olayangroup : 2015-05-14 Crescent Holding GmbH
+olayangroup
+
+// oldnavy : 2015-07-31 The Gap, Inc.
+oldnavy
+
+// ollo : 2015-06-04 Dish DBS Corporation
+ollo
+
+// omega : 2015-01-08 The Swatch Group Ltd
+omega
+
+// one : 2014-11-07 One.com A/S
+one
+
+// ong : 2014-03-06 Public Interest Registry
+ong
+
+// onl : 2013-09-16 iRegistry GmbH
+onl
+
+// online : 2015-01-15 DotOnline Inc.
+online
+
+// onyourside : 2015-07-23 Nationwide Mutual Insurance Company
+onyourside
+
+// ooo : 2014-01-09 INFIBEAM AVENUES LIMITED
+ooo
+
+// open : 2015-07-31 American Express Travel Related Services Company, Inc.
+open
+
+// oracle : 2014-06-19 Oracle Corporation
+oracle
+
+// orange : 2015-03-12 Orange Brand Services Limited
+orange
+
+// organic : 2014-03-27 Afilias Limited
+organic
+
+// origins : 2015-10-01 The Estée Lauder Companies Inc.
+origins
+
+// osaka : 2014-09-04 Osaka Registry Co., Ltd.
+osaka
+
+// otsuka : 2013-10-11 Otsuka Holdings Co., Ltd.
+otsuka
+
+// ott : 2015-06-04 Dish DBS Corporation
+ott
+
+// ovh : 2014-01-16 MédiaBC
+ovh
+
+// page : 2014-12-04 Charleston Road Registry Inc.
+page
+
+// panasonic : 2015-07-30 Panasonic Corporation
+panasonic
+
+// paris : 2014-01-30 City of Paris
+paris
+
+// pars : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+pars
+
+// partners : 2013-12-05 Binky Moon, LLC
+partners
+
+// parts : 2013-12-05 Binky Moon, LLC
+parts
+
+// party : 2014-09-11 Blue Sky Registry Limited
+party
+
+// passagens : 2015-03-05 Travel Reservations SRL
+passagens
+
+// pay : 2015-08-27 Amazon Registry Services, Inc.
+pay
+
+// pccw : 2015-05-14 PCCW Enterprises Limited
+pccw
+
+// pet : 2015-05-07 Afilias Limited
+pet
+
+// pfizer : 2015-09-11 Pfizer Inc.
+pfizer
+
+// pharmacy : 2014-06-19 National Association of Boards of Pharmacy
+pharmacy
+
+// phd : 2016-07-28 Charleston Road Registry Inc.
+phd
+
+// philips : 2014-11-07 Koninklijke Philips N.V.
+philips
+
+// phone : 2016-06-02 Dish DBS Corporation
+phone
+
+// photo : 2013-11-14 UNR Corp.
+photo
+
+// photography : 2013-09-20 Binky Moon, LLC
+photography
+
+// photos : 2013-10-17 Binky Moon, LLC
+photos
+
+// physio : 2014-05-01 PhysBiz Pty Ltd
+physio
+
+// pics : 2013-11-14 UNR Corp.
+pics
+
+// pictet : 2014-06-26 Pictet Europe S.A.
+pictet
+
+// pictures : 2014-03-06 Binky Moon, LLC
+pictures
+
+// pid : 2015-01-08 Top Level Spectrum, Inc.
+pid
+
+// pin : 2014-12-18 Amazon Registry Services, Inc.
+pin
+
+// ping : 2015-06-11 Ping Registry Provider, Inc.
+ping
+
+// pink : 2013-10-01 Afilias Limited
+pink
+
+// pioneer : 2015-07-16 Pioneer Corporation
+pioneer
+
+// pizza : 2014-06-26 Binky Moon, LLC
+pizza
+
+// place : 2014-04-24 Binky Moon, LLC
+place
+
+// play : 2015-03-05 Charleston Road Registry Inc.
+play
+
+// playstation : 2015-07-02 Sony Interactive Entertainment Inc.
+playstation
+
+// plumbing : 2013-09-10 Binky Moon, LLC
+plumbing
+
+// plus : 2015-02-05 Binky Moon, LLC
+plus
+
+// pnc : 2015-07-02 PNC Domain Co., LLC
+pnc
+
+// pohl : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG
+pohl
+
+// poker : 2014-07-03 Afilias Limited
+poker
+
+// politie : 2015-08-20 Politie Nederland
+politie
+
+// porn : 2014-10-16 ICM Registry PN LLC
+porn
+
+// pramerica : 2015-07-30 Prudential Financial, Inc.
+pramerica
+
+// praxi : 2013-12-05 Praxi S.p.A.
+praxi
+
+// press : 2014-04-03 DotPress Inc.
+press
+
+// prime : 2015-06-25 Amazon Registry Services, Inc.
+prime
+
+// prod : 2014-01-23 Charleston Road Registry Inc.
+prod
+
+// productions : 2013-12-05 Binky Moon, LLC
+productions
+
+// prof : 2014-07-24 Charleston Road Registry Inc.
+prof
+
+// progressive : 2015-07-23 Progressive Casualty Insurance Company
+progressive
+
+// promo : 2014-12-18 Afilias Limited
+promo
+
+// properties : 2013-12-05 Binky Moon, LLC
+properties
+
+// property : 2014-05-22 UNR Corp.
+property
+
+// protection : 2015-04-23 XYZ.COM LLC
+protection
+
+// pru : 2015-07-30 Prudential Financial, Inc.
+pru
+
+// prudential : 2015-07-30 Prudential Financial, Inc.
+prudential
+
+// pub : 2013-12-12 Dog Beach, LLC
+pub
+
+// pwc : 2015-10-29 PricewaterhouseCoopers LLP
+pwc
+
+// qpon : 2013-11-14 dotCOOL, Inc.
+qpon
+
+// quebec : 2013-12-19 PointQuébec Inc
+quebec
+
+// quest : 2015-03-26 XYZ.COM LLC
+quest
+
+// qvc : 2015-07-30 QVC, Inc.
+qvc
+
+// racing : 2014-12-04 Premier Registry Limited
+racing
+
+// radio : 2016-07-21 European Broadcasting Union (EBU)
+radio
+
+// raid : 2015-07-23 Johnson Shareholdings, Inc.
+raid
+
+// read : 2014-12-18 Amazon Registry Services, Inc.
+read
+
+// realestate : 2015-09-11 dotRealEstate LLC
+realestate
+
+// realtor : 2014-05-29 Real Estate Domains LLC
+realtor
+
+// realty : 2015-03-19 Dog Beach, LLC
+realty
+
+// recipes : 2013-10-17 Binky Moon, LLC
+recipes
+
+// red : 2013-11-07 Afilias Limited
+red
+
+// redstone : 2014-10-31 Redstone Haute Couture Co., Ltd.
+redstone
+
+// redumbrella : 2015-03-26 Travelers TLD, LLC
+redumbrella
+
+// rehab : 2014-03-06 Dog Beach, LLC
+rehab
+
+// reise : 2014-03-13 Binky Moon, LLC
+reise
+
+// reisen : 2014-03-06 Binky Moon, LLC
+reisen
+
+// reit : 2014-09-04 National Association of Real Estate Investment Trusts, Inc.
+reit
+
+// reliance : 2015-04-02 Reliance Industries Limited
+reliance
+
+// ren : 2013-12-12 ZDNS International Limited
+ren
+
+// rent : 2014-12-04 XYZ.COM LLC
+rent
+
+// rentals : 2013-12-05 Binky Moon, LLC
+rentals
+
+// repair : 2013-11-07 Binky Moon, LLC
+repair
+
+// report : 2013-12-05 Binky Moon, LLC
+report
+
+// republican : 2014-03-20 Dog Beach, LLC
+republican
+
+// rest : 2013-12-19 Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable
+rest
+
+// restaurant : 2014-07-03 Binky Moon, LLC
+restaurant
+
+// review : 2014-11-20 dot Review Limited
+review
+
+// reviews : 2013-09-13 Dog Beach, LLC
+reviews
+
+// rexroth : 2015-06-18 Robert Bosch GMBH
+rexroth
+
+// rich : 2013-11-21 iRegistry GmbH
+rich
+
+// richardli : 2015-05-14 Pacific Century Asset Management (HK) Limited
+richardli
+
+// ricoh : 2014-11-20 Ricoh Company, Ltd.
+ricoh
+
+// ril : 2015-04-02 Reliance Industries Limited
+ril
+
+// rio : 2014-02-27 Empresa Municipal de Informática SA - IPLANRIO
+rio
+
+// rip : 2014-07-10 Dog Beach, LLC
+rip
+
+// rmit : 2015-11-19 Royal Melbourne Institute of Technology
+rmit
+
+// rocher : 2014-12-18 Ferrero Trading Lux S.A.
+rocher
+
+// rocks : 2013-11-14 Dog Beach, LLC
+rocks
+
+// rodeo : 2013-12-19 Minds + Machines Group Limited
+rodeo
+
+// rogers : 2015-08-06 Rogers Communications Canada Inc.
+rogers
+
+// room : 2014-12-18 Amazon Registry Services, Inc.
+room
+
+// rsvp : 2014-05-08 Charleston Road Registry Inc.
+rsvp
+
+// rugby : 2016-12-15 World Rugby Strategic Developments Limited
+rugby
+
+// ruhr : 2013-10-02 regiodot GmbH & Co. KG
+ruhr
+
+// run : 2015-03-19 Binky Moon, LLC
+run
+
+// rwe : 2015-04-02 RWE AG
+rwe
+
+// ryukyu : 2014-01-09 BRregistry, Inc.
+ryukyu
+
+// saarland : 2013-12-12 dotSaarland GmbH
+saarland
+
+// safe : 2014-12-18 Amazon Registry Services, Inc.
+safe
+
+// safety : 2015-01-08 Safety Registry Services, LLC.
+safety
+
+// sakura : 2014-12-18 SAKURA Internet Inc.
+sakura
+
+// sale : 2014-10-16 Dog Beach, LLC
+sale
+
+// salon : 2014-12-11 Binky Moon, LLC
+salon
+
+// samsclub : 2015-07-31 Wal-Mart Stores, Inc.
+samsclub
+
+// samsung : 2014-04-03 SAMSUNG SDS CO., LTD
+samsung
+
+// sandvik : 2014-11-13 Sandvik AB
+sandvik
+
+// sandvikcoromant : 2014-11-07 Sandvik AB
+sandvikcoromant
+
+// sanofi : 2014-10-09 Sanofi
+sanofi
+
+// sap : 2014-03-27 SAP AG
+sap
+
+// sarl : 2014-07-03 Binky Moon, LLC
+sarl
+
+// sas : 2015-04-02 Research IP LLC
+sas
+
+// save : 2015-06-25 Amazon Registry Services, Inc.
+save
+
+// saxo : 2014-10-31 Saxo Bank A/S
+saxo
+
+// sbi : 2015-03-12 STATE BANK OF INDIA
+sbi
+
+// sbs : 2014-11-07 SPECIAL BROADCASTING SERVICE CORPORATION
+sbs
+
+// sca : 2014-03-13 SVENSKA CELLULOSA AKTIEBOLAGET SCA (publ)
+sca
+
+// scb : 2014-02-20 The Siam Commercial Bank Public Company Limited ("SCB")
+scb
+
+// schaeffler : 2015-08-06 Schaeffler Technologies AG & Co. KG
+schaeffler
+
+// schmidt : 2014-04-03 SCHMIDT GROUPE S.A.S.
+schmidt
+
+// scholarships : 2014-04-24 Scholarships.com, LLC
+scholarships
+
+// school : 2014-12-18 Binky Moon, LLC
+school
+
+// schule : 2014-03-06 Binky Moon, LLC
+schule
+
+// schwarz : 2014-09-18 Schwarz Domains und Services GmbH & Co. KG
+schwarz
+
+// science : 2014-09-11 dot Science Limited
+science
+
+// scjohnson : 2015-07-23 Johnson Shareholdings, Inc.
+scjohnson
+
+// scot : 2014-01-23 Dot Scot Registry Limited
+scot
+
+// search : 2016-06-09 Charleston Road Registry Inc.
+search
+
+// seat : 2014-05-22 SEAT, S.A. (Sociedad Unipersonal)
+seat
+
+// secure : 2015-08-27 Amazon Registry Services, Inc.
+secure
+
+// security : 2015-05-14 XYZ.COM LLC
+security
+
+// seek : 2014-12-04 Seek Limited
+seek
+
+// select : 2015-10-08 Registry Services, LLC
+select
+
+// sener : 2014-10-24 Sener Ingeniería y Sistemas, S.A.
+sener
+
+// services : 2014-02-27 Binky Moon, LLC
+services
+
+// ses : 2015-07-23 SES
+ses
+
+// seven : 2015-08-06 Seven West Media Ltd
+seven
+
+// sew : 2014-07-17 SEW-EURODRIVE GmbH & Co KG
+sew
+
+// sex : 2014-11-13 ICM Registry SX LLC
+sex
+
+// sexy : 2013-09-11 UNR Corp.
+sexy
+
+// sfr : 2015-08-13 Societe Francaise du Radiotelephone - SFR
+sfr
+
+// shangrila : 2015-09-03 Shangri‐La International Hotel Management Limited
+shangrila
+
+// sharp : 2014-05-01 Sharp Corporation
+sharp
+
+// shaw : 2015-04-23 Shaw Cablesystems G.P.
+shaw
+
+// shell : 2015-07-30 Shell Information Technology International Inc
+shell
+
+// shia : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+shia
+
+// shiksha : 2013-11-14 Afilias Limited
+shiksha
+
+// shoes : 2013-10-02 Binky Moon, LLC
+shoes
+
+// shop : 2016-04-08 GMO Registry, Inc.
+shop
+
+// shopping : 2016-03-31 Binky Moon, LLC
+shopping
+
+// shouji : 2015-01-08 Beijing Qihu Keji Co., Ltd.
+shouji
+
+// show : 2015-03-05 Binky Moon, LLC
+show
+
+// showtime : 2015-08-06 CBS Domains Inc.
+showtime
+
+// silk : 2015-06-25 Amazon Registry Services, Inc.
+silk
+
+// sina : 2015-03-12 Sina Corporation
+sina
+
+// singles : 2013-08-27 Binky Moon, LLC
+singles
+
+// site : 2015-01-15 DotSite Inc.
+site
+
+// ski : 2015-04-09 Afilias Limited
+ski
+
+// skin : 2015-01-15 XYZ.COM LLC
+skin
+
+// sky : 2014-06-19 Sky International AG
+sky
+
+// skype : 2014-12-18 Microsoft Corporation
+skype
+
+// sling : 2015-07-30 DISH Technologies L.L.C.
+sling
+
+// smart : 2015-07-09 Smart Communications, Inc. (SMART)
+smart
+
+// smile : 2014-12-18 Amazon Registry Services, Inc.
+smile
+
+// sncf : 2015-02-19 Société Nationale des Chemins de fer Francais S N C F
+sncf
+
+// soccer : 2015-03-26 Binky Moon, LLC
+soccer
+
+// social : 2013-11-07 Dog Beach, LLC
+social
+
+// softbank : 2015-07-02 SoftBank Group Corp.
+softbank
+
+// software : 2014-03-20 Dog Beach, LLC
+software
+
+// sohu : 2013-12-19 Sohu.com Limited
+sohu
+
+// solar : 2013-11-07 Binky Moon, LLC
+solar
+
+// solutions : 2013-11-07 Binky Moon, LLC
+solutions
+
+// song : 2015-02-26 Amazon Registry Services, Inc.
+song
+
+// sony : 2015-01-08 Sony Corporation
+sony
+
+// soy : 2014-01-23 Charleston Road Registry Inc.
+soy
+
+// spa : 2019-09-19 Asia Spa and Wellness Promotion Council Limited
+spa
+
+// space : 2014-04-03 DotSpace Inc.
+space
+
+// sport : 2017-11-16 Global Association of International Sports Federations (GAISF)
+sport
+
+// spot : 2015-02-26 Amazon Registry Services, Inc.
+spot
+
+// spreadbetting : 2014-12-11 Dotspreadbetting Registry Limited
+spreadbetting
+
+// srl : 2015-05-07 InterNetX, Corp
+srl
+
+// stada : 2014-11-13 STADA Arzneimittel AG
+stada
+
+// staples : 2015-07-30 Staples, Inc.
+staples
+
+// star : 2015-01-08 Star India Private Limited
+star
+
+// statebank : 2015-03-12 STATE BANK OF INDIA
+statebank
+
+// statefarm : 2015-07-30 State Farm Mutual Automobile Insurance Company
+statefarm
+
+// stc : 2014-10-09 Saudi Telecom Company
+stc
+
+// stcgroup : 2014-10-09 Saudi Telecom Company
+stcgroup
+
+// stockholm : 2014-12-18 Stockholms kommun
+stockholm
+
+// storage : 2014-12-22 XYZ.COM LLC
+storage
+
+// store : 2015-04-09 DotStore Inc.
+store
+
+// stream : 2016-01-08 dot Stream Limited
+stream
+
+// studio : 2015-02-11 Dog Beach, LLC
+studio
+
+// study : 2014-12-11 OPEN UNIVERSITIES AUSTRALIA PTY LTD
+study
+
+// style : 2014-12-04 Binky Moon, LLC
+style
+
+// sucks : 2014-12-22 Vox Populi Registry Ltd.
+sucks
+
+// supplies : 2013-12-19 Binky Moon, LLC
+supplies
+
+// supply : 2013-12-19 Binky Moon, LLC
+supply
+
+// support : 2013-10-24 Binky Moon, LLC
+support
+
+// surf : 2014-01-09 Minds + Machines Group Limited
+surf
+
+// surgery : 2014-03-20 Binky Moon, LLC
+surgery
+
+// suzuki : 2014-02-20 SUZUKI MOTOR CORPORATION
+suzuki
+
+// swatch : 2015-01-08 The Swatch Group Ltd
+swatch
+
+// swiftcover : 2015-07-23 Swiftcover Insurance Services Limited
+swiftcover
+
+// swiss : 2014-10-16 Swiss Confederation
+swiss
+
+// sydney : 2014-09-18 State of New South Wales, Department of Premier and Cabinet
+sydney
+
+// systems : 2013-11-07 Binky Moon, LLC
+systems
+
+// tab : 2014-12-04 Tabcorp Holdings Limited
+tab
+
+// taipei : 2014-07-10 Taipei City Government
+taipei
+
+// talk : 2015-04-09 Amazon Registry Services, Inc.
+talk
+
+// taobao : 2015-01-15 Alibaba Group Holding Limited
+taobao
+
+// target : 2015-07-31 Target Domain Holdings, LLC
+target
+
+// tatamotors : 2015-03-12 Tata Motors Ltd
+tatamotors
+
+// tatar : 2014-04-24 Limited Liability Company "Coordination Center of Regional Domain of Tatarstan Republic"
+tatar
+
+// tattoo : 2013-08-30 UNR Corp.
+tattoo
+
+// tax : 2014-03-20 Binky Moon, LLC
+tax
+
+// taxi : 2015-03-19 Binky Moon, LLC
+taxi
+
+// tci : 2014-09-12 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+tci
+
+// tdk : 2015-06-11 TDK Corporation
+tdk
+
+// team : 2015-03-05 Binky Moon, LLC
+team
+
+// tech : 2015-01-30 Personals TLD Inc.
+tech
+
+// technology : 2013-09-13 Binky Moon, LLC
+technology
+
+// temasek : 2014-08-07 Temasek Holdings (Private) Limited
+temasek
+
+// tennis : 2014-12-04 Binky Moon, LLC
+tennis
+
+// teva : 2015-07-02 Teva Pharmaceutical Industries Limited
+teva
+
+// thd : 2015-04-02 Home Depot Product Authority, LLC
+thd
+
+// theater : 2015-03-19 Binky Moon, LLC
+theater
+
+// theatre : 2015-05-07 XYZ.COM LLC
+theatre
+
+// tiaa : 2015-07-23 Teachers Insurance and Annuity Association of America
+tiaa
+
+// tickets : 2015-02-05 Accent Media Limited
+tickets
+
+// tienda : 2013-11-14 Binky Moon, LLC
+tienda
+
+// tiffany : 2015-01-30 Tiffany and Company
+tiffany
+
+// tips : 2013-09-20 Binky Moon, LLC
+tips
+
+// tires : 2014-11-07 Binky Moon, LLC
+tires
+
+// tirol : 2014-04-24 punkt Tirol GmbH
+tirol
+
+// tjmaxx : 2015-07-16 The TJX Companies, Inc.
+tjmaxx
+
+// tjx : 2015-07-16 The TJX Companies, Inc.
+tjx
+
+// tkmaxx : 2015-07-16 The TJX Companies, Inc.
+tkmaxx
+
+// tmall : 2015-01-15 Alibaba Group Holding Limited
+tmall
+
+// today : 2013-09-20 Binky Moon, LLC
+today
+
+// tokyo : 2013-11-13 GMO Registry, Inc.
+tokyo
+
+// tools : 2013-11-21 Binky Moon, LLC
+tools
+
+// top : 2014-03-20 .TOP Registry
+top
+
+// toray : 2014-12-18 Toray Industries, Inc.
+toray
+
+// toshiba : 2014-04-10 TOSHIBA Corporation
+toshiba
+
+// total : 2015-08-06 Total SA
+total
+
+// tours : 2015-01-22 Binky Moon, LLC
+tours
+
+// town : 2014-03-06 Binky Moon, LLC
+town
+
+// toyota : 2015-04-23 TOYOTA MOTOR CORPORATION
+toyota
+
+// toys : 2014-03-06 Binky Moon, LLC
+toys
+
+// trade : 2014-01-23 Elite Registry Limited
+trade
+
+// trading : 2014-12-11 Dottrading Registry Limited
+trading
+
+// training : 2013-11-07 Binky Moon, LLC
+training
+
+// travel : 2015-10-09 Dog Beach, LLC
+travel
+
+// travelchannel : 2015-07-02 Lifestyle Domain Holdings, Inc.
+travelchannel
+
+// travelers : 2015-03-26 Travelers TLD, LLC
+travelers
+
+// travelersinsurance : 2015-03-26 Travelers TLD, LLC
+travelersinsurance
+
+// trust : 2014-10-16 UNR Corp.
+trust
+
+// trv : 2015-03-26 Travelers TLD, LLC
+trv
+
+// tube : 2015-06-11 Latin American Telecom LLC
+tube
+
+// tui : 2014-07-03 TUI AG
+tui
+
+// tunes : 2015-02-26 Amazon Registry Services, Inc.
+tunes
+
+// tushu : 2014-12-18 Amazon Registry Services, Inc.
+tushu
+
+// tvs : 2015-02-19 T V SUNDRAM IYENGAR & SONS LIMITED
+tvs
+
+// ubank : 2015-08-20 National Australia Bank Limited
+ubank
+
+// ubs : 2014-12-11 UBS AG
+ubs
+
+// unicom : 2015-10-15 China United Network Communications Corporation Limited
+unicom
+
+// university : 2014-03-06 Binky Moon, LLC
+university
+
+// uno : 2013-09-11 DotSite Inc.
+uno
+
+// uol : 2014-05-01 UBN INTERNET LTDA.
+uol
+
+// ups : 2015-06-25 UPS Market Driver, Inc.
+ups
+
+// vacations : 2013-12-05 Binky Moon, LLC
+vacations
+
+// vana : 2014-12-11 Lifestyle Domain Holdings, Inc.
+vana
+
+// vanguard : 2015-09-03 The Vanguard Group, Inc.
+vanguard
+
+// vegas : 2014-01-16 Dot Vegas, Inc.
+vegas
+
+// ventures : 2013-08-27 Binky Moon, LLC
+ventures
+
+// verisign : 2015-08-13 VeriSign, Inc.
+verisign
+
+// versicherung : 2014-03-20 tldbox GmbH
+versicherung
+
+// vet : 2014-03-06 Dog Beach, LLC
+vet
+
+// viajes : 2013-10-17 Binky Moon, LLC
+viajes
+
+// video : 2014-10-16 Dog Beach, LLC
+video
+
+// vig : 2015-05-14 VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe
+vig
+
+// viking : 2015-04-02 Viking River Cruises (Bermuda) Ltd.
+viking
+
+// villas : 2013-12-05 Binky Moon, LLC
+villas
+
+// vin : 2015-06-18 Binky Moon, LLC
+vin
+
+// vip : 2015-01-22 Minds + Machines Group Limited
+vip
+
+// virgin : 2014-09-25 Virgin Enterprises Limited
+virgin
+
+// visa : 2015-07-30 Visa Worldwide Pte. Limited
+visa
+
+// vision : 2013-12-05 Binky Moon, LLC
+vision
+
+// viva : 2014-11-07 Saudi Telecom Company
+viva
+
+// vivo : 2015-07-31 Telefonica Brasil S.A.
+vivo
+
+// vlaanderen : 2014-02-06 DNS.be vzw
+vlaanderen
+
+// vodka : 2013-12-19 Minds + Machines Group Limited
+vodka
+
+// volkswagen : 2015-05-14 Volkswagen Group of America Inc.
+volkswagen
+
+// volvo : 2015-11-12 Volvo Holding Sverige Aktiebolag
+volvo
+
+// vote : 2013-11-21 Monolith Registry LLC
+vote
+
+// voting : 2013-11-13 Valuetainment Corp.
+voting
+
+// voto : 2013-11-21 Monolith Registry LLC
+voto
+
+// voyage : 2013-08-27 Binky Moon, LLC
+voyage
+
+// vuelos : 2015-03-05 Travel Reservations SRL
+vuelos
+
+// wales : 2014-05-08 Nominet UK
+wales
+
+// walmart : 2015-07-31 Wal-Mart Stores, Inc.
+walmart
+
+// walter : 2014-11-13 Sandvik AB
+walter
+
+// wang : 2013-10-24 Zodiac Wang Limited
+wang
+
+// wanggou : 2014-12-18 Amazon Registry Services, Inc.
+wanggou
+
+// watch : 2013-11-14 Binky Moon, LLC
+watch
+
+// watches : 2014-12-22 Richemont DNS Inc.
+watches
+
+// weather : 2015-01-08 International Business Machines Corporation
+weather
+
+// weatherchannel : 2015-03-12 International Business Machines Corporation
+weatherchannel
+
+// webcam : 2014-01-23 dot Webcam Limited
+webcam
+
+// weber : 2015-06-04 Saint-Gobain Weber SA
+weber
+
+// website : 2014-04-03 DotWebsite Inc.
+website
+
+// wedding : 2014-04-24 Minds + Machines Group Limited
+wedding
+
+// weibo : 2015-03-05 Sina Corporation
+weibo
+
+// weir : 2015-01-29 Weir Group IP Limited
+weir
+
+// whoswho : 2014-02-20 Who's Who Registry
+whoswho
+
+// wien : 2013-10-28 punkt.wien GmbH
+wien
+
+// wiki : 2013-11-07 Top Level Design, LLC
+wiki
+
+// williamhill : 2014-03-13 William Hill Organization Limited
+williamhill
+
+// win : 2014-11-20 First Registry Limited
+win
+
+// windows : 2014-12-18 Microsoft Corporation
+windows
+
+// wine : 2015-06-18 Binky Moon, LLC
+wine
+
+// winners : 2015-07-16 The TJX Companies, Inc.
+winners
+
+// wme : 2014-02-13 William Morris Endeavor Entertainment, LLC
+wme
+
+// wolterskluwer : 2015-08-06 Wolters Kluwer N.V.
+wolterskluwer
+
+// woodside : 2015-07-09 Woodside Petroleum Limited
+woodside
+
+// work : 2013-12-19 Minds + Machines Group Limited
+work
+
+// works : 2013-11-14 Binky Moon, LLC
+works
+
+// world : 2014-06-12 Binky Moon, LLC
+world
+
+// wow : 2015-10-08 Amazon Registry Services, Inc.
+wow
+
+// wtc : 2013-12-19 World Trade Centers Association, Inc.
+wtc
+
+// wtf : 2014-03-06 Binky Moon, LLC
+wtf
+
+// xbox : 2014-12-18 Microsoft Corporation
+xbox
+
+// xerox : 2014-10-24 Xerox DNHC LLC
+xerox
+
+// xfinity : 2015-07-09 Comcast IP Holdings I, LLC
+xfinity
+
+// xihuan : 2015-01-08 Beijing Qihu Keji Co., Ltd.
+xihuan
+
+// xin : 2014-12-11 Elegant Leader Limited
+xin
+
+// xn--11b4c3d : 2015-01-15 VeriSign Sarl
+कॉम
+
+// xn--1ck2e1b : 2015-02-26 Amazon Registry Services, Inc.
+セール
+
+// xn--1qqw23a : 2014-01-09 Guangzhou YU Wei Information Technology Co., Ltd.
+佛山
+
+// xn--30rr7y : 2014-06-12 Excellent First Limited
+慈善
+
+// xn--3bst00m : 2013-09-13 Eagle Horizon Limited
+集团
+
+// xn--3ds443g : 2013-09-08 TLD REGISTRY LIMITED OY
+在线
+
+// xn--3oq18vl8pn36a : 2015-07-02 Volkswagen (China) Investment Co., Ltd.
+大众汽车
+
+// xn--3pxu8k : 2015-01-15 VeriSign Sarl
+点看
+
+// xn--42c2d9a : 2015-01-15 VeriSign Sarl
+คอม
+
+// xn--45q11c : 2013-11-21 Zodiac Gemini Ltd
+八卦
+
+// xn--4gbrim : 2013-10-04 Fans TLD Limited
+موقع
+
+// xn--55qw42g : 2013-11-08 China Organizational Name Administration Center
+公益
+
+// xn--55qx5d : 2013-11-14 China Internet Network Information Center (CNNIC)
+公司
+
+// xn--5su34j936bgsg : 2015-09-03 Shangri‐La International Hotel Management Limited
+香格里拉
+
+// xn--5tzm5g : 2014-12-22 Global Website TLD Asia Limited
+网站
+
+// xn--6frz82g : 2013-09-23 Afilias Limited
+移动
+
+// xn--6qq986b3xl : 2013-09-13 Tycoon Treasure Limited
+我爱你
+
+// xn--80adxhks : 2013-12-19 Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID)
+москва
+
+// xn--80aqecdr1a : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication)
+католик
+
+// xn--80asehdb : 2013-07-14 CORE Association
+онлайн
+
+// xn--80aswg : 2013-07-14 CORE Association
+сайт
+
+// xn--8y0a063a : 2015-03-26 China United Network Communications Corporation Limited
+联通
+
+// xn--9dbq2a : 2015-01-15 VeriSign Sarl
+קום
+
+// xn--9et52u : 2014-06-12 RISE VICTORY LIMITED
+时尚
+
+// xn--9krt00a : 2015-03-12 Sina Corporation
+微博
+
+// xn--b4w605ferd : 2014-08-07 Temasek Holdings (Private) Limited
+淡马锡
+
+// xn--bck1b9a5dre4c : 2015-02-26 Amazon Registry Services, Inc.
+ファッション
+
+// xn--c1avg : 2013-11-14 Public Interest Registry
+орг
+
+// xn--c2br7g : 2015-01-15 VeriSign Sarl
+नेट
+
+// xn--cck2b3b : 2015-02-26 Amazon Registry Services, Inc.
+ストア
+
+// xn--cckwcxetd : 2019-12-19 Amazon Registry Services, Inc.
+アマゾン
+
+// xn--cg4bki : 2013-09-27 SAMSUNG SDS CO., LTD
+삼성
+
+// xn--czr694b : 2014-01-16 Internet DotTrademark Organisation Limited
+商标
+
+// xn--czrs0t : 2013-12-19 Binky Moon, LLC
+商店
+
+// xn--czru2d : 2013-11-21 Zodiac Aquarius Limited
+商城
+
+// xn--d1acj3b : 2013-11-20 The Foundation for Network Initiatives “The Smart Internet”
+дети
+
+// xn--eckvdtc9d : 2014-12-18 Amazon Registry Services, Inc.
+ポイント
+
+// xn--efvy88h : 2014-08-22 Guangzhou YU Wei Information Technology Co., Ltd.
+新闻
+
+// xn--fct429k : 2015-04-09 Amazon Registry Services, Inc.
+家電
+
+// xn--fhbei : 2015-01-15 VeriSign Sarl
+كوم
+
+// xn--fiq228c5hs : 2013-09-08 TLD REGISTRY LIMITED OY
+中文网
+
+// xn--fiq64b : 2013-10-14 CITIC Group Corporation
+中信
+
+// xn--fjq720a : 2014-05-22 Binky Moon, LLC
+娱乐
+
+// xn--flw351e : 2014-07-31 Charleston Road Registry Inc.
+谷歌
+
+// xn--fzys8d69uvgm : 2015-05-14 PCCW Enterprises Limited
+電訊盈科
+
+// xn--g2xx48c : 2015-01-30 Nawang Heli(Xiamen) Network Service Co., LTD.
+购物
+
+// xn--gckr3f0f : 2015-02-26 Amazon Registry Services, Inc.
+クラウド
+
+// xn--gk3at1e : 2015-10-08 Amazon Registry Services, Inc.
+通販
+
+// xn--hxt814e : 2014-05-15 Zodiac Taurus Limited
+网店
+
+// xn--i1b6b1a6a2e : 2013-11-14 Public Interest Registry
+संगठन
+
+// xn--imr513n : 2014-12-11 Internet DotTrademark Organisation Limited
+餐厅
+
+// xn--io0a7i : 2013-11-14 China Internet Network Information Center (CNNIC)
+网络
+
+// xn--j1aef : 2015-01-15 VeriSign Sarl
+ком
+
+// xn--jlq480n2rg : 2019-12-19 Amazon Registry Services, Inc.
+亚马逊
+
+// xn--jlq61u9w7b : 2015-01-08 Nokia Corporation
+诺基亚
+
+// xn--jvr189m : 2015-02-26 Amazon Registry Services, Inc.
+食品
+
+// xn--kcrx77d1x4a : 2014-11-07 Koninklijke Philips N.V.
+飞利浦
+
+// xn--kput3i : 2014-02-13 Beijing RITT-Net Technology Development Co., Ltd
+手机
+
+// xn--mgba3a3ejt : 2014-11-20 Aramco Services Company
+ارامكو
+
+// xn--mgba7c0bbn0a : 2015-05-14 Crescent Holding GmbH
+العليان
+
+// xn--mgbaakc7dvf : 2015-09-03 Emirates Telecommunications Corporation (trading as Etisalat)
+اتصالات
+
+// xn--mgbab2bd : 2013-10-31 CORE Association
+بازار
+
+// xn--mgbca7dzdo : 2015-07-30 Abu Dhabi Systems and Information Centre
+ابوظبي
+
+// xn--mgbi4ecexp : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication)
+كاثوليك
+
+// xn--mgbt3dhd : 2014-09-04 Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti.
+همراه
+
+// xn--mk1bu44c : 2015-01-15 VeriSign Sarl
+닷컴
+
+// xn--mxtq1m : 2014-03-06 Net-Chinese Co., Ltd.
+政府
+
+// xn--ngbc5azd : 2013-07-13 International Domain Registry Pty. Ltd.
+شبكة
+
+// xn--ngbe9e0a : 2014-12-04 Kuwait Finance House
+بيتك
+
+// xn--ngbrx : 2015-11-12 League of Arab States
+عرب
+
+// xn--nqv7f : 2013-11-14 Public Interest Registry
+机构
+
+// xn--nqv7fs00ema : 2013-11-14 Public Interest Registry
+组织机构
+
+// xn--nyqy26a : 2014-11-07 Stable Tone Limited
+健康
+
+// xn--otu796d : 2017-08-06 Jiang Yu Liang Cai Technology Company Limited
+招聘
+
+// xn--p1acf : 2013-12-12 Rusnames Limited
+рус
+
+// xn--pssy2u : 2015-01-15 VeriSign Sarl
+大拿
+
+// xn--q9jyb4c : 2013-09-17 Charleston Road Registry Inc.
+みんな
+
+// xn--qcka1pmc : 2014-07-31 Charleston Road Registry Inc.
+グーグル
+
+// xn--rhqv96g : 2013-09-11 Stable Tone Limited
+世界
+
+// xn--rovu88b : 2015-02-26 Amazon Registry Services, Inc.
+書籍
+
+// xn--ses554g : 2014-01-16 KNET Co., Ltd.
+网址
+
+// xn--t60b56a : 2015-01-15 VeriSign Sarl
+닷넷
+
+// xn--tckwe : 2015-01-15 VeriSign Sarl
+コム
+
+// xn--tiq49xqyj : 2015-10-21 Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication)
+天主教
+
+// xn--unup4y : 2013-07-14 Binky Moon, LLC
+游戏
+
+// xn--vermgensberater-ctb : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG
+vermögensberater
+
+// xn--vermgensberatung-pwb : 2014-06-23 Deutsche Vermögensberatung Aktiengesellschaft DVAG
+vermögensberatung
+
+// xn--vhquv : 2013-08-27 Binky Moon, LLC
+企业
+
+// xn--vuq861b : 2014-10-16 Beijing Tele-info Network Technology Co., Ltd.
+信息
+
+// xn--w4r85el8fhu5dnra : 2015-04-30 Kerry Trading Co. Limited
+嘉里大酒店
+
+// xn--w4rs40l : 2015-07-30 Kerry Trading Co. Limited
+嘉里
+
+// xn--xhq521b : 2013-11-14 Guangzhou YU Wei Information Technology Co., Ltd.
+广东
+
+// xn--zfr164b : 2013-11-08 China Organizational Name Administration Center
+政务
+
+// xyz : 2013-12-05 XYZ.COM LLC
+xyz
+
+// yachts : 2014-01-09 XYZ.COM LLC
+yachts
+
+// yahoo : 2015-04-02 Yahoo! Domain Services Inc.
+yahoo
+
+// yamaxun : 2014-12-18 Amazon Registry Services, Inc.
+yamaxun
+
+// yandex : 2014-04-10 Yandex Europe B.V.
+yandex
+
+// yodobashi : 2014-11-20 YODOBASHI CAMERA CO.,LTD.
+yodobashi
+
+// yoga : 2014-05-29 Minds + Machines Group Limited
+yoga
+
+// yokohama : 2013-12-12 GMO Registry, Inc.
+yokohama
+
+// you : 2015-04-09 Amazon Registry Services, Inc.
+you
+
+// youtube : 2014-05-01 Charleston Road Registry Inc.
+youtube
+
+// yun : 2015-01-08 Beijing Qihu Keji Co., Ltd.
+yun
+
+// zappos : 2015-06-25 Amazon Registry Services, Inc.
+zappos
+
+// zara : 2014-11-07 Industria de Diseño Textil, S.A. (INDITEX, S.A.)
+zara
+
+// zero : 2014-12-18 Amazon Registry Services, Inc.
+zero
+
+// zip : 2014-05-08 Charleston Road Registry Inc.
+zip
+
+// zone : 2013-11-14 Binky Moon, LLC
+zone
+
+// zuerich : 2014-11-07 Kanton Zürich (Canton of Zurich)
+zuerich
+
+
+// ===END ICANN DOMAINS===
+// ===BEGIN PRIVATE DOMAINS===
+// (Note: these are in alphabetical order by company name)
+
+// 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
+
+// Adobe : https://www.adobe.com/
+// Submitted by Ian Boston <boston@adobe.com>
+adobeaemcloud.com
+adobeaemcloud.net
+*.dev.adobeaemcloud.com
+
+// Agnat sp. z o.o. : https://domena.pl
+// Submitted by Przemyslaw Plewa <it-admin@domena.pl>
+beep.pl
+
+// 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
+
+// Amazon CloudFront : https://aws.amazon.com/cloudfront/
+// Submitted by Donavan Miller <donavanm@amazon.com>
+cloudfront.net
+
+// Amazon Elastic Compute Cloud : https://aws.amazon.com/ec2/
+// Submitted by Luke Wells <psl-maintainers@amazon.com>
+*.compute.amazonaws.com
+*.compute-1.amazonaws.com
+*.compute.amazonaws.com.cn
+us-east-1.amazonaws.com
+
+// Amazon Elastic Beanstalk : https://aws.amazon.com/elasticbeanstalk/
+// Submitted by Luke Wells <psl-maintainers@amazon.com>
+cn-north-1.eb.amazonaws.com.cn
+cn-northwest-1.eb.amazonaws.com.cn
+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
+ca-central-1.elasticbeanstalk.com
+eu-central-1.elasticbeanstalk.com
+eu-west-1.elasticbeanstalk.com
+eu-west-2.elasticbeanstalk.com
+eu-west-3.elasticbeanstalk.com
+sa-east-1.elasticbeanstalk.com
+us-east-1.elasticbeanstalk.com
+us-east-2.elasticbeanstalk.com
+us-gov-west-1.elasticbeanstalk.com
+us-west-1.elasticbeanstalk.com
+us-west-2.elasticbeanstalk.com
+
+// Amazon Elastic Load Balancing : https://aws.amazon.com/elasticloadbalancing/
+// Submitted by Luke Wells <psl-maintainers@amazon.com>
+*.elb.amazonaws.com
+*.elb.amazonaws.com.cn
+
+// Amazon Global Accelerator : https://aws.amazon.com/global-accelerator/
+// Submitted by Daniel Massaguer <psl-maintainers@amazon.com>
+awsglobalaccelerator.com
+
+// Amazon S3 : https://aws.amazon.com/s3/
+// Submitted by Luke Wells <psl-maintainers@amazon.com>
+s3.amazonaws.com
+s3-ap-northeast-1.amazonaws.com
+s3-ap-northeast-2.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-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-west-1.amazonaws.com
+s3-sa-east-1.amazonaws.com
+s3-us-gov-west-1.amazonaws.com
+s3-us-east-2.amazonaws.com
+s3-us-west-1.amazonaws.com
+s3-us-west-2.amazonaws.com
+s3.ap-northeast-2.amazonaws.com
+s3.ap-south-1.amazonaws.com
+s3.cn-north-1.amazonaws.com.cn
+s3.ca-central-1.amazonaws.com
+s3.eu-central-1.amazonaws.com
+s3.eu-west-2.amazonaws.com
+s3.eu-west-3.amazonaws.com
+s3.us-east-2.amazonaws.com
+s3.dualstack.ap-northeast-1.amazonaws.com
+s3.dualstack.ap-northeast-2.amazonaws.com
+s3.dualstack.ap-south-1.amazonaws.com
+s3.dualstack.ap-southeast-1.amazonaws.com
+s3.dualstack.ap-southeast-2.amazonaws.com
+s3.dualstack.ca-central-1.amazonaws.com
+s3.dualstack.eu-central-1.amazonaws.com
+s3.dualstack.eu-west-1.amazonaws.com
+s3.dualstack.eu-west-2.amazonaws.com
+s3.dualstack.eu-west-3.amazonaws.com
+s3.dualstack.sa-east-1.amazonaws.com
+s3.dualstack.us-east-1.amazonaws.com
+s3.dualstack.us-east-2.amazonaws.com
+s3-website-us-east-1.amazonaws.com
+s3-website-us-west-1.amazonaws.com
+s3-website-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.ap-northeast-2.amazonaws.com
+s3-website.ap-south-1.amazonaws.com
+s3-website.ca-central-1.amazonaws.com
+s3-website.eu-central-1.amazonaws.com
+s3-website.eu-west-2.amazonaws.com
+s3-website.eu-west-3.amazonaws.com
+s3-website.us-east-2.amazonaws.com
+
+// Amsterdam Wireless: https://www.amsterdamwireless.nl/
+// Submitted by Imre Jonk <hostmaster@amsterdamwireless.nl>
+amsw.nl
+
+// 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
+
+// Appspace : https://www.appspace.com
+// Submitted by Appspace Security Team <security@appspace.com>
+appspacehosted.com
+appspaceusercontent.com
+
+// Aptible : https://www.aptible.com/
+// Submitted by Thomas Orozco <thomas@aptible.com>
+on-aptible.com
+
+// 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
+
+// AVM : https://avm.de
+// Submitted by Andreas Weise <a.weise@avm.de>
+myfritz.net
+
+// AW AdvisorWebsites.com Software Inc : https://advisorwebsites.com
+// Submitted by James Kennedy <domains@advisorwebsites.com>
+*.awdev.ca
+*.advisor.ws
+
+// 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
+
+// Banzai Cloud
+// Submitted by Janos Matyas <info@banzaicloud.com>
+*.banzai.cloud
+app.banzaicloud.io
+*.backyards.banzaicloud.io
+
+
+// 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
+
+// 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
+
+// Boomla : https://boomla.com
+// Submitted by Tibor Halter <thalter@boomla.com>
+boomla.net
+
+// 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
+
+// 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
+
+// callidomus : https://www.callidomus.com/
+// Submitted by Marcus Popp <admin@callidomus.com>
+mycd.eu
+
+// Carrd : https://carrd.co
+// Submitted by AJ <aj@carrd.co>
+carrd.co
+crd.co
+uwu.ai
+
+// 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
+gb.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
+
+// Globe Hosting SRL : https://www.globehosting.com/
+// Submitted by Gavin Brown <gavin.brown@centralnic.com>
+co.ro
+shop.ro
+
+// c.la : http://www.c.la/
+c.la
+
+// certmgr.org : https://certmgr.org
+// Submitted by B. Blechschmidt <hostmaster@certmgr.org>
+certmgr.org
+
+// Civilized Discourse Construction Kit, Inc. : https://www.discourse.org/
+// Submitted by Rishabh Nambiar & Michael Brown <team@discourse.org>
+discourse.group
+discourse.team
+
+// ClearVox : http://www.clearvox.nl/
+// Submitted by Leon Rowland <leon@clearvox.nl>
+virtueeldomein.nl
+
+// 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 <colin@clerk.dev>
+*.lcl.dev
+*.stg.dev
+
+// Clic2000 : https://clic2000.fr
+// Submitted by Mathilde Blanchemanche <mathilde@clic2000.fr>
+clic2000.net
+
+// 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 Philip Langdale <security@cloudera.com>
+cloudera.site
+
+// Cloudflare, Inc. : https://www.cloudflare.com/
+// Submitted by Cloudflare Team <publicsuffixlist@cloudflare.com>
+pages.dev
+trycloudflare.com
+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
+
+// 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
+
+// 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
+
+// Craynic, s.r.o. : http://www.craynic.com/
+// Submitted by Ales Krajnik <ales.krajnik@craynic.com>
+realm.cz
+
+// 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
+
+// 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
+
+// Datawire, Inc : https://www.datawire.io
+// Submitted by Richard Li <secalert@datawire.io>
+edgestack.me
+
+// 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
+
+// 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 : https://digitalocean.com/
+// Submitted by Braxton Huggins <bhuggins@digitalocean.com>
+ondigitalocean.app
+
+// 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
+
+// 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
+
+// ECG Robotics, Inc: https://ecgrobotics.org
+// Submitted by <frc1533@ecgrobotics.org>
+onred.one
+staging.onred.one
+
+// One.com: https://www.one.com/
+// Submitted by Jacob Bunk Nielsen <jbn@one.com>
+service.one
+
+// Enonic : http://enonic.com/
+// Submitted by Erik Kaareng-Sunde <esu@enonic.com>
+enonic.io
+customer.enonic.io
+
+// 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
+
+// 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
+
+// Facebook, Inc.
+// Submitted by Peter Ruibal <public-suffix@fb.com>
+apps.fbsbx.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>
+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
+
+// 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
+couk.me
+ukco.me
+
+// 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
+
+// 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
+
+// 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 OG : http://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
+
+// GDS : https://www.gov.uk/service-manual/operations/operating-servicegovuk-subdomains
+// Submitted by David Illsley <david.illsley@digital.cabinet-office.gov.uk>
+service.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
+
+// GitHub, Inc.
+// Submitted by Patrick Toomey <security@github.com>
+github.io
+githubusercontent.com
+
+// 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
+
+// GMO Pepabo, Inc. : https://pepabo.com/
+// Submitted by dojineko <admin@pepabo.com>
+lolipop.io
+
+// GOV.UK Platform as a Service : https://www.cloud.service.gov.uk/
+// Submitted by Tom Whitwell <tom.whitwell@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
+
+// UKHomeOffice : https://www.gov.uk/government/organisations/home-office
+// Submitted by Jon Shanks <jon.shanks@digital.homeoffice.gov.uk>
+homeoffice.gov.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
+a.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
+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
+
+// Aaron Marais' Gitlab pages: https://lab.aaronleem.co.za
+// Submitted by Aaron Marais <its_me@aaronleem.co.za>
+graphox.us
+
+// 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
+// Submited 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
+
+// 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>
+myravendb.com
+ravendb.community
+ravendb.me
+development.run
+ravendb.run
+
+// Hong Kong Productivity Council: https://www.hkpc.org/
+// Submitted by SECaaS Team <summchan@hkpc.org>
+secaas.hk
+
+// 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
+org.yt
+
+// 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
+
+// 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/
+// Submited 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.eu
+// Submitted by Kim-Alexander Brodowski <info@iserv.eu>
+mein-iserv.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/
+// Submited by Ihor Kolodyuk <ik@jelastic.com>
+mel.cloudlets.com.au
+cloud.interhostsolutions.be
+users.scale.virtualcloud.com.br
+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
+it1-eur.jenv-arubabiz.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
+clicketcloud.com
+ams.cloudswitches.com
+au.cloudswitches.com
+sg.cloudswitches.com
+dopaas.com
+elastyco.com
+nv.elastyco.com
+hidora.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
+paas.leviracloud.eu
+fi.cloudplatform.fi
+demo.datacenter.fi
+paas.datacenter.fi
+jele.host
+mircloud.host
+jele.io
+ocs.opusinteractive.io
+cloud.unispace.io
+cloud-de.unispace.io
+cloud-fr1.unispace.io
+jc.neen.it
+cloud.jelastic.open.tim.it
+jcloud.kz
+upaas.kazteleport.kz
+jl.serv.net.mx
+cloudjiffy.net
+fra1-de.cloudjiffy.net
+west1-us.cloudjiffy.net
+ams1.jls.docktera.net
+jls-sto1.elastx.net
+jls-sto2.elastx.net
+jls-sto3.elastx.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
+atl.jelastic.vps-host.net
+njs.jelastic.vps-host.net
+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
+
+// 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
+
+// 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
+
+// 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
+
+// LiquidNet Ltd : http://www.liquidnetlimited.com/
+// Submitted by Victor Velchev <admin@liquidnetlimited.com>
+we.bs
+
+// 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
+
+// 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.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
+
+// 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
+
+// Memset hosting : https://www.memset.com
+// Submitted by Tom Whitwell <domains@memset.com>
+miniserver.com
+memset.net
+
+// 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 Mitch Webster <miwebst@microsoft.com>
+*.azurecontainer.io
+azurewebsites.net
+azure-mobile.net
+cloudapp.net
+azurestaticapps.net
+centralus.azurestaticapps.net
+eastasia.azurestaticapps.net
+eastus2.azurestaticapps.net
+westeurope.azurestaticapps.net
+westus2.azurestaticapps.net
+
+// minion.systems : http://minion.systems
+// Submitted by Robert Böttinger <r@minion.systems>
+csx.cc
+
+// MobileEducation, LLC : https://joinforte.com
+// Submitted by Grayson Martin <grayson.martin@mobileeducation.us>
+forte.id
+
+// 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
+
+// Names.of.London : https://names.of.london/
+// Submitted by James Stevens <registry[at]names.of.london> or <publiclist[at]jrcs.net>
+pony.club
+of.fashion
+in.london
+of.london
+from.marketing
+with.marketing
+for.men
+repair.men
+and.mom
+for.mom
+for.one
+under.one
+for.sale
+that.win
+from.work
+to.work
+
+// NCTU.ME : https://nctu.me/
+// Submitted by Tocknicsu <admin@nctu.me>
+nctu.me
+
+// 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.io
+
+// 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
+
+// Northflank Ltd. : https://northflank.com/
+// Submitted by Marco Suter <marco@northflank.com>
+northflank.app
+code.run
+
+// 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
+
+// Nodum B.V. : https://nodum.io/
+// Submitted by Wietse Wind <hello+publicsuffixlist@nodum.io>
+nodum.co
+nodum.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
+
+// NymNom : https://nymnom.com/
+// Submitted by NymNom <psl@nymnom.com>
+nom.ae
+nom.af
+nom.ai
+nom.al
+nym.by
+nom.bz
+nym.bz
+nom.cl
+nym.ec
+nom.gd
+nom.ge
+nom.gl
+nym.gr
+nom.gt
+nym.gy
+nym.hk
+nom.hn
+nym.ie
+nom.im
+nom.ke
+nym.kz
+nym.la
+nym.lc
+nom.li
+nym.li
+nym.lt
+nym.lu
+nom.lv
+nym.me
+nom.mk
+nym.mn
+nym.mx
+nom.nu
+nym.nz
+nym.pe
+nym.pt
+nom.pw
+nom.qa
+nym.ro
+nom.rs
+nom.si
+nym.sk
+nom.st
+nym.su
+nym.sx
+nom.tj
+nym.tw
+nom.ug
+nom.uy
+nom.vc
+nom.vg
+
+// 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 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
+
+// Oursky Limited : https://skygear.io/
+// Submited by Skygear Developer <hello@skygear.io>
+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
+
+// 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
+ra-ru.ru
+zsew.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>
+bc.platform.sh
+ent.platform.sh
+eu.platform.sh
+us.platform.sh
+*.platformsh.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
+
+// Port53 : https://port53.io/
+// Submitted by Maximilian Schieder <maxi@zeug.co>
+dyn53.io
+
+// Positive Codes Technology Company : http://co.bn/faq.html
+// Submitted by Zulfais <pc@co.bn>
+co.bn
+
+// 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
+
+// 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
+
+// QuickBackend: https://www.quickbackend.com
+// Submitted by Dani Biro <dani@pymet.com>
+qbuser.com
+
+// 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 Mason Clayton <mason@repl.it>
+repl.co
+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
+
+// Rochester Institute of Technology : http://www.rit.edu/
+// Submitted by Jennifer Herting <jchits@rit.edu>
+git-pages.rit.edu
+
+// 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
+
+// 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
+
+// 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
+
+// Senseering GmbH : https://www.senseering.de
+// Submitted by Felix Mönckemeyer <f.moenckemeyer@senseering.de>
+senseering.net
+
+// Service Online LLC : http://drs.ua/
+// Submitted by Serhii Bulakh <support@drs.ua>
+biz.ua
+co.ua
+pp.ua
+
+// 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
+
+// Small Technology Foundation : https://small-tech.org
+// Submitted by Aral Balkan <aral@small-tech.org>
+small-web.org
+
+// Snowplow Analytics : https://snowplowanalytics.com/
+// Submitted by Ian Streeter <ian@snowplowanalytics.com>
+try-snowplow.com
+
+// Stackhero : https://www.stackhero.io
+// Submitted by Adrien Gillon <adrien+public-suffix-list@stackhero.io>
+stackhero-network.com
+
+// staticland : https://static.land
+// Submitted by Seth Vincent <sethvincent@gmail.com>
+static.land
+dev.static.land
+sites.static.land
+
+// 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
+
+// Standard Library : https://stdlib.com
+// Submitted by Jacob Lee <jacob@stdlib.com>
+api.stdlib.com
+
+// Storj Labs Inc. : https://storj.io/
+// Submitted by Philip Hutchins <hostmaster@storj.io>
+storj.farm
+
+// 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
+
+// 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>
+diskstation.me
+dscloud.biz
+dscloud.me
+dscloud.mobi
+dsmynas.com
+dsmynas.net
+dsmynas.org
+familyds.com
+familyds.net
+familyds.org
+i234.me
+myds.me
+synology.me
+vpnplus.to
+direct.quickconnect.to
+
+// TAIFUN Software AG : http://taifun-software.de
+// Submitted by Bjoern Henke <dev-server@taifun-software.de>
+taifun-dns.de
+
+// TASK geographical domains (www.task.gda.pl/uslugi/dns)
+gda.pl
+gdansk.pl
+gdynia.pl
+med.pl
+sopot.pl
+
+// Teckids e.V. : https://www.teckids.org
+// Submitted by Dominik George <dominik.george@teckids.org>
+edugit.org
+
+// Telebit : https://telebit.cloud
+// Submitted by AJ ONeal <aj@telebit.cloud>
+telebit.app
+telebit.io
+*.telebit.xyz
+
+// The Gwiddle Foundation : https://gwiddlefoundation.org.uk
+// Submitted by Joshua Bayfield <joshua.bayfield@gwiddlefoundation.org.uk>
+gwiddle.co.uk
+
+// Thingdust AG : https://thingdust.com/
+// Submitted by Adrian Imboden <adi@thingdust.com>
+thingdustdata.com
+cust.dev.thingdust.io
+cust.disrec.thingdust.io
+cust.prod.thingdust.io
+cust.testing.thingdust.io
+*.firenet.ch
+*.svc.firenet.ch
+
+// 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
+
+// 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
+
+// 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
+
+// United Gameserver GmbH : https://united-gameserver.de
+// Submitted by Stefan Schwarz <sysadm@united-gameserver.de>
+virtualuser.de
+virtual-user.de
+
+// 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
+blog.kg
+io.kg
+jp.kg
+tv.kg
+uk.kg
+us.kg
+de.ls
+at.md
+de.md
+jp.md
+to.md
+uwu.nu
+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
+
+// Waffle Computer Inc., Ltd. : https://docs.waffleinfo.com
+// Submitted by Masayuki Note <masa@blade.wafflecell.com>
+wafflecell.com
+
+// WapBlog.ID : https://www.wapblog.id
+// Submitted by Fajar Sodik <official@wapblog.id>
+idnblogger.com
+indowapblog.com
+bloger.id
+wblog.id
+wbq.me
+fastblog.net
+
+// WebHare bv: https://www.webhare.com/
+// Submitted by Arnold Hendriks <info@webhare.com>
+*.webhare.dev
+
+// 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
+
+// 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
+
+// www.com.vc : http://www.com.vc
+// Submitted by Li Hui <lihui@sinopub.com>
+cn.vu
+
+// 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>
+nohost.me
+noho.st
+
+// ZaNiC : http://www.za.net/
+// Submitted by registry <hostmaster@nic.za.net>
+za.net
+za.org
+
+// 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
+
+// Mintere : https://mintere.com/
+// Submitted by Ben Aubin <security@mintere.com>
+mintere.site
+
+// Cityhost LLC : https://cityhost.ua
+// Submitted by Maksym Rivtin <support@cityhost.net.ua>
+cx.ua
+
+// 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
+
+// Impertrix Solutions : <https://impertrixcdn.com>
+// Submitted by Zhixiang Zhao <csuite@impertrix.com>
+impertrixcdn.com
+impertrix.com
+
+// GignoSystemJapan: http://gsj.bz
+// Submitted by GignoSystemJapan <kakutou-ec@gsj.bz>
+gsj.bz
+
+// Rusnames Limited: http://rusnames.ru/
+// Submitted by Sergey Zotov <admin@rusnames.ru>
+биз.рус
+ком.рус
+крым.рус
+мир.рус
+мск.рус
+орг.рус
+самара.рус
+сочи.рус
+спб.рус
+я.рус
+// ===END PRIVATE DOMAINS===
diff --git a/netwerk/dns/mdns/libmdns/DNSServiceDiscovery.jsm b/netwerk/dns/mdns/libmdns/DNSServiceDiscovery.jsm
new file mode 100644
index 0000000000..bf153ea1bd
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/DNSServiceDiscovery.jsm
@@ -0,0 +1,219 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const { MulticastDNS } = ChromeUtils.import(
+ AppConstants.platform == "android" &&
+ !Services.prefs.getBoolPref("network.mdns.use_js_fallback")
+ ? "resource://gre/modules/MulticastDNSAndroid.jsm"
+ : "resource://gre/modules/MulticastDNS.jsm"
+);
+
+const DNSSERVICEINFO_CONTRACT_ID =
+ "@mozilla.org/toolkit/components/mdnsresponder/dns-info;1";
+
+function log(aMsg) {
+ dump("-*- nsDNSServiceDiscovery.js : " + aMsg + "\n");
+}
+
+function generateUuid() {
+ var uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(
+ Ci.nsIUUIDGenerator
+ );
+ return uuidGenerator.generateUUID().toString();
+}
+
+// Helper class to transform return objects to correct type.
+function ListenerWrapper(aListener, aMdns) {
+ this.listener = aListener;
+ this.mdns = aMdns;
+
+ this.discoveryStarting = false;
+ this.stopDiscovery = false;
+
+ this.registrationStarting = false;
+ this.stopRegistration = false;
+
+ this.uuid = generateUuid();
+}
+
+ListenerWrapper.prototype = {
+ // Helper function for transforming an Object into nsIDNSServiceInfo.
+ makeServiceInfo(aServiceInfo) {
+ let serviceInfo = Cc[DNSSERVICEINFO_CONTRACT_ID].createInstance(
+ Ci.nsIDNSServiceInfo
+ );
+
+ for (let name of [
+ "host",
+ "address",
+ "port",
+ "serviceName",
+ "serviceType",
+ ]) {
+ try {
+ serviceInfo[name] = aServiceInfo[name];
+ } catch (e) {
+ // ignore exceptions
+ }
+ }
+
+ let attributes;
+ try {
+ attributes = _toPropertyBag2(aServiceInfo.attributes);
+ } catch (err) {
+ // Ignore unset attributes in object.
+ log("Caught unset attributes error: " + err + " - " + err.stack);
+ attributes = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
+ Ci.nsIWritablePropertyBag2
+ );
+ }
+ serviceInfo.attributes = attributes;
+
+ return serviceInfo;
+ },
+
+ /* transparent types */
+ onDiscoveryStarted(aServiceType) {
+ this.discoveryStarting = false;
+ this.listener.onDiscoveryStarted(aServiceType);
+
+ if (this.stopDiscovery) {
+ this.mdns.stopDiscovery(aServiceType, this);
+ }
+ },
+ onDiscoveryStopped(aServiceType) {
+ this.listener.onDiscoveryStopped(aServiceType);
+ },
+ onStartDiscoveryFailed(aServiceType, aErrorCode) {
+ log("onStartDiscoveryFailed: " + aServiceType + " (" + aErrorCode + ")");
+ this.discoveryStarting = false;
+ this.stopDiscovery = true;
+ this.listener.onStartDiscoveryFailed(aServiceType, aErrorCode);
+ },
+ onStopDiscoveryFailed(aServiceType, aErrorCode) {
+ log("onStopDiscoveryFailed: " + aServiceType + " (" + aErrorCode + ")");
+ this.listener.onStopDiscoveryFailed(aServiceType, aErrorCode);
+ },
+
+ /* transform types */
+ onServiceFound(aServiceInfo) {
+ this.listener.onServiceFound(this.makeServiceInfo(aServiceInfo));
+ },
+ onServiceLost(aServiceInfo) {
+ this.listener.onServiceLost(this.makeServiceInfo(aServiceInfo));
+ },
+ onServiceRegistered(aServiceInfo) {
+ this.registrationStarting = false;
+ this.listener.onServiceRegistered(this.makeServiceInfo(aServiceInfo));
+
+ if (this.stopRegistration) {
+ this.mdns.unregisterService(aServiceInfo, this);
+ }
+ },
+ onServiceUnregistered(aServiceInfo) {
+ this.listener.onServiceUnregistered(this.makeServiceInfo(aServiceInfo));
+ },
+ onServiceResolved(aServiceInfo) {
+ this.listener.onServiceResolved(this.makeServiceInfo(aServiceInfo));
+ },
+
+ onRegistrationFailed(aServiceInfo, aErrorCode) {
+ log("onRegistrationFailed: (" + aErrorCode + ")");
+ this.registrationStarting = false;
+ this.stopRegistration = true;
+ this.listener.onRegistrationFailed(
+ this.makeServiceInfo(aServiceInfo),
+ aErrorCode
+ );
+ },
+ onUnregistrationFailed(aServiceInfo, aErrorCode) {
+ log("onUnregistrationFailed: (" + aErrorCode + ")");
+ this.listener.onUnregistrationFailed(
+ this.makeServiceInfo(aServiceInfo),
+ aErrorCode
+ );
+ },
+ onResolveFailed(aServiceInfo, aErrorCode) {
+ log("onResolveFailed: (" + aErrorCode + ")");
+ this.listener.onResolveFailed(
+ this.makeServiceInfo(aServiceInfo),
+ aErrorCode
+ );
+ },
+};
+
+function nsDNSServiceDiscovery() {
+ log("nsDNSServiceDiscovery");
+ this.mdns = new MulticastDNS();
+}
+
+nsDNSServiceDiscovery.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsISupportsWeakReference",
+ "nsIDNSServiceDiscovery",
+ ]),
+
+ startDiscovery(aServiceType, aListener) {
+ log("startDiscovery");
+ let listener = new ListenerWrapper(aListener, this.mdns);
+ listener.discoveryStarting = true;
+ this.mdns.startDiscovery(aServiceType, listener);
+
+ return {
+ QueryInterface: ChromeUtils.generateQI(["nsICancelable"]),
+ cancel: function() {
+ if (this.discoveryStarting || this.stopDiscovery) {
+ this.stopDiscovery = true;
+ return;
+ }
+ this.mdns.stopDiscovery(aServiceType, listener);
+ }.bind(listener),
+ };
+ },
+
+ registerService(aServiceInfo, aListener) {
+ log("registerService");
+ let listener = new ListenerWrapper(aListener, this.mdns);
+ listener.registrationStarting = true;
+ this.mdns.registerService(aServiceInfo, listener);
+
+ return {
+ QueryInterface: ChromeUtils.generateQI(["nsICancelable"]),
+ cancel: function() {
+ if (this.registrationStarting || this.stopRegistration) {
+ this.stopRegistration = true;
+ return;
+ }
+ this.mdns.unregisterService(aServiceInfo, listener);
+ }.bind(listener),
+ };
+ },
+
+ resolveService(aServiceInfo, aListener) {
+ log("resolveService");
+ this.mdns.resolveService(aServiceInfo, new ListenerWrapper(aListener));
+ },
+};
+
+var EXPORTED_SYMBOLS = ["nsDNSServiceDiscovery"];
+
+function _toPropertyBag2(obj) {
+ if (obj.QueryInterface) {
+ return obj.QueryInterface(Ci.nsIPropertyBag2);
+ }
+
+ let result = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
+ Ci.nsIWritablePropertyBag2
+ );
+ for (let name in obj) {
+ result.setPropertyAsAString(name, obj[name]);
+ }
+ return result;
+}
diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp
new file mode 100644
index 0000000000..ae780b614d
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp
@@ -0,0 +1,719 @@
+/* -*- 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 "MDNSResponderOperator.h"
+#include "MDNSResponderReply.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Unused.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsDNSServiceInfo.h"
+#include "nsHashPropertyBag.h"
+#include "nsIProperty.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIVariant.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetAddr.h"
+#include "nsNetCID.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCID.h"
+#include "private/pprio.h"
+
+#include "nsASocketHandler.h"
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gMDNSLog("MDNSResponderOperator");
+#undef LOG_I
+#define LOG_I(...) \
+ MOZ_LOG(mozilla::net::gMDNSLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#undef LOG_E
+#define LOG_E(...) \
+ MOZ_LOG(mozilla::net::gMDNSLog, mozilla::LogLevel::Error, (__VA_ARGS__))
+
+class MDNSResponderOperator::ServiceWatcher final : public nsASocketHandler {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsASocketHandler methods
+ virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(fd == mFD);
+
+ if (outFlags & (PR_POLL_ERR | PR_POLL_HUP | PR_POLL_NVAL)) {
+ LOG_E("error polling on listening socket (%p)", fd);
+ mCondition = NS_ERROR_UNEXPECTED;
+ }
+
+ if (!(outFlags & PR_POLL_READ)) {
+ return;
+ }
+
+ DNSServiceProcessResult(mService);
+ }
+
+ virtual void OnSocketDetached(PRFileDesc* fd) override {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mThread);
+ MOZ_ASSERT(fd == mFD);
+
+ if (!mFD) {
+ return;
+ }
+
+ // Bug 1175387: do not double close the handle here.
+ PR_ChangeFileDescNativeHandle(mFD, -1);
+ PR_Close(mFD);
+ mFD = nullptr;
+
+ mThread->Dispatch(
+ NewRunnableMethod("MDNSResponderOperator::ServiceWatcher::Deallocate",
+ this, &ServiceWatcher::Deallocate),
+ NS_DISPATCH_NORMAL);
+ }
+
+ virtual void IsLocal(bool* aIsLocal) override { *aIsLocal = true; }
+
+ virtual void KeepWhenOffline(bool* aKeepWhenOffline) override {
+ *aKeepWhenOffline = true;
+ }
+
+ virtual uint64_t ByteCountSent() override { return 0; }
+ virtual uint64_t ByteCountReceived() override { return 0; }
+
+ explicit ServiceWatcher(DNSServiceRef aService,
+ MDNSResponderOperator* aOperator)
+ : mThread(nullptr),
+ mSts(nullptr),
+ mOperatorHolder(aOperator),
+ mService(aService),
+ mFD(nullptr),
+ mAttached(false) {
+ if (!gSocketTransportService) {
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ }
+ }
+
+ nsresult Init() {
+ MOZ_ASSERT(!OnSocketThread(), "on socket thread");
+ mThread = NS_GetCurrentThread();
+
+ if (!mService) {
+ return NS_OK;
+ }
+
+ if (!gSocketTransportService) {
+ return NS_ERROR_FAILURE;
+ }
+ mSts = gSocketTransportService;
+
+ int osfd = DNSServiceRefSockFD(mService);
+ if (osfd == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mFD = PR_ImportFile(osfd);
+ return PostEvent("MDNSResponderOperator::ServiceWatcher::OnMsgAttach",
+ &ServiceWatcher::OnMsgAttach);
+ }
+
+ void Close() {
+ MOZ_ASSERT(!OnSocketThread(), "on socket thread");
+
+ if (!gSocketTransportService) {
+ Deallocate();
+ return;
+ }
+
+ PostEvent("MDNSResponderOperator::ServiceWatcher::OnMsgClose",
+ &ServiceWatcher::OnMsgClose);
+ }
+
+ private:
+ ~ServiceWatcher() = default;
+
+ void Deallocate() {
+ if (mService) {
+ DNSServiceRefDeallocate(mService);
+ mService = nullptr;
+ }
+ mOperatorHolder = nullptr;
+ }
+
+ nsresult PostEvent(const char* aName, void (ServiceWatcher::*func)(void)) {
+ return gSocketTransportService->Dispatch(
+ NewRunnableMethod(aName, this, func), NS_DISPATCH_NORMAL);
+ }
+
+ void OnMsgClose() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ 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 OnMsgAttach() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ 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 TryAttach() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ 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(
+ "MDNSResponderOperator::ServiceWatcher::OnMsgAttach", this,
+ &ServiceWatcher::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;
+ }
+
+ nsCOMPtr<nsIThread> mThread;
+ RefPtr<nsSocketTransportService> mSts;
+ RefPtr<MDNSResponderOperator> mOperatorHolder;
+ DNSServiceRef mService;
+ PRFileDesc* mFD;
+ bool mAttached;
+};
+
+NS_IMPL_ISUPPORTS(MDNSResponderOperator::ServiceWatcher, nsISupports)
+
+MDNSResponderOperator::MDNSResponderOperator()
+ : mService(nullptr),
+ mWatcher(nullptr),
+ mThread(NS_GetCurrentThread()),
+ mIsCancelled(false) {}
+
+MDNSResponderOperator::~MDNSResponderOperator() { Stop(); }
+
+nsresult MDNSResponderOperator::Start() {
+ if (mIsCancelled) {
+ return NS_OK;
+ }
+
+ if (IsServing()) {
+ Stop();
+ }
+
+ return NS_OK;
+}
+
+nsresult MDNSResponderOperator::Stop() { return ResetService(nullptr); }
+
+nsresult MDNSResponderOperator::ResetService(DNSServiceRef aService) {
+ nsresult rv;
+
+ if (aService != mService) {
+ if (mWatcher) {
+ mWatcher->Close();
+ mWatcher = nullptr;
+ }
+
+ if (aService) {
+ RefPtr<ServiceWatcher> watcher = new ServiceWatcher(aService, this);
+ if (NS_WARN_IF(NS_FAILED(rv = watcher->Init()))) {
+ return rv;
+ }
+ mWatcher = watcher;
+ }
+
+ mService = aService;
+ }
+ return NS_OK;
+}
+
+BrowseOperator::BrowseOperator(const nsACString& aServiceType,
+ nsIDNSServiceDiscoveryListener* aListener)
+ : MDNSResponderOperator(),
+ mServiceType(aServiceType),
+ mListener(aListener) {}
+
+nsresult BrowseOperator::Start() {
+ nsresult rv;
+ if (NS_WARN_IF(NS_FAILED(rv = MDNSResponderOperator::Start()))) {
+ return rv;
+ }
+
+ DNSServiceRef service = nullptr;
+ DNSServiceErrorType err = DNSServiceBrowse(
+ &service, 0, kDNSServiceInterfaceIndexAny, mServiceType.get(), nullptr,
+ &BrowseReplyRunnable::Reply, this);
+ NS_WARNING_ASSERTION(kDNSServiceErr_NoError == err, "DNSServiceBrowse fail");
+
+ if (mListener) {
+ if (kDNSServiceErr_NoError == err) {
+ mListener->OnDiscoveryStarted(mServiceType);
+ } else {
+ mListener->OnStartDiscoveryFailed(mServiceType, err);
+ }
+ }
+
+ if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return ResetService(service);
+}
+
+nsresult BrowseOperator::Stop() {
+ bool isServing = IsServing();
+ nsresult rv = MDNSResponderOperator::Stop();
+
+ if (isServing && mListener) {
+ if (NS_SUCCEEDED(rv)) {
+ mListener->OnDiscoveryStopped(mServiceType);
+ } else {
+ mListener->OnStopDiscoveryFailed(mServiceType, static_cast<uint32_t>(rv));
+ }
+ }
+
+ return rv;
+}
+
+void BrowseOperator::Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aServiceName,
+ const nsACString& aRegType,
+ const nsACString& aReplyDomain) {
+ MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
+
+ if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) {
+ LOG_E("BrowseOperator::Reply (%d)", aErrorCode);
+ if (mListener) {
+ mListener->OnStartDiscoveryFailed(mServiceType, aErrorCode);
+ }
+ return;
+ }
+
+ if (!mListener) {
+ return;
+ }
+ nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo();
+
+ if (NS_WARN_IF(!info)) {
+ return;
+ }
+ if (NS_WARN_IF(NS_FAILED(info->SetServiceName(aServiceName)))) {
+ return;
+ }
+ if (NS_WARN_IF(NS_FAILED(info->SetServiceType(aRegType)))) {
+ return;
+ }
+ if (NS_WARN_IF(NS_FAILED(info->SetDomainName(aReplyDomain)))) {
+ return;
+ }
+
+ if (aFlags & kDNSServiceFlagsAdd) {
+ mListener->OnServiceFound(info);
+ } else {
+ mListener->OnServiceLost(info);
+ }
+}
+
+RegisterOperator::RegisterOperator(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSRegistrationListener* aListener)
+ : MDNSResponderOperator(),
+ mServiceInfo(aServiceInfo),
+ mListener(aListener) {}
+
+nsresult RegisterOperator::Start() {
+ nsresult rv;
+
+ rv = MDNSResponderOperator::Start();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ uint16_t port;
+ if (NS_WARN_IF(NS_FAILED(rv = mServiceInfo->GetPort(&port)))) {
+ return rv;
+ }
+ nsAutoCString type;
+ if (NS_WARN_IF(NS_FAILED(rv = mServiceInfo->GetServiceType(type)))) {
+ return rv;
+ }
+
+ TXTRecordRef txtRecord;
+ char buf[TXT_BUFFER_SIZE] = {0};
+ TXTRecordCreate(&txtRecord, TXT_BUFFER_SIZE, buf);
+
+ nsCOMPtr<nsIPropertyBag2> attributes;
+ if (NS_FAILED(rv = mServiceInfo->GetAttributes(getter_AddRefs(attributes)))) {
+ LOG_I("register: no attributes");
+ } else {
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ if (NS_WARN_IF(NS_FAILED(
+ rv = attributes->GetEnumerator(getter_AddRefs(enumerator))))) {
+ return rv;
+ }
+
+ bool hasMoreElements;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) &&
+ hasMoreElements) {
+ nsCOMPtr<nsISupports> element;
+ MOZ_ALWAYS_SUCCEEDS(enumerator->GetNext(getter_AddRefs(element)));
+ nsCOMPtr<nsIProperty> property = do_QueryInterface(element);
+ MOZ_ASSERT(property);
+
+ nsAutoString name;
+ nsCOMPtr<nsIVariant> value;
+ MOZ_ALWAYS_SUCCEEDS(property->GetName(name));
+ MOZ_ALWAYS_SUCCEEDS(property->GetValue(getter_AddRefs(value)));
+
+ nsAutoCString str;
+ if (NS_WARN_IF(NS_FAILED(value->GetAsACString(str)))) {
+ continue;
+ }
+
+ TXTRecordSetValue(&txtRecord,
+ /* it's safe because key name is ASCII only. */
+ NS_LossyConvertUTF16toASCII(name).get(), str.Length(),
+ str.get());
+ }
+ }
+
+ nsAutoCString host;
+ nsAutoCString name;
+ nsAutoCString domain;
+
+ DNSServiceRef service = nullptr;
+ DNSServiceErrorType err = DNSServiceRegister(
+ &service, 0, 0,
+ NS_SUCCEEDED(mServiceInfo->GetServiceName(name)) ? name.get() : nullptr,
+ type.get(),
+ NS_SUCCEEDED(mServiceInfo->GetDomainName(domain)) ? domain.get()
+ : nullptr,
+ NS_SUCCEEDED(mServiceInfo->GetHost(host)) ? host.get() : nullptr,
+ NativeEndian::swapToNetworkOrder(port), TXTRecordGetLength(&txtRecord),
+ TXTRecordGetBytesPtr(&txtRecord), &RegisterReplyRunnable::Reply, this);
+ NS_WARNING_ASSERTION(kDNSServiceErr_NoError == err,
+ "DNSServiceRegister fail");
+
+ TXTRecordDeallocate(&txtRecord);
+
+ if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
+ if (mListener) {
+ mListener->OnRegistrationFailed(mServiceInfo, err);
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ return ResetService(service);
+}
+
+nsresult RegisterOperator::Stop() {
+ bool isServing = IsServing();
+ nsresult rv = MDNSResponderOperator::Stop();
+
+ if (isServing && mListener) {
+ if (NS_SUCCEEDED(rv)) {
+ mListener->OnServiceUnregistered(mServiceInfo);
+ } else {
+ mListener->OnUnregistrationFailed(mServiceInfo,
+ static_cast<uint32_t>(rv));
+ }
+ }
+
+ return rv;
+}
+
+void RegisterOperator::Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aName,
+ const nsACString& aRegType,
+ const nsACString& aDomain) {
+ MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
+
+ if (kDNSServiceErr_NoError != aErrorCode) {
+ LOG_E("RegisterOperator::Reply (%d)", aErrorCode);
+ }
+
+ if (!mListener) {
+ return;
+ }
+ nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(mServiceInfo);
+ if (NS_WARN_IF(NS_FAILED(info->SetServiceName(aName)))) {
+ return;
+ }
+ if (NS_WARN_IF(NS_FAILED(info->SetServiceType(aRegType)))) {
+ return;
+ }
+ if (NS_WARN_IF(NS_FAILED(info->SetDomainName(aDomain)))) {
+ return;
+ }
+
+ if (kDNSServiceErr_NoError == aErrorCode) {
+ if (aFlags & kDNSServiceFlagsAdd) {
+ mListener->OnServiceRegistered(info);
+ } else {
+ // If a successfully-registered name later suffers a name conflict
+ // or similar problem and has to be deregistered, the callback will
+ // be invoked with the kDNSServiceFlagsAdd flag not set.
+ LOG_E("RegisterOperator::Reply: deregister");
+ if (NS_WARN_IF(NS_FAILED(Stop()))) {
+ return;
+ }
+ }
+ } else {
+ mListener->OnRegistrationFailed(info, aErrorCode);
+ }
+}
+
+ResolveOperator::ResolveOperator(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSServiceResolveListener* aListener)
+ : MDNSResponderOperator(),
+ mServiceInfo(aServiceInfo),
+ mListener(aListener) {}
+
+nsresult ResolveOperator::Start() {
+ nsresult rv;
+ if (NS_WARN_IF(NS_FAILED(rv = MDNSResponderOperator::Start()))) {
+ return rv;
+ }
+
+ nsAutoCString name;
+ mServiceInfo->GetServiceName(name);
+ nsAutoCString type;
+ mServiceInfo->GetServiceType(type);
+ nsAutoCString domain;
+ mServiceInfo->GetDomainName(domain);
+
+ LOG_I("Resolve: (%s), (%s), (%s)", name.get(), type.get(), domain.get());
+
+ DNSServiceRef service = nullptr;
+ DNSServiceErrorType err = DNSServiceResolve(
+ &service, 0, kDNSServiceInterfaceIndexAny, name.get(), type.get(),
+ domain.get(), (DNSServiceResolveReply)&ResolveReplyRunnable::Reply, this);
+
+ if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
+ if (mListener) {
+ mListener->OnResolveFailed(mServiceInfo, err);
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ return ResetService(service);
+}
+
+void ResolveOperator::Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aFullName,
+ const nsACString& aHostTarget, uint16_t aPort,
+ uint16_t aTxtLen, const unsigned char* aTxtRecord) {
+ MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
+
+ auto guard = MakeScopeExit([&] { Unused << NS_WARN_IF(NS_FAILED(Stop())); });
+
+ if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) {
+ LOG_E("ResolveOperator::Reply (%d)", aErrorCode);
+ return;
+ }
+
+ // Resolve TXT record
+ int count = TXTRecordGetCount(aTxtLen, aTxtRecord);
+ LOG_I("resolve: txt count = %d, len = %d", count, aTxtLen);
+ nsCOMPtr<nsIWritablePropertyBag2> attributes = new nsHashPropertyBag();
+ if (NS_WARN_IF(!attributes)) {
+ return;
+ }
+ if (count) {
+ for (int i = 0; i < count; ++i) {
+ char key[TXT_BUFFER_SIZE] = {'\0'};
+ uint8_t vSize = 0;
+ const void* value = nullptr;
+ if (kDNSServiceErr_NoError !=
+ TXTRecordGetItemAtIndex(aTxtLen, aTxtRecord, i, TXT_BUFFER_SIZE, key,
+ &vSize, &value)) {
+ break;
+ }
+
+ nsAutoCString str(reinterpret_cast<const char*>(value), vSize);
+ LOG_I("resolve TXT: (%d) %s=%s", vSize, key, str.get());
+
+ if (NS_WARN_IF(NS_FAILED(attributes->SetPropertyAsACString(
+ /* it's safe to convert because key name is ASCII only. */
+ NS_ConvertASCIItoUTF16(key), str)))) {
+ break;
+ }
+ }
+ }
+
+ if (!mListener) {
+ return;
+ }
+ nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(mServiceInfo);
+ if (NS_WARN_IF(NS_FAILED(info->SetHost(aHostTarget)))) {
+ return;
+ }
+ if (NS_WARN_IF(NS_FAILED(info->SetPort(aPort)))) {
+ return;
+ }
+ if (NS_WARN_IF(NS_FAILED(info->SetAttributes(attributes)))) {
+ return;
+ }
+
+ if (kDNSServiceErr_NoError == aErrorCode) {
+ GetAddrInfor(info);
+ } else {
+ mListener->OnResolveFailed(info, aErrorCode);
+ Unused << NS_WARN_IF(NS_FAILED(Stop()));
+ }
+}
+
+void ResolveOperator::GetAddrInfor(nsIDNSServiceInfo* aServiceInfo) {
+ RefPtr<GetAddrInfoOperator> getAddreOp =
+ new GetAddrInfoOperator(aServiceInfo, mListener);
+ Unused << NS_WARN_IF(NS_FAILED(getAddreOp->Start()));
+}
+
+GetAddrInfoOperator::GetAddrInfoOperator(
+ nsIDNSServiceInfo* aServiceInfo, nsIDNSServiceResolveListener* aListener)
+ : MDNSResponderOperator(),
+ mServiceInfo(aServiceInfo),
+ mListener(aListener) {}
+
+nsresult GetAddrInfoOperator::Start() {
+ nsresult rv;
+ if (NS_WARN_IF(NS_FAILED(rv = MDNSResponderOperator::Start()))) {
+ return rv;
+ }
+
+ nsAutoCString host;
+ mServiceInfo->GetHost(host);
+
+ LOG_I("GetAddrInfo: (%s)", host.get());
+
+ DNSServiceRef service = nullptr;
+ DNSServiceErrorType err = DNSServiceGetAddrInfo(
+ &service, kDNSServiceFlagsForceMulticast, kDNSServiceInterfaceIndexAny,
+ kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, host.get(),
+ (DNSServiceGetAddrInfoReply)&GetAddrInfoReplyRunnable::Reply, this);
+
+ if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
+ if (mListener) {
+ mListener->OnResolveFailed(mServiceInfo, err);
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ return ResetService(service);
+}
+
+void GetAddrInfoOperator::Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aHostName,
+ const NetAddr& aAddress, uint32_t aTTL) {
+ MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
+
+ auto guard = MakeScopeExit([&] { Unused << NS_WARN_IF(NS_FAILED(Stop())); });
+
+ if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) {
+ LOG_E("GetAddrInfoOperator::Reply (%d)", aErrorCode);
+ return;
+ }
+
+ if (!mListener) {
+ return;
+ }
+
+ NetAddr addr = aAddress;
+ nsCOMPtr<nsINetAddr> address = new nsNetAddr(&addr);
+ nsCString addressStr;
+ if (NS_WARN_IF(NS_FAILED(address->GetAddress(addressStr)))) {
+ return;
+ }
+
+ nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(mServiceInfo);
+ if (NS_WARN_IF(NS_FAILED(info->SetAddress(addressStr)))) {
+ return;
+ }
+
+ /**
+ * |kDNSServiceFlagsMoreComing| means this callback will be one or more
+ * callback events later, so this instance should be kept alive until all
+ * follow-up events are processed.
+ */
+ if (aFlags & kDNSServiceFlagsMoreComing) {
+ guard.release();
+ }
+
+ if (kDNSServiceErr_NoError == aErrorCode) {
+ mListener->OnServiceResolved(info);
+ } else {
+ mListener->OnResolveFailed(info, aErrorCode);
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h
new file mode 100644
index 0000000000..394f68777c
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h
@@ -0,0 +1,133 @@
+/* -*- 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_dns_mdns_libmdns_MDNSResponderOperator_h
+#define mozilla_netwerk_dns_mdns_libmdns_MDNSResponderOperator_h
+
+#include "dns_sd.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIDNSServiceDiscovery.h"
+#include "nsIThread.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+class MDNSResponderOperator {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MDNSResponderOperator)
+
+ public:
+ MDNSResponderOperator();
+
+ virtual nsresult Start();
+ virtual nsresult Stop();
+ void Cancel() { mIsCancelled = true; }
+ nsIThread* GetThread() const { return mThread; }
+
+ protected:
+ virtual ~MDNSResponderOperator();
+
+ bool IsServing() const { return mService; }
+ nsresult ResetService(DNSServiceRef aService);
+
+ private:
+ class ServiceWatcher;
+
+ DNSServiceRef mService;
+ RefPtr<ServiceWatcher> mWatcher;
+ nsCOMPtr<nsIThread> mThread; // remember caller thread for callback
+ Atomic<bool> mIsCancelled;
+};
+
+class BrowseOperator final : public MDNSResponderOperator {
+ public:
+ BrowseOperator(const nsACString& aServiceType,
+ nsIDNSServiceDiscoveryListener* aListener);
+
+ nsresult Start() override;
+ nsresult Stop() override;
+
+ void Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex, DNSServiceErrorType aErrorCode,
+ const nsACString& aServiceName, const nsACString& aRegType,
+ const nsACString& aReplyDomain);
+
+ private:
+ ~BrowseOperator() = default;
+
+ nsCString mServiceType;
+ nsCOMPtr<nsIDNSServiceDiscoveryListener> mListener;
+};
+
+class RegisterOperator final : public MDNSResponderOperator {
+ enum { TXT_BUFFER_SIZE = 256 };
+
+ public:
+ RegisterOperator(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSRegistrationListener* aListener);
+
+ nsresult Start() override;
+ nsresult Stop() override;
+
+ void Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ DNSServiceErrorType aErrorCode, const nsACString& aName,
+ const nsACString& aRegType, const nsACString& aDomain);
+
+ private:
+ ~RegisterOperator() = default;
+
+ nsCOMPtr<nsIDNSServiceInfo> mServiceInfo;
+ nsCOMPtr<nsIDNSRegistrationListener> mListener;
+};
+
+class ResolveOperator final : public MDNSResponderOperator {
+ enum { TXT_BUFFER_SIZE = 256 };
+
+ public:
+ ResolveOperator(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSServiceResolveListener* aListener);
+
+ nsresult Start() override;
+
+ void Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex, DNSServiceErrorType aErrorCode,
+ const nsACString& aFullName, const nsACString& aHostTarget,
+ uint16_t aPort, uint16_t aTxtLen, const unsigned char* aTxtRecord);
+
+ private:
+ ~ResolveOperator() = default;
+ void GetAddrInfor(nsIDNSServiceInfo* aServiceInfo);
+
+ nsCOMPtr<nsIDNSServiceInfo> mServiceInfo;
+ nsCOMPtr<nsIDNSServiceResolveListener> mListener;
+};
+
+union NetAddr;
+
+class GetAddrInfoOperator final : public MDNSResponderOperator {
+ public:
+ GetAddrInfoOperator(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSServiceResolveListener* aListener);
+
+ nsresult Start() override;
+
+ void Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex, DNSServiceErrorType aErrorCode,
+ const nsACString& aHostName, const NetAddr& aAddress,
+ uint32_t aTTL);
+
+ private:
+ ~GetAddrInfoOperator() = default;
+
+ nsCOMPtr<nsIDNSServiceInfo> mServiceInfo;
+ nsCOMPtr<nsIDNSServiceResolveListener> mListener;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_dns_mdns_libmdns_MDNSResponderOperator_h
diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp b/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp
new file mode 100644
index 0000000000..ee08a87211
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp
@@ -0,0 +1,215 @@
+/* -*- 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 "MDNSResponderReply.h"
+#include "mozilla/EndianUtils.h"
+#include "private/pprio.h"
+
+namespace mozilla {
+namespace net {
+
+BrowseReplyRunnable::BrowseReplyRunnable(
+ DNSServiceRef aSdRef, DNSServiceFlags aFlags, uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode, const nsACString& aServiceName,
+ const nsACString& aRegType, const nsACString& aReplyDomain,
+ BrowseOperator* aContext)
+ : Runnable("net::BrowseReplyRunnable"),
+ mSdRef(aSdRef),
+ mFlags(aFlags),
+ mInterfaceIndex(aInterfaceIndex),
+ mErrorCode(aErrorCode),
+ mServiceName(aServiceName),
+ mRegType(aRegType),
+ mReplyDomain(aReplyDomain),
+ mContext(aContext) {}
+
+NS_IMETHODIMP
+BrowseReplyRunnable::Run() {
+ MOZ_ASSERT(mContext);
+ mContext->Reply(mSdRef, mFlags, mInterfaceIndex, mErrorCode, mServiceName,
+ mRegType, mReplyDomain);
+ return NS_OK;
+}
+
+void BrowseReplyRunnable::Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const char* aServiceName, const char* aRegType,
+ const char* aReplyDomain, void* aContext) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ BrowseOperator* obj(reinterpret_cast<BrowseOperator*>(aContext));
+ if (!obj) {
+ return;
+ }
+
+ nsCOMPtr<nsIThread> thread(obj->GetThread());
+ if (!thread) {
+ return;
+ }
+
+ thread->Dispatch(
+ new BrowseReplyRunnable(aSdRef, aFlags, aInterfaceIndex, aErrorCode,
+ nsCString(aServiceName), nsCString(aRegType),
+ nsCString(aReplyDomain), obj),
+ NS_DISPATCH_NORMAL);
+}
+
+RegisterReplyRunnable::RegisterReplyRunnable(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aName,
+ const nsACString& aRegType,
+ const nsACString& domain,
+ RegisterOperator* aContext)
+ : Runnable("net::RegisterReplyRunnable"),
+ mSdRef(aSdRef),
+ mFlags(aFlags),
+ mErrorCode(aErrorCode),
+ mName(aName),
+ mRegType(aRegType),
+ mDomain(domain),
+ mContext(aContext) {}
+
+NS_IMETHODIMP
+RegisterReplyRunnable::Run() {
+ MOZ_ASSERT(mContext);
+
+ mContext->Reply(mSdRef, mFlags, mErrorCode, mName, mRegType, mDomain);
+ return NS_OK;
+}
+
+void RegisterReplyRunnable::Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ DNSServiceErrorType aErrorCode,
+ const char* aName, const char* aRegType,
+ const char* domain, void* aContext) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ RegisterOperator* obj(reinterpret_cast<RegisterOperator*>(aContext));
+ if (!obj) {
+ return;
+ }
+
+ nsCOMPtr<nsIThread> thread(obj->GetThread());
+ if (!thread) {
+ return;
+ }
+
+ thread->Dispatch(
+ new RegisterReplyRunnable(aSdRef, aFlags, aErrorCode, nsCString(aName),
+ nsCString(aRegType), nsCString(domain), obj),
+ NS_DISPATCH_NORMAL);
+}
+
+ResolveReplyRunnable::ResolveReplyRunnable(
+ DNSServiceRef aSdRef, DNSServiceFlags aFlags, uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode, const nsACString& aFullName,
+ const nsACString& aHostTarget, uint16_t aPort, uint16_t aTxtLen,
+ const unsigned char* aTxtRecord, ResolveOperator* aContext)
+ : Runnable("net::ResolveReplyRunnable"),
+ mSdRef(aSdRef),
+ mFlags(aFlags),
+ mInterfaceIndex(aInterfaceIndex),
+ mErrorCode(aErrorCode),
+ mFullname(aFullName),
+ mHosttarget(aHostTarget),
+ mPort(aPort),
+ mTxtLen(aTxtLen),
+ mTxtRecord(new unsigned char[aTxtLen]),
+ mContext(aContext) {
+ if (mTxtRecord) {
+ memcpy(mTxtRecord.get(), aTxtRecord, aTxtLen);
+ }
+}
+
+NS_IMETHODIMP
+ResolveReplyRunnable::Run() {
+ MOZ_ASSERT(mContext);
+ mContext->Reply(mSdRef, mFlags, mInterfaceIndex, mErrorCode, mFullname,
+ mHosttarget, mPort, mTxtLen, mTxtRecord.get());
+ return NS_OK;
+}
+
+void ResolveReplyRunnable::Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const char* aFullName, const char* aHostTarget,
+ uint16_t aPort, uint16_t aTxtLen,
+ const unsigned char* aTxtRecord,
+ void* aContext) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ ResolveOperator* obj(reinterpret_cast<ResolveOperator*>(aContext));
+ if (!obj) {
+ return;
+ }
+
+ nsCOMPtr<nsIThread> thread(obj->GetThread());
+ if (!thread) {
+ return;
+ }
+
+ thread->Dispatch(
+ new ResolveReplyRunnable(aSdRef, aFlags, aInterfaceIndex, aErrorCode,
+ nsCString(aFullName), nsCString(aHostTarget),
+ NativeEndian::swapFromNetworkOrder(aPort),
+ aTxtLen, aTxtRecord, obj),
+ NS_DISPATCH_NORMAL);
+}
+
+GetAddrInfoReplyRunnable::GetAddrInfoReplyRunnable(
+ DNSServiceRef aSdRef, DNSServiceFlags aFlags, uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode, const nsACString& aHostName,
+ const mozilla::net::NetAddr& aAddress, uint32_t aTTL,
+ GetAddrInfoOperator* aContext)
+ : Runnable("net::GetAddrInfoReplyRunnable"),
+ mSdRef(aSdRef),
+ mFlags(aFlags),
+ mInterfaceIndex(aInterfaceIndex),
+ mErrorCode(aErrorCode),
+ mHostName(aHostName),
+ mAddress(aAddress),
+ mTTL(aTTL),
+ mContext(aContext) {}
+
+NS_IMETHODIMP
+GetAddrInfoReplyRunnable::Run() {
+ MOZ_ASSERT(mContext);
+ mContext->Reply(mSdRef, mFlags, mInterfaceIndex, mErrorCode, mHostName,
+ mAddress, mTTL);
+ return NS_OK;
+}
+
+void GetAddrInfoReplyRunnable::Reply(
+ DNSServiceRef aSdRef, DNSServiceFlags aFlags, uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode, const char* aHostName,
+ const struct sockaddr* aAddress, uint32_t aTTL, void* aContext) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ GetAddrInfoOperator* obj(reinterpret_cast<GetAddrInfoOperator*>(aContext));
+ if (!obj) {
+ return;
+ }
+
+ nsCOMPtr<nsIThread> thread(obj->GetThread());
+ if (!thread) {
+ return;
+ }
+
+ NetAddr address;
+ address.raw.family = aAddress->sa_family;
+
+ static_assert(sizeof(address.raw.data) >= sizeof(aAddress->sa_data),
+ "size of sockaddr.sa_data is too big");
+ memcpy(&address.raw.data, aAddress->sa_data, sizeof(aAddress->sa_data));
+
+ thread->Dispatch(
+ new GetAddrInfoReplyRunnable(aSdRef, aFlags, aInterfaceIndex, aErrorCode,
+ nsCString(aHostName), address, aTTL, obj),
+ NS_DISPATCH_NORMAL);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderReply.h b/netwerk/dns/mdns/libmdns/MDNSResponderReply.h
new file mode 100644
index 0000000000..120bb6dae7
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderReply.h
@@ -0,0 +1,130 @@
+/* -*- 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_dns_mdns_libmdns_MDNSResponderReply_h
+#define mozilla_netwerk_dns_mdns_libmdns_MDNSResponderReply_h
+
+#include "dns_sd.h"
+#include "MDNSResponderOperator.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/RefPtr.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+class BrowseReplyRunnable final : public Runnable {
+ public:
+ BrowseReplyRunnable(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex, DNSServiceErrorType aErrorCode,
+ const nsACString& aServiceName,
+ const nsACString& aRegType,
+ const nsACString& aReplyDomain, BrowseOperator* aContext);
+
+ NS_IMETHOD Run() override;
+
+ static void Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex, DNSServiceErrorType aErrorCode,
+ const char* aServiceName, const char* aRegType,
+ const char* aReplyDomain, void* aContext);
+
+ private:
+ DNSServiceRef mSdRef;
+ DNSServiceFlags mFlags;
+ uint32_t mInterfaceIndex;
+ DNSServiceErrorType mErrorCode;
+ nsCString mServiceName;
+ nsCString mRegType;
+ nsCString mReplyDomain;
+ RefPtr<BrowseOperator> mContext;
+};
+
+class RegisterReplyRunnable final : public Runnable {
+ public:
+ RegisterReplyRunnable(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ DNSServiceErrorType aErrorCode, const nsACString& aName,
+ const nsACString& aRegType, const nsACString& aDomain,
+ RegisterOperator* aContext);
+
+ NS_IMETHOD Run() override;
+
+ static void Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ DNSServiceErrorType aErrorCode, const char* aName,
+ const char* aRegType, const char* aDomain, void* aContext);
+
+ private:
+ DNSServiceRef mSdRef;
+ DNSServiceFlags mFlags;
+ DNSServiceErrorType mErrorCode;
+ nsCString mName;
+ nsCString mRegType;
+ nsCString mDomain;
+ RefPtr<RegisterOperator> mContext;
+};
+
+class ResolveReplyRunnable final : public Runnable {
+ public:
+ ResolveReplyRunnable(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex, DNSServiceErrorType aErrorCode,
+ const nsACString& aFullName,
+ const nsACString& aHostTarget, uint16_t aPort,
+ uint16_t aTxtLen, const unsigned char* aTxtRecord,
+ ResolveOperator* aContext);
+ ~ResolveReplyRunnable() = default;
+
+ NS_IMETHOD Run() override;
+
+ static void Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex, DNSServiceErrorType aErrorCode,
+ const char* aFullName, const char* aHostTarget,
+ uint16_t aPort, uint16_t aTxtLen,
+ const unsigned char* aTxtRecord, void* aContext);
+
+ private:
+ DNSServiceRef mSdRef;
+ DNSServiceFlags mFlags;
+ uint32_t mInterfaceIndex;
+ DNSServiceErrorType mErrorCode;
+ nsCString mFullname;
+ nsCString mHosttarget;
+ uint16_t mPort;
+ uint16_t mTxtLen;
+ UniquePtr<unsigned char> mTxtRecord;
+ RefPtr<ResolveOperator> mContext;
+};
+
+class GetAddrInfoReplyRunnable final : public Runnable {
+ public:
+ GetAddrInfoReplyRunnable(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aHostName,
+ const mozilla::net::NetAddr& aAddress, uint32_t aTTL,
+ GetAddrInfoOperator* aContext);
+ ~GetAddrInfoReplyRunnable() = default;
+
+ NS_IMETHOD Run() override;
+
+ static void Reply(DNSServiceRef aSdRef, DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex, DNSServiceErrorType aErrorCode,
+ const char* aHostName, const struct sockaddr* aAddress,
+ uint32_t aTTL, void* aContext);
+
+ private:
+ DNSServiceRef mSdRef;
+ DNSServiceFlags mFlags;
+ uint32_t mInterfaceIndex;
+ DNSServiceErrorType mErrorCode;
+ nsCString mHostName;
+ mozilla::net::NetAddr mAddress;
+ uint32_t mTTL;
+ RefPtr<GetAddrInfoOperator> mContext;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_dns_mdns_libmdns_MDNSResponderReply_h
diff --git a/netwerk/dns/mdns/libmdns/MulticastDNSAndroid.jsm b/netwerk/dns/mdns/libmdns/MulticastDNSAndroid.jsm
new file mode 100644
index 0000000000..d94e90efaa
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MulticastDNSAndroid.jsm
@@ -0,0 +1,262 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 EXPORTED_SYMBOLS = ["MulticastDNS"];
+
+const { EventDispatcher } = ChromeUtils.import(
+ "resource://gre/modules/Messaging.jsm"
+);
+
+const DEBUG = false;
+
+var log = function(s) {};
+if (DEBUG) {
+ log = ChromeUtils.import(
+ "resource://gre/modules/AndroidLog.jsm",
+ {}
+ ).AndroidLog.d.bind(null, "MulticastDNS");
+}
+
+const FAILURE_INTERNAL_ERROR = -65537;
+
+// Helper function for sending commands to Java.
+function send(type, data, callback) {
+ let msg = {
+ type,
+ };
+
+ for (let i in data) {
+ try {
+ msg[i] = data[i];
+ } catch (e) {}
+ }
+
+ EventDispatcher.instance.sendRequestForResult(msg).then(
+ result => callback(result, null),
+ err =>
+ callback(null, typeof err === "number" ? err : FAILURE_INTERNAL_ERROR)
+ );
+}
+
+// Receives service found/lost event from NsdManager
+function ServiceManager() {}
+
+ServiceManager.prototype = {
+ listeners: {},
+ numListeners: 0,
+
+ onEvent(event, data, callback) {
+ if (event === "NsdManager:ServiceFound") {
+ this.onServiceFound();
+ } else if (event === "NsdManager:ServiceLost") {
+ this.onServiceLost();
+ }
+ },
+
+ registerEvent() {
+ log("registerEvent");
+ EventDispatcher.instance.registerListener(this, [
+ "NsdManager:ServiceFound",
+ "NsdManager:ServiceLost",
+ ]);
+ },
+
+ unregisterEvent() {
+ log("unregisterEvent");
+ EventDispatcher.instance.unregisterListener(this, [
+ "NsdManager:ServiceFound",
+ "NsdManager:ServiceLost",
+ ]);
+ },
+
+ addListener(aServiceType, aListener) {
+ log("addListener: " + aServiceType + ", " + aListener);
+
+ if (!this.listeners[aServiceType]) {
+ this.listeners[aServiceType] = [];
+ }
+ if (this.listeners[aServiceType].includes(aListener)) {
+ log("listener already exists");
+ return;
+ }
+
+ this.listeners[aServiceType].push(aListener);
+ ++this.numListeners;
+
+ if (this.numListeners === 1) {
+ this.registerEvent();
+ }
+
+ log("listener added: " + this);
+ },
+
+ removeListener(aServiceType, aListener) {
+ log("removeListener: " + aServiceType + ", " + aListener);
+
+ if (!this.listeners[aServiceType]) {
+ log("listener doesn't exist");
+ return;
+ }
+ let index = this.listeners[aServiceType].indexOf(aListener);
+ if (index < 0) {
+ log("listener doesn't exist");
+ return;
+ }
+
+ this.listeners[aServiceType].splice(index, 1);
+ --this.numListeners;
+
+ if (this.numListeners === 0) {
+ this.unregisterEvent();
+ }
+
+ log("listener removed" + this);
+ },
+
+ onServiceFound(aServiceInfo) {
+ let listeners = this.listeners[aServiceInfo.serviceType];
+ if (listeners) {
+ for (let listener of listeners) {
+ listener.onServiceFound(aServiceInfo);
+ }
+ } else {
+ log("no listener");
+ }
+ return {};
+ },
+
+ onServiceLost(aServiceInfo) {
+ let listeners = this.listeners[aServiceInfo.serviceType];
+ if (listeners) {
+ for (let listener of listeners) {
+ listener.onServiceLost(aServiceInfo);
+ }
+ } else {
+ log("no listener");
+ }
+ return {};
+ },
+};
+
+// make an object from nsIPropertyBag2
+function parsePropertyBag2(bag) {
+ if (!bag || !(bag instanceof Ci.nsIPropertyBag2)) {
+ throw new TypeError("Not a property bag");
+ }
+
+ let attributes = [];
+ for (let { name } of bag.enumerator) {
+ let value = bag.getPropertyAsACString(name);
+ attributes.push({
+ name,
+ value,
+ });
+ }
+
+ return attributes;
+}
+
+function MulticastDNS() {
+ this.serviceManager = new ServiceManager();
+}
+
+MulticastDNS.prototype = {
+ startDiscovery(aServiceType, aListener) {
+ this.serviceManager.addListener(aServiceType, aListener);
+
+ let serviceInfo = {
+ serviceType: aServiceType,
+ uniqueId: aListener.uuid,
+ };
+
+ send("NsdManager:DiscoverServices", serviceInfo, (result, err) => {
+ if (err) {
+ log("onStartDiscoveryFailed: " + aServiceType + " (" + err + ")");
+ this.serviceManager.removeListener(aServiceType, aListener);
+ aListener.onStartDiscoveryFailed(aServiceType, err);
+ } else {
+ aListener.onDiscoveryStarted(result);
+ }
+ });
+ },
+
+ stopDiscovery(aServiceType, aListener) {
+ this.serviceManager.removeListener(aServiceType, aListener);
+
+ let serviceInfo = {
+ uniqueId: aListener.uuid,
+ };
+
+ send("NsdManager:StopServiceDiscovery", serviceInfo, (result, err) => {
+ if (err) {
+ log("onStopDiscoveryFailed: " + aServiceType + " (" + err + ")");
+ aListener.onStopDiscoveryFailed(aServiceType, err);
+ } else {
+ aListener.onDiscoveryStopped(aServiceType);
+ }
+ });
+ },
+
+ registerService(aServiceInfo, aListener) {
+ let serviceInfo = {
+ port: aServiceInfo.port,
+ serviceType: aServiceInfo.serviceType,
+ uniqueId: aListener.uuid,
+ };
+
+ try {
+ serviceInfo.host = aServiceInfo.host;
+ } catch (e) {
+ // host unspecified
+ }
+ try {
+ serviceInfo.serviceName = aServiceInfo.serviceName;
+ } catch (e) {
+ // serviceName unspecified
+ }
+ try {
+ serviceInfo.attributes = parsePropertyBag2(aServiceInfo.attributes);
+ } catch (e) {
+ // attributes unspecified
+ }
+
+ send("NsdManager:RegisterService", serviceInfo, (result, err) => {
+ if (err) {
+ log("onRegistrationFailed: (" + err + ")");
+ aListener.onRegistrationFailed(aServiceInfo, err);
+ } else {
+ aListener.onServiceRegistered(result);
+ }
+ });
+ },
+
+ unregisterService(aServiceInfo, aListener) {
+ let serviceInfo = {
+ uniqueId: aListener.uuid,
+ };
+
+ send("NsdManager:UnregisterService", serviceInfo, (result, err) => {
+ if (err) {
+ log("onUnregistrationFailed: (" + err + ")");
+ aListener.onUnregistrationFailed(aServiceInfo, err);
+ } else {
+ aListener.onServiceUnregistered(aServiceInfo);
+ }
+ });
+ },
+
+ resolveService(aServiceInfo, aListener) {
+ send("NsdManager:ResolveService", aServiceInfo, (result, err) => {
+ if (err) {
+ log("onResolveFailed: (" + err + ")");
+ aListener.onResolveFailed(aServiceInfo, err);
+ } else {
+ aListener.onServiceResolved(result);
+ }
+ });
+ },
+};
diff --git a/netwerk/dns/mdns/libmdns/components.conf b/netwerk/dns/mdns/libmdns/components.conf
new file mode 100644
index 0000000000..6e64140c82
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/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': '{14a50f2b-7ff6-48a5-88e3-615fd111f5d3}',
+ 'contract_ids': ['@mozilla.org/toolkit/components/mdnsresponder/dns-info;1'],
+ 'type': 'mozilla::net::nsDNSServiceInfo',
+ 'headers': ['/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.h'],
+ },
+]
+
+if buildconfig.substs['MOZ_WIDGET_TOOLKIT'] != 'cocoa':
+ Classes += [
+ {
+ 'cid': '{f9346d98-f27a-4e89-b744-493843416480}',
+ 'contract_ids': ['@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1'],
+ 'jsm': 'resource://gre/modules/DNSServiceDiscovery.jsm',
+ 'constructor': 'nsDNSServiceDiscovery',
+ },
+ ]
diff --git a/netwerk/dns/mdns/libmdns/fallback/DNSPacket.jsm b/netwerk/dns/mdns/libmdns/fallback/DNSPacket.jsm
new file mode 100644
index 0000000000..1dd859c6d0
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DNSPacket.jsm
@@ -0,0 +1,304 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 EXPORTED_SYMBOLS = ["DNSPacket"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const { DataReader } = ChromeUtils.import(
+ "resource://gre/modules/DataReader.jsm"
+);
+const { DataWriter } = ChromeUtils.import(
+ "resource://gre/modules/DataWriter.jsm"
+);
+const { DNSRecord } = ChromeUtils.import(
+ "resource://gre/modules/DNSRecord.jsm"
+);
+const { DNSResourceRecord } = ChromeUtils.import(
+ "resource://gre/modules/DNSResourceRecord.jsm"
+);
+
+const DEBUG = true;
+
+function debug(msg) {
+ Services.console.logStringMessage("DNSPacket: " + msg);
+}
+
+let DNS_PACKET_SECTION_TYPES = [
+ "QD", // Question
+ "AN", // Answer
+ "NS", // Authority
+ "AR", // Additional
+];
+
+/**
+ * DNS Packet Structure
+ * *************************************************
+ *
+ * Header
+ * ======
+ *
+ * 00 2-Bytes 15
+ * -------------------------------------------------
+ * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
+ * -------------------------------------------------
+ * |<==================== ID =====================>|
+ * |QR|<== OP ===>|AA|TC|RD|RA|UN|AD|CD|<== RC ===>|
+ * |<================== QDCOUNT ==================>|
+ * |<================== ANCOUNT ==================>|
+ * |<================== NSCOUNT ==================>|
+ * |<================== ARCOUNT ==================>|
+ * -------------------------------------------------
+ *
+ * ID: 2-Bytes
+ * FLAGS: 2-Bytes
+ * - QR: 1-Bit
+ * - OP: 4-Bits
+ * - AA: 1-Bit
+ * - TC: 1-Bit
+ * - RD: 1-Bit
+ * - RA: 1-Bit
+ * - UN: 1-Bit
+ * - AD: 1-Bit
+ * - CD: 1-Bit
+ * - RC: 4-Bits
+ * QDCOUNT: 2-Bytes
+ * ANCOUNT: 2-Bytes
+ * NSCOUNT: 2-Bytes
+ * ARCOUNT: 2-Bytes
+ *
+ *
+ * Data
+ * ====
+ *
+ * 00 2-Bytes 15
+ * -------------------------------------------------
+ * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
+ * -------------------------------------------------
+ * |<???=============== QD[...] ===============???>|
+ * |<???=============== AN[...] ===============???>|
+ * |<???=============== NS[...] ===============???>|
+ * |<???=============== AR[...] ===============???>|
+ * -------------------------------------------------
+ *
+ * QD: ??-Bytes
+ * AN: ??-Bytes
+ * NS: ??-Bytes
+ * AR: ??-Bytes
+ *
+ *
+ * Question Record
+ * ===============
+ *
+ * 00 2-Bytes 15
+ * -------------------------------------------------
+ * |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|
+ * -------------------------------------------------
+ * |<???================ NAME =================???>|
+ * |<=================== TYPE ====================>|
+ * |<=================== CLASS ===================>|
+ * -------------------------------------------------
+ *
+ * NAME: ??-Bytes
+ * TYPE: 2-Bytes
+ * CLASS: 2-Bytes
+ *
+ *
+ * Resource Record
+ * ===============
+ *
+ * 00 4-Bytes 31
+ * -------------------------------------------------
+ * |00|02|04|06|08|10|12|14|16|18|20|22|24|26|28|30|
+ * -------------------------------------------------
+ * |<???================ NAME =================???>|
+ * |<======= TYPE ========>|<======= CLASS =======>|
+ * |<==================== TTL ====================>|
+ * |<====== DATALEN ======>|<???==== DATA =====???>|
+ * -------------------------------------------------
+ *
+ * NAME: ??-Bytes
+ * TYPE: 2-Bytes
+ * CLASS: 2-Bytes
+ * DATALEN: 2-Bytes
+ * DATA: ??-Bytes (Specified By DATALEN)
+ */
+class DNSPacket {
+ constructor() {
+ this._flags = _valueToFlags(0x0000);
+ this._records = {};
+
+ DNS_PACKET_SECTION_TYPES.forEach(sectionType => {
+ this._records[sectionType] = [];
+ });
+ }
+
+ static parse(data) {
+ let reader = new DataReader(data);
+ if (reader.getValue(2) !== 0x0000) {
+ throw new Error("Packet must start with 0x0000");
+ }
+
+ let packet = new DNSPacket();
+ packet._flags = _valueToFlags(reader.getValue(2));
+
+ let recordCounts = {};
+
+ // Parse the record counts.
+ DNS_PACKET_SECTION_TYPES.forEach(sectionType => {
+ recordCounts[sectionType] = reader.getValue(2);
+ });
+
+ // Parse the actual records.
+ DNS_PACKET_SECTION_TYPES.forEach(sectionType => {
+ let recordCount = recordCounts[sectionType];
+ for (let i = 0; i < recordCount; i++) {
+ if (sectionType === "QD") {
+ packet.addRecord(
+ sectionType,
+ DNSRecord.parseFromPacketReader(reader)
+ );
+ } else {
+ packet.addRecord(
+ sectionType,
+ DNSResourceRecord.parseFromPacketReader(reader)
+ );
+ }
+ }
+ });
+
+ if (!reader.eof) {
+ DEBUG && debug("Did not complete parsing packet data");
+ }
+
+ return packet;
+ }
+
+ getFlag(flag) {
+ return this._flags[flag];
+ }
+
+ setFlag(flag, value) {
+ this._flags[flag] = value;
+ }
+
+ addRecord(sectionType, record) {
+ this._records[sectionType].push(record);
+ }
+
+ getRecords(sectionTypes, recordType) {
+ let records = [];
+
+ sectionTypes.forEach(sectionType => {
+ records = records.concat(this._records[sectionType]);
+ });
+
+ if (!recordType) {
+ return records;
+ }
+
+ return records.filter(r => r.recordType === recordType);
+ }
+
+ serialize() {
+ let writer = new DataWriter();
+
+ // Write leading 0x0000 (2 bytes)
+ writer.putValue(0x0000, 2);
+
+ // Write `flags` (2 bytes)
+ writer.putValue(_flagsToValue(this._flags), 2);
+
+ // Write lengths of record sections (2 bytes each)
+ DNS_PACKET_SECTION_TYPES.forEach(sectionType => {
+ writer.putValue(this._records[sectionType].length, 2);
+ });
+
+ // Write records
+ DNS_PACKET_SECTION_TYPES.forEach(sectionType => {
+ this._records[sectionType].forEach(record => {
+ writer.putBytes(record.serialize());
+ });
+ });
+
+ return writer.data;
+ }
+
+ toJSON() {
+ return JSON.stringify(this.toJSONObject());
+ }
+
+ toJSONObject() {
+ let result = { flags: this._flags };
+ DNS_PACKET_SECTION_TYPES.forEach(sectionType => {
+ result[sectionType] = [];
+
+ let records = this._records[sectionType];
+ records.forEach(record => {
+ result[sectionType].push(record.toJSONObject());
+ });
+ });
+
+ return result;
+ }
+}
+
+/**
+ * @private
+ */
+function _valueToFlags(value) {
+ return {
+ QR: (value & 0x8000) >> 15,
+ OP: (value & 0x7800) >> 11,
+ AA: (value & 0x0400) >> 10,
+ TC: (value & 0x0200) >> 9,
+ RD: (value & 0x0100) >> 8,
+ RA: (value & 0x0080) >> 7,
+ UN: (value & 0x0040) >> 6,
+ AD: (value & 0x0020) >> 5,
+ CD: (value & 0x0010) >> 4,
+ RC: (value & 0x000f) >> 0,
+ };
+}
+
+/**
+ * @private
+ */
+function _flagsToValue(flags) {
+ let value = 0x0000;
+
+ value += flags.QR & 0x01;
+
+ value <<= 4;
+ value += flags.OP & 0x0f;
+
+ value <<= 1;
+ value += flags.AA & 0x01;
+
+ value <<= 1;
+ value += flags.TC & 0x01;
+
+ value <<= 1;
+ value += flags.RD & 0x01;
+
+ value <<= 1;
+ value += flags.RA & 0x01;
+
+ value <<= 1;
+ value += flags.UN & 0x01;
+
+ value <<= 1;
+ value += flags.AD & 0x01;
+
+ value <<= 1;
+ value += flags.CD & 0x01;
+
+ value <<= 4;
+ value += flags.RC & 0x0f;
+
+ return value;
+}
diff --git a/netwerk/dns/mdns/libmdns/fallback/DNSRecord.jsm b/netwerk/dns/mdns/libmdns/fallback/DNSRecord.jsm
new file mode 100644
index 0000000000..682fd8b103
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DNSRecord.jsm
@@ -0,0 +1,71 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 EXPORTED_SYMBOLS = ["DNSRecord"];
+
+const { DataWriter } = ChromeUtils.import(
+ "resource://gre/modules/DataWriter.jsm"
+);
+const { DNS_CLASS_CODES, DNS_RECORD_TYPES } = ChromeUtils.import(
+ "resource://gre/modules/DNSTypes.jsm"
+);
+
+class DNSRecord {
+ constructor(properties = {}) {
+ this.name = properties.name || "";
+ this.recordType = properties.recordType || DNS_RECORD_TYPES.ANY;
+ this.classCode = properties.classCode || DNS_CLASS_CODES.IN;
+ this.cacheFlush = properties.cacheFlush || false;
+ }
+
+ static parseFromPacketReader(reader) {
+ let name = reader.getLabel();
+ let recordType = reader.getValue(2);
+ let classCode = reader.getValue(2);
+ let cacheFlush = !!(classCode & 0x8000);
+ classCode &= 0xff;
+
+ return new this({
+ name,
+ recordType,
+ classCode,
+ cacheFlush,
+ });
+ }
+
+ serialize() {
+ let writer = new DataWriter();
+
+ // Write `name` (ends with trailing 0x00 byte)
+ writer.putLabel(this.name);
+
+ // Write `recordType` (2 bytes)
+ writer.putValue(this.recordType, 2);
+
+ // Write `classCode` (2 bytes)
+ let classCode = this.classCode;
+ if (this.cacheFlush) {
+ classCode |= 0x8000;
+ }
+ writer.putValue(classCode, 2);
+
+ return writer.data;
+ }
+
+ toJSON() {
+ return JSON.stringify(this.toJSONObject());
+ }
+
+ toJSONObject() {
+ return {
+ name: this.name,
+ recordType: this.recordType,
+ classCode: this.classCode,
+ cacheFlush: this.cacheFlush,
+ };
+ }
+}
diff --git a/netwerk/dns/mdns/libmdns/fallback/DNSResourceRecord.jsm b/netwerk/dns/mdns/libmdns/fallback/DNSResourceRecord.jsm
new file mode 100644
index 0000000000..e89f418410
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DNSResourceRecord.jsm
@@ -0,0 +1,221 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 EXPORTED_SYMBOLS = ["DNSResourceRecord"];
+
+const { DataReader } = ChromeUtils.import(
+ "resource://gre/modules/DataReader.jsm"
+);
+const { DataWriter } = ChromeUtils.import(
+ "resource://gre/modules/DataWriter.jsm"
+);
+const { DNSRecord } = ChromeUtils.import(
+ "resource://gre/modules/DNSRecord.jsm"
+);
+const { DNS_RECORD_TYPES } = ChromeUtils.import(
+ "resource://gre/modules/DNSTypes.jsm"
+);
+
+const DNS_RESOURCE_RECORD_DEFAULT_TTL = 120; // 120 seconds
+
+class DNSResourceRecord extends DNSRecord {
+ constructor(properties = {}) {
+ super(properties);
+
+ this.ttl = properties.ttl || DNS_RESOURCE_RECORD_DEFAULT_TTL;
+ this.data = properties.data || {};
+ }
+
+ static parseFromPacketReader(reader) {
+ let record = super.parseFromPacketReader(reader);
+
+ let ttl = reader.getValue(4);
+ let recordData = reader.getBytes(reader.getValue(2));
+ let packetData = reader.data;
+
+ let data;
+
+ switch (record.recordType) {
+ case DNS_RECORD_TYPES.A:
+ data = _parseA(recordData, packetData);
+ break;
+ case DNS_RECORD_TYPES.PTR:
+ data = _parsePTR(recordData, packetData);
+ break;
+ case DNS_RECORD_TYPES.TXT:
+ data = _parseTXT(recordData, packetData);
+ break;
+ case DNS_RECORD_TYPES.SRV:
+ data = _parseSRV(recordData, packetData);
+ break;
+ default:
+ data = null;
+ break;
+ }
+
+ record.ttl = ttl;
+ record.data = data;
+
+ return record;
+ }
+
+ serialize() {
+ let writer = new DataWriter(super.serialize());
+
+ // Write `ttl` (4 bytes)
+ writer.putValue(this.ttl, 4);
+
+ let data;
+
+ switch (this.recordType) {
+ case DNS_RECORD_TYPES.A:
+ data = _serializeA(this.data);
+ break;
+ case DNS_RECORD_TYPES.PTR:
+ data = _serializePTR(this.data);
+ break;
+ case DNS_RECORD_TYPES.TXT:
+ data = _serializeTXT(this.data);
+ break;
+ case DNS_RECORD_TYPES.SRV:
+ data = _serializeSRV(this.data);
+ break;
+ default:
+ data = new Uint8Array();
+ break;
+ }
+
+ // Write `data` length.
+ writer.putValue(data.length, 2);
+
+ // Write `data` (ends with trailing 0x00 byte)
+ writer.putBytes(data);
+
+ return writer.data;
+ }
+
+ toJSON() {
+ return JSON.stringify(this.toJSONObject());
+ }
+
+ toJSONObject() {
+ let result = super.toJSONObject();
+ result.ttl = this.ttl;
+ result.data = this.data;
+ return result;
+ }
+}
+
+/**
+ * @private
+ */
+function _parseA(recordData, packetData) {
+ let reader = new DataReader(recordData);
+
+ let parts = [];
+ for (let i = 0; i < 4; i++) {
+ parts.push(reader.getValue(1));
+ }
+
+ return parts.join(".");
+}
+
+/**
+ * @private
+ */
+function _parsePTR(recordData, packetData) {
+ let reader = new DataReader(recordData);
+
+ return reader.getLabel(packetData);
+}
+
+/**
+ * @private
+ */
+function _parseTXT(recordData, packetData) {
+ let reader = new DataReader(recordData);
+
+ let result = {};
+
+ let label = reader.getLabel(packetData);
+ if (label.length > 0) {
+ let parts = label.split(".");
+ parts.forEach(part => {
+ let [name] = part.split("=", 1);
+ let value = part.substr(name.length + 1);
+ result[name] = value;
+ });
+ }
+
+ return result;
+}
+
+/**
+ * @private
+ */
+function _parseSRV(recordData, packetData) {
+ let reader = new DataReader(recordData);
+
+ let priority = reader.getValue(2);
+ let weight = reader.getValue(2);
+ let port = reader.getValue(2);
+ let target = reader.getLabel(packetData);
+
+ return { priority, weight, port, target };
+}
+
+/**
+ * @private
+ */
+function _serializeA(data) {
+ let writer = new DataWriter();
+
+ let parts = data.split(".");
+ for (let i = 0; i < 4; i++) {
+ writer.putValue(parseInt(parts[i], 10) || 0);
+ }
+
+ return writer.data;
+}
+
+/**
+ * @private
+ */
+function _serializePTR(data) {
+ let writer = new DataWriter();
+
+ writer.putLabel(data);
+
+ return writer.data;
+}
+
+/**
+ * @private
+ */
+function _serializeTXT(data) {
+ let writer = new DataWriter();
+
+ for (let name in data) {
+ writer.putLengthString(name + "=" + data[name]);
+ }
+
+ return writer.data;
+}
+
+/**
+ * @private
+ */
+function _serializeSRV(data) {
+ let writer = new DataWriter();
+
+ writer.putValue(data.priority || 0, 2);
+ writer.putValue(data.weight || 0, 2);
+ writer.putValue(data.port || 0, 2);
+ writer.putLabel(data.target);
+
+ return writer.data;
+}
diff --git a/netwerk/dns/mdns/libmdns/fallback/DNSTypes.jsm b/netwerk/dns/mdns/libmdns/fallback/DNSTypes.jsm
new file mode 100644
index 0000000000..878e22917d
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DNSTypes.jsm
@@ -0,0 +1,99 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 EXPORTED_SYMBOLS = [
+ "DNS_QUERY_RESPONSE_CODES",
+ "DNS_AUTHORITATIVE_ANSWER_CODES",
+ "DNS_CLASS_CODES",
+ "DNS_RECORD_TYPES",
+];
+
+let DNS_QUERY_RESPONSE_CODES = {
+ QUERY: 0, // RFC 1035 - Query
+ RESPONSE: 1, // RFC 1035 - Reponse
+};
+
+let DNS_AUTHORITATIVE_ANSWER_CODES = {
+ NO: 0, // RFC 1035 - Not Authoritative
+ YES: 1, // RFC 1035 - Is Authoritative
+};
+
+let DNS_CLASS_CODES = {
+ IN: 0x01, // RFC 1035 - Internet
+ CS: 0x02, // RFC 1035 - CSNET
+ CH: 0x03, // RFC 1035 - CHAOS
+ HS: 0x04, // RFC 1035 - Hesiod
+ NONE: 0xfe, // RFC 2136 - None
+ ANY: 0xff, // RFC 1035 - Any
+};
+
+let DNS_RECORD_TYPES = {
+ SIGZERO: 0, // RFC 2931
+ A: 1, // RFC 1035
+ NS: 2, // RFC 1035
+ MD: 3, // RFC 1035
+ MF: 4, // RFC 1035
+ CNAME: 5, // RFC 1035
+ SOA: 6, // RFC 1035
+ MB: 7, // RFC 1035
+ MG: 8, // RFC 1035
+ MR: 9, // RFC 1035
+ NULL: 10, // RFC 1035
+ WKS: 11, // RFC 1035
+ PTR: 12, // RFC 1035
+ HINFO: 13, // RFC 1035
+ MINFO: 14, // RFC 1035
+ MX: 15, // RFC 1035
+ TXT: 16, // RFC 1035
+ RP: 17, // RFC 1183
+ AFSDB: 18, // RFC 1183
+ X25: 19, // RFC 1183
+ ISDN: 20, // RFC 1183
+ RT: 21, // RFC 1183
+ NSAP: 22, // RFC 1706
+ NSAP_PTR: 23, // RFC 1348
+ SIG: 24, // RFC 2535
+ KEY: 25, // RFC 2535
+ PX: 26, // RFC 2163
+ GPOS: 27, // RFC 1712
+ AAAA: 28, // RFC 1886
+ LOC: 29, // RFC 1876
+ NXT: 30, // RFC 2535
+ EID: 31, // RFC ????
+ NIMLOC: 32, // RFC ????
+ SRV: 33, // RFC 2052
+ ATMA: 34, // RFC ????
+ NAPTR: 35, // RFC 2168
+ KX: 36, // RFC 2230
+ CERT: 37, // RFC 2538
+ DNAME: 39, // RFC 2672
+ OPT: 41, // RFC 2671
+ APL: 42, // RFC 3123
+ DS: 43, // RFC 4034
+ SSHFP: 44, // RFC 4255
+ IPSECKEY: 45, // RFC 4025
+ RRSIG: 46, // RFC 4034
+ NSEC: 47, // RFC 4034
+ DNSKEY: 48, // RFC 4034
+ DHCID: 49, // RFC 4701
+ NSEC3: 50, // RFC ????
+ NSEC3PARAM: 51, // RFC ????
+ HIP: 55, // RFC 5205
+ SPF: 99, // RFC 4408
+ UINFO: 100, // RFC ????
+ UID: 101, // RFC ????
+ GID: 102, // RFC ????
+ UNSPEC: 103, // RFC ????
+ TKEY: 249, // RFC 2930
+ TSIG: 250, // RFC 2931
+ IXFR: 251, // RFC 1995
+ AXFR: 252, // RFC 1035
+ MAILB: 253, // RFC 1035
+ MAILA: 254, // RFC 1035
+ ANY: 255, // RFC 1035
+ DLV: 32769, // RFC 4431
+};
diff --git a/netwerk/dns/mdns/libmdns/fallback/DataReader.jsm b/netwerk/dns/mdns/libmdns/fallback/DataReader.jsm
new file mode 100644
index 0000000000..978ab4dde0
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DataReader.jsm
@@ -0,0 +1,134 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 EXPORTED_SYMBOLS = ["DataReader"];
+
+class DataReader {
+ // `data` is `Uint8Array`
+ constructor(data, startByte = 0) {
+ this._data = data;
+ this._cursor = startByte;
+ }
+
+ get buffer() {
+ return this._data.buffer;
+ }
+
+ get data() {
+ return this._data;
+ }
+
+ get eof() {
+ return this._cursor >= this._data.length;
+ }
+
+ getBytes(length = 1) {
+ if (!length) {
+ return new Uint8Array();
+ }
+
+ let end = this._cursor + length;
+ if (end > this._data.length) {
+ return new Uint8Array();
+ }
+
+ let uint8Array = new Uint8Array(this.buffer.slice(this._cursor, end));
+ this._cursor += length;
+
+ return uint8Array;
+ }
+
+ getString(length) {
+ let uint8Array = this.getBytes(length);
+ return _uint8ArrayToString(uint8Array);
+ }
+
+ getValue(length) {
+ let uint8Array = this.getBytes(length);
+ return _uint8ArrayToValue(uint8Array);
+ }
+
+ getLabel(decompressData) {
+ let parts = [];
+ let partLength;
+
+ while ((partLength = this.getValue(1))) {
+ // If a length has been specified instead of a pointer,
+ // read the string of the specified length.
+ if (partLength !== 0xc0) {
+ parts.push(this.getString(partLength));
+ continue;
+ }
+
+ // TODO: Handle case where we have a pointer to the label
+ parts.push(String.fromCharCode(0xc0) + this.getString(1));
+ break;
+ }
+
+ let label = parts.join(".");
+
+ return _decompressLabel(label, decompressData || this._data);
+ }
+}
+
+/**
+ * @private
+ */
+function _uint8ArrayToValue(uint8Array) {
+ let length = uint8Array.length;
+ if (length === 0) {
+ return null;
+ }
+
+ let value = 0;
+ for (let i = 0; i < length; i++) {
+ value = value << 8;
+ value += uint8Array[i];
+ }
+
+ return value;
+}
+
+/**
+ * @private
+ */
+function _uint8ArrayToString(uint8Array) {
+ let length = uint8Array.length;
+ if (length === 0) {
+ return "";
+ }
+
+ let results = [];
+ for (let i = 0; i < length; i += 1024) {
+ results.push(
+ String.fromCharCode.apply(null, uint8Array.subarray(i, i + 1024))
+ );
+ }
+
+ return results.join("");
+}
+
+/**
+ * @private
+ */
+function _decompressLabel(label, decompressData) {
+ let result = "";
+
+ for (let i = 0, length = label.length; i < length; i++) {
+ if (label.charCodeAt(i) !== 0xc0) {
+ result += label.charAt(i);
+ continue;
+ }
+
+ i++;
+
+ let reader = new DataReader(decompressData, label.charCodeAt(i));
+ result += _decompressLabel(reader.getLabel(), decompressData);
+ }
+
+ return result;
+}
diff --git a/netwerk/dns/mdns/libmdns/fallback/DataWriter.jsm b/netwerk/dns/mdns/libmdns/fallback/DataWriter.jsm
new file mode 100644
index 0000000000..05949a1d05
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/DataWriter.jsm
@@ -0,0 +1,97 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 EXPORTED_SYMBOLS = ["DataWriter"];
+
+class DataWriter {
+ constructor(data, maxBytes = 512) {
+ if (typeof data === "number") {
+ maxBytes = data;
+ data = undefined;
+ }
+
+ this._buffer = new ArrayBuffer(maxBytes);
+ this._data = new Uint8Array(this._buffer);
+ this._cursor = 0;
+
+ if (data) {
+ this.putBytes(data);
+ }
+ }
+
+ get buffer() {
+ return this._buffer.slice(0, this._cursor);
+ }
+
+ get data() {
+ return new Uint8Array(this.buffer);
+ }
+
+ // `data` is `Uint8Array`
+ putBytes(data) {
+ if (this._cursor + data.length > this._data.length) {
+ throw new Error("DataWriter buffer is exceeded");
+ }
+
+ for (let i = 0, length = data.length; i < length; i++) {
+ this._data[this._cursor] = data[i];
+ this._cursor++;
+ }
+ }
+
+ putByte(byte) {
+ if (this._cursor + 1 > this._data.length) {
+ throw new Error("DataWriter buffer is exceeded");
+ }
+
+ this._data[this._cursor] = byte;
+ this._cursor++;
+ }
+
+ putValue(value, length) {
+ length = length || 1;
+ if (length == 1) {
+ this.putByte(value);
+ } else {
+ this.putBytes(_valueToUint8Array(value, length));
+ }
+ }
+
+ putLabel(label) {
+ // Eliminate any trailing '.'s in the label (valid in text representation).
+ label = label.replace(/\.$/, "");
+ let parts = label.split(".");
+ parts.forEach(part => {
+ this.putLengthString(part);
+ });
+ this.putValue(0);
+ }
+
+ putLengthString(string) {
+ if (string.length > 0xff) {
+ throw new Error("String too long.");
+ }
+ this.putValue(string.length);
+ for (let i = 0; i < string.length; i++) {
+ this.putValue(string.charCodeAt(i));
+ }
+ }
+}
+
+/**
+ * @private
+ */
+function _valueToUint8Array(value, length) {
+ let arrayBuffer = new ArrayBuffer(length);
+ let uint8Array = new Uint8Array(arrayBuffer);
+ for (let i = length - 1; i >= 0; i--) {
+ uint8Array[i] = value & 0xff;
+ value = value >> 8;
+ }
+
+ return uint8Array;
+}
diff --git a/netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm b/netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm
new file mode 100644
index 0000000000..efc21fe523
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/fallback/MulticastDNS.jsm
@@ -0,0 +1,985 @@
+/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 EXPORTED_SYMBOLS = ["MulticastDNS"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { clearTimeout, setTimeout } = ChromeUtils.import(
+ "resource://gre/modules/Timer.jsm"
+);
+
+const { DNSPacket } = ChromeUtils.import(
+ "resource://gre/modules/DNSPacket.jsm"
+);
+const { DNSRecord } = ChromeUtils.import(
+ "resource://gre/modules/DNSRecord.jsm"
+);
+const { DNSResourceRecord } = ChromeUtils.import(
+ "resource://gre/modules/DNSResourceRecord.jsm"
+);
+const {
+ DNS_AUTHORITATIVE_ANSWER_CODES,
+ DNS_CLASS_CODES,
+ DNS_QUERY_RESPONSE_CODES,
+ DNS_RECORD_TYPES,
+} = ChromeUtils.import("resource://gre/modules/DNSTypes.jsm");
+
+const NS_NETWORK_LINK_TOPIC = "network:link-status-changed";
+
+let networkInfoService = Cc[
+ "@mozilla.org/network-info-service;1"
+].createInstance(Ci.nsINetworkInfoService);
+
+const DEBUG = true;
+
+const MDNS_MULTICAST_GROUP = "224.0.0.251";
+const MDNS_PORT = 5353;
+const DEFAULT_TTL = 120;
+
+function debug(msg) {
+ dump("MulticastDNS: " + msg + "\n");
+}
+
+function ServiceKey(svc) {
+ return (
+ "" +
+ svc.serviceType.length +
+ "/" +
+ svc.serviceType +
+ "|" +
+ svc.serviceName.length +
+ "/" +
+ svc.serviceName +
+ "|" +
+ svc.port
+ );
+}
+
+function TryGet(obj, name) {
+ try {
+ return obj[name];
+ } catch (err) {
+ return undefined;
+ }
+}
+
+function IsIpv4Address(addr) {
+ let parts = addr.split(".");
+ if (parts.length != 4) {
+ return false;
+ }
+ for (let part of parts) {
+ let partInt = Number.parseInt(part, 10);
+ if (partInt.toString() != part) {
+ return false;
+ }
+ if (partInt < 0 || partInt >= 256) {
+ return false;
+ }
+ }
+ return true;
+}
+
+class PublishedService {
+ constructor(attrs) {
+ this.serviceType = attrs.serviceType.replace(/\.$/, "");
+ this.serviceName = attrs.serviceName;
+ this.domainName = TryGet(attrs, "domainName") || "local";
+ this.address = TryGet(attrs, "address") || "0.0.0.0";
+ this.port = attrs.port;
+ this.serviceAttrs = _propertyBagToObject(TryGet(attrs, "attributes") || {});
+ this.host = TryGet(attrs, "host");
+ this.key = this.generateKey();
+ this.lastAdvertised = undefined;
+ this.advertiseTimer = undefined;
+ }
+
+ equals(svc) {
+ return (
+ this.port == svc.port &&
+ this.serviceName == svc.serviceName &&
+ this.serviceType == svc.serviceType
+ );
+ }
+
+ generateKey() {
+ return ServiceKey(this);
+ }
+
+ ptrMatch(name) {
+ return name == this.serviceType + "." + this.domainName;
+ }
+
+ clearAdvertiseTimer() {
+ if (!this.advertiseTimer) {
+ return;
+ }
+ clearTimeout(this.advertiseTimer);
+ this.advertiseTimer = undefined;
+ }
+}
+
+class MulticastDNS {
+ constructor() {
+ this._listeners = new Map();
+ this._sockets = new Map();
+ this._services = new Map();
+ this._discovered = new Map();
+ this._querySocket = undefined;
+ this._broadcastReceiverSocket = undefined;
+ this._broadcastTimer = undefined;
+
+ this._networkLinkObserver = {
+ observe: (subject, topic, data) => {
+ DEBUG &&
+ debug(
+ NS_NETWORK_LINK_TOPIC +
+ "(" +
+ data +
+ "); Clearing list of previously discovered services"
+ );
+ this._discovered.clear();
+ },
+ };
+ }
+
+ _attachNetworkLinkObserver() {
+ if (this._networkLinkObserverTimeout) {
+ clearTimeout(this._networkLinkObserverTimeout);
+ }
+
+ if (!this._isNetworkLinkObserverAttached) {
+ DEBUG && debug("Attaching observer " + NS_NETWORK_LINK_TOPIC);
+ Services.obs.addObserver(
+ this._networkLinkObserver,
+ NS_NETWORK_LINK_TOPIC
+ );
+ this._isNetworkLinkObserverAttached = true;
+ }
+ }
+
+ _detachNetworkLinkObserver() {
+ if (this._isNetworkLinkObserverAttached) {
+ if (this._networkLinkObserverTimeout) {
+ clearTimeout(this._networkLinkObserverTimeout);
+ }
+
+ this._networkLinkObserverTimeout = setTimeout(() => {
+ DEBUG && debug("Detaching observer " + NS_NETWORK_LINK_TOPIC);
+ Services.obs.removeObserver(
+ this._networkLinkObserver,
+ NS_NETWORK_LINK_TOPIC
+ );
+ this._isNetworkLinkObserverAttached = false;
+ this._networkLinkObserverTimeout = null;
+ }, 5000);
+ }
+ }
+
+ startDiscovery(aServiceType, aListener) {
+ DEBUG && debug('startDiscovery("' + aServiceType + '")');
+ let { serviceType } = _parseServiceDomainName(aServiceType);
+
+ this._attachNetworkLinkObserver();
+ this._addServiceListener(serviceType, aListener);
+
+ try {
+ this._query(serviceType + ".local");
+ aListener.onDiscoveryStarted(serviceType);
+ } catch (e) {
+ DEBUG && debug('startDiscovery("' + serviceType + '") FAILED: ' + e);
+ this._removeServiceListener(serviceType, aListener);
+ aListener.onStartDiscoveryFailed(serviceType, Cr.NS_ERROR_FAILURE);
+ }
+ }
+
+ stopDiscovery(aServiceType, aListener) {
+ DEBUG && debug('stopDiscovery("' + aServiceType + '")');
+ let { serviceType } = _parseServiceDomainName(aServiceType);
+
+ this._detachNetworkLinkObserver();
+ this._removeServiceListener(serviceType, aListener);
+
+ aListener.onDiscoveryStopped(serviceType);
+
+ this._checkCloseSockets();
+ }
+
+ resolveService(aServiceInfo, aListener) {
+ DEBUG && debug("resolveService(): " + aServiceInfo.serviceName);
+
+ // Address info is already resolved during discovery
+ setTimeout(() => aListener.onServiceResolved(aServiceInfo));
+ }
+
+ registerService(aServiceInfo, aListener) {
+ DEBUG && debug("registerService(): " + aServiceInfo.serviceName);
+
+ // Initialize the broadcast receiver socket in case it
+ // hasn't already been started so we can listen for
+ // multicast queries/announcements on all interfaces.
+ this._getBroadcastReceiverSocket();
+
+ for (let name of ["port", "serviceName", "serviceType"]) {
+ if (!TryGet(aServiceInfo, name)) {
+ aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE);
+ throw new Error('Invalid nsIDNSServiceInfo; Missing "' + name + '"');
+ }
+ }
+
+ let publishedService;
+ try {
+ publishedService = new PublishedService(aServiceInfo);
+ } catch (e) {
+ DEBUG &&
+ debug("Error constructing PublishedService: " + e + " - " + e.stack);
+ setTimeout(() =>
+ aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE)
+ );
+ return;
+ }
+
+ // Ensure such a service does not already exist.
+ if (this._services.get(publishedService.key)) {
+ setTimeout(() =>
+ aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE)
+ );
+ return;
+ }
+
+ // Make sure that the service addr is '0.0.0.0', or there is at least one
+ // socket open on the address the service is open on.
+ this._getSockets().then(sockets => {
+ if (
+ publishedService.address != "0.0.0.0" &&
+ !sockets.get(publishedService.address)
+ ) {
+ setTimeout(() =>
+ aListener.onRegistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE)
+ );
+ return;
+ }
+
+ this._services.set(publishedService.key, publishedService);
+
+ // Service registered.. call onServiceRegistered on next tick.
+ setTimeout(() => aListener.onServiceRegistered(aServiceInfo));
+
+ // Set a timeout to start advertising the service too.
+ publishedService.advertiseTimer = setTimeout(() => {
+ this._advertiseService(publishedService.key, /* firstAdv = */ true);
+ });
+ });
+ }
+
+ unregisterService(aServiceInfo, aListener) {
+ DEBUG && debug("unregisterService(): " + aServiceInfo.serviceName);
+
+ let serviceKey;
+ try {
+ serviceKey = ServiceKey(aServiceInfo);
+ } catch (e) {
+ setTimeout(() =>
+ aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE)
+ );
+ return;
+ }
+
+ let publishedService = this._services.get(serviceKey);
+ if (!publishedService) {
+ setTimeout(() =>
+ aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE)
+ );
+ return;
+ }
+
+ // Clear any advertise timeout for this published service.
+ publishedService.clearAdvertiseTimer();
+
+ // Delete the service from the service map.
+ if (!this._services.delete(serviceKey)) {
+ setTimeout(() =>
+ aListener.onUnregistrationFailed(aServiceInfo, Cr.NS_ERROR_FAILURE)
+ );
+ return;
+ }
+
+ // Check the broadcast timer again to rejig when it should run next.
+ this._checkStartBroadcastTimer();
+
+ // Check to see if sockets should be closed, and if so close them.
+ this._checkCloseSockets();
+
+ aListener.onServiceUnregistered(aServiceInfo);
+ }
+
+ _respondToQuery(serviceKey, message) {
+ let address = message.fromAddr.address;
+ let port = message.fromAddr.port;
+ DEBUG &&
+ debug(
+ "_respondToQuery(): key=" +
+ serviceKey +
+ ", fromAddr=" +
+ address +
+ ":" +
+ port
+ );
+
+ let publishedService = this._services.get(serviceKey);
+ if (!publishedService) {
+ debug("_respondToQuery Could not find service (key=" + serviceKey + ")");
+ return;
+ }
+
+ DEBUG &&
+ debug("_respondToQuery(): key=" + serviceKey + ": SENDING RESPONSE");
+ this._advertiseServiceHelper(publishedService, { address, port });
+ }
+
+ _advertiseService(serviceKey, firstAdv) {
+ DEBUG && debug("_advertiseService(): key=" + serviceKey);
+ let publishedService = this._services.get(serviceKey);
+ if (!publishedService) {
+ debug(
+ "_advertiseService Could not find service to advertise (key=" +
+ serviceKey +
+ ")"
+ );
+ return;
+ }
+
+ publishedService.advertiseTimer = undefined;
+
+ this._advertiseServiceHelper(publishedService, null).then(() => {
+ // If first advertisement, re-advertise in 1 second.
+ // Otherwise, set the lastAdvertised time.
+ if (firstAdv) {
+ publishedService.advertiseTimer = setTimeout(() => {
+ this._advertiseService(serviceKey);
+ }, 1000);
+ } else {
+ publishedService.lastAdvertised = Date.now();
+ this._checkStartBroadcastTimer();
+ }
+ });
+ }
+
+ _advertiseServiceHelper(svc, target) {
+ if (!target) {
+ target = { address: MDNS_MULTICAST_GROUP, port: MDNS_PORT };
+ }
+
+ return this._getSockets().then(sockets => {
+ sockets.forEach((socket, address) => {
+ if (svc.address == "0.0.0.0" || address == svc.address) {
+ let packet = this._makeServicePacket(svc, [address]);
+ let data = packet.serialize();
+ try {
+ socket.send(target.address, target.port, data);
+ } catch (err) {
+ DEBUG &&
+ debug(
+ "Failed to send packet to " + target.address + ":" + target.port
+ );
+ }
+ }
+ });
+ });
+ }
+
+ _cancelBroadcastTimer() {
+ if (!this._broadcastTimer) {
+ return;
+ }
+ clearTimeout(this._broadcastTimer);
+ this._broadcastTimer = undefined;
+ }
+
+ _checkStartBroadcastTimer() {
+ DEBUG && debug("_checkStartBroadcastTimer()");
+ // Cancel any existing broadcasting timer.
+ this._cancelBroadcastTimer();
+
+ let now = Date.now();
+
+ // Go through services and find services to broadcast.
+ let bcastServices = [];
+ let nextBcastWait = undefined;
+ for (let [, publishedService] of this._services) {
+ // if lastAdvertised is undefined, service hasn't finished it's initial
+ // two broadcasts.
+ if (publishedService.lastAdvertised === undefined) {
+ continue;
+ }
+
+ // Otherwise, check lastAdvertised against now.
+ let msSinceAdv = now - publishedService.lastAdvertised;
+
+ // If msSinceAdv is more than 90% of the way to the TTL, advertise now.
+ if (msSinceAdv > DEFAULT_TTL * 1000 * 0.9) {
+ bcastServices.push(publishedService);
+ continue;
+ }
+
+ // Otherwise, calculate the next time to advertise for this service.
+ // We set that at 95% of the time to the TTL expiry.
+ let nextAdvWait = DEFAULT_TTL * 1000 * 0.95 - msSinceAdv;
+ if (nextBcastWait === undefined || nextBcastWait > nextAdvWait) {
+ nextBcastWait = nextAdvWait;
+ }
+ }
+
+ // Schedule an immediate advertisement of all services to be advertised now.
+ for (let svc of bcastServices) {
+ svc.advertiseTimer = setTimeout(() => this._advertiseService(svc.key));
+ }
+
+ // Schedule next broadcast check for the next bcast time.
+ if (nextBcastWait !== undefined) {
+ DEBUG &&
+ debug(
+ "_checkStartBroadcastTimer(): Scheduling next check in " +
+ nextBcastWait +
+ "ms"
+ );
+ this._broadcastTimer = setTimeout(
+ () => this._checkStartBroadcastTimer(),
+ nextBcastWait
+ );
+ }
+ }
+
+ _query(name) {
+ DEBUG && debug('query("' + name + '")');
+ let packet = new DNSPacket();
+ packet.setFlag("QR", DNS_QUERY_RESPONSE_CODES.QUERY);
+
+ // PTR Record
+ packet.addRecord(
+ "QD",
+ new DNSRecord({
+ name,
+ recordType: DNS_RECORD_TYPES.PTR,
+ classCode: DNS_CLASS_CODES.IN,
+ cacheFlush: true,
+ })
+ );
+
+ let data = packet.serialize();
+
+ // Initialize the broadcast receiver socket in case it
+ // hasn't already been started so we can listen for
+ // multicast queries/announcements on all interfaces.
+ this._getBroadcastReceiverSocket();
+
+ this._getQuerySocket().then(querySocket => {
+ DEBUG && debug('sending query on query socket ("' + name + '")');
+ querySocket.send(MDNS_MULTICAST_GROUP, MDNS_PORT, data);
+ });
+
+ // Automatically announce previously-discovered
+ // services that match and haven't expired yet.
+ setTimeout(() => {
+ DEBUG &&
+ debug('announcing previously discovered services ("' + name + '")');
+ let { serviceType } = _parseServiceDomainName(name);
+
+ this._clearExpiredDiscoveries();
+ this._discovered.forEach((discovery, key) => {
+ let serviceInfo = discovery.serviceInfo;
+ if (serviceInfo.serviceType !== serviceType) {
+ return;
+ }
+
+ let listeners = this._listeners.get(serviceInfo.serviceType) || [];
+ listeners.forEach(listener => {
+ listener.onServiceFound(serviceInfo);
+ });
+ });
+ });
+ }
+
+ _clearExpiredDiscoveries() {
+ this._discovered.forEach((discovery, key) => {
+ if (discovery.expireTime < Date.now()) {
+ this._discovered.delete(key);
+ }
+ });
+ }
+
+ _handleQueryPacket(packet, message) {
+ packet.getRecords(["QD"]).forEach(record => {
+ // Don't respond if the query's class code is not IN or ANY.
+ if (
+ record.classCode !== DNS_CLASS_CODES.IN &&
+ record.classCode !== DNS_CLASS_CODES.ANY
+ ) {
+ return;
+ }
+
+ // Don't respond if the query's record type is not PTR or ANY.
+ if (
+ record.recordType !== DNS_RECORD_TYPES.PTR &&
+ record.recordType !== DNS_RECORD_TYPES.ANY
+ ) {
+ return;
+ }
+
+ for (let [serviceKey, publishedService] of this._services) {
+ DEBUG && debug("_handleQueryPacket: " + packet.toJSON());
+ if (publishedService.ptrMatch(record.name)) {
+ this._respondToQuery(serviceKey, message);
+ }
+ }
+ });
+ }
+
+ _makeServicePacket(service, addresses) {
+ let packet = new DNSPacket();
+ packet.setFlag("QR", DNS_QUERY_RESPONSE_CODES.RESPONSE);
+ packet.setFlag("AA", DNS_AUTHORITATIVE_ANSWER_CODES.YES);
+
+ let host = service.host || _hostname;
+
+ // e.g.: foo-bar-service._http._tcp.local
+ let serviceDomainName =
+ service.serviceName + "." + service.serviceType + ".local";
+
+ // PTR Record
+ packet.addRecord(
+ "AN",
+ new DNSResourceRecord({
+ name: service.serviceType + ".local", // e.g.: _http._tcp.local
+ recordType: DNS_RECORD_TYPES.PTR,
+ data: serviceDomainName,
+ })
+ );
+
+ // SRV Record
+ packet.addRecord(
+ "AR",
+ new DNSResourceRecord({
+ name: serviceDomainName,
+ recordType: DNS_RECORD_TYPES.SRV,
+ classCode: DNS_CLASS_CODES.IN,
+ cacheFlush: true,
+ data: {
+ priority: 0,
+ weight: 0,
+ port: service.port,
+ target: host, // e.g.: My-Android-Phone.local
+ },
+ })
+ );
+
+ // A Records
+ for (let address of addresses) {
+ packet.addRecord(
+ "AR",
+ new DNSResourceRecord({
+ name: host,
+ recordType: DNS_RECORD_TYPES.A,
+ data: address,
+ })
+ );
+ }
+
+ // TXT Record
+ packet.addRecord(
+ "AR",
+ new DNSResourceRecord({
+ name: serviceDomainName,
+ recordType: DNS_RECORD_TYPES.TXT,
+ classCode: DNS_CLASS_CODES.IN,
+ cacheFlush: true,
+ data: service.serviceAttrs || {},
+ })
+ );
+
+ return packet;
+ }
+
+ _handleResponsePacket(packet, message) {
+ let services = {};
+ let hosts = {};
+
+ let srvRecords = packet.getRecords(["AN", "AR"], DNS_RECORD_TYPES.SRV);
+ let txtRecords = packet.getRecords(["AN", "AR"], DNS_RECORD_TYPES.TXT);
+ let ptrRecords = packet.getRecords(["AN", "AR"], DNS_RECORD_TYPES.PTR);
+ let aRecords = packet.getRecords(["AN", "AR"], DNS_RECORD_TYPES.A);
+
+ srvRecords.forEach(record => {
+ let data = record.data || {};
+
+ services[record.name] = {
+ host: data.target,
+ port: data.port,
+ ttl: record.ttl,
+ };
+ });
+
+ txtRecords.forEach(record => {
+ if (!services[record.name]) {
+ return;
+ }
+
+ services[record.name].attributes = record.data;
+ });
+
+ aRecords.forEach(record => {
+ if (IsIpv4Address(record.data)) {
+ hosts[record.name] = record.data;
+ }
+ });
+
+ ptrRecords.forEach(record => {
+ let name = record.data;
+ if (!services[name]) {
+ return;
+ }
+
+ let { host, port } = services[name];
+ if (!host || !port) {
+ return;
+ }
+
+ let { serviceName, serviceType, domainName } = _parseServiceDomainName(
+ name
+ );
+ if (!serviceName || !serviceType || !domainName) {
+ return;
+ }
+
+ let address = hosts[host];
+ if (!address) {
+ return;
+ }
+
+ let ttl = services[name].ttl || 0;
+ let serviceInfo = {
+ serviceName,
+ serviceType,
+ host,
+ address,
+ port,
+ domainName,
+ attributes: services[name].attributes || {},
+ };
+
+ this._onServiceFound(serviceInfo, ttl);
+ });
+ }
+
+ _onServiceFound(serviceInfo, ttl = 0) {
+ let expireTime = Date.now() + ttl * 1000;
+ let key =
+ serviceInfo.serviceName +
+ "." +
+ serviceInfo.serviceType +
+ "." +
+ serviceInfo.domainName +
+ " @" +
+ serviceInfo.address +
+ ":" +
+ serviceInfo.port;
+
+ // If this service was already discovered, just update
+ // its expiration time and don't re-emit it.
+ if (this._discovered.has(key)) {
+ this._discovered.get(key).expireTime = expireTime;
+ return;
+ }
+
+ this._discovered.set(key, {
+ serviceInfo,
+ expireTime,
+ });
+
+ let listeners = this._listeners.get(serviceInfo.serviceType) || [];
+ listeners.forEach(listener => {
+ listener.onServiceFound(serviceInfo);
+ });
+
+ DEBUG && debug("_onServiceFound()" + serviceInfo.serviceName);
+ }
+
+ /**
+ * Gets a non-exclusive socket on 0.0.0.0:{random} to send
+ * multicast queries on all interfaces. This socket does
+ * not need to join a multicast group since it is still
+ * able to *send* multicast queries, but it does not need
+ * to *listen* for multicast queries/announcements since
+ * the `_broadcastReceiverSocket` is already handling them.
+ */
+ _getQuerySocket() {
+ return new Promise((resolve, reject) => {
+ if (!this._querySocket) {
+ this._querySocket = _openSocket("0.0.0.0", 0, {
+ onPacketReceived: this._onPacketReceived.bind(this),
+ onStopListening: this._onStopListening.bind(this),
+ });
+ }
+ resolve(this._querySocket);
+ });
+ }
+
+ /**
+ * Gets a non-exclusive socket on 0.0.0.0:5353 to listen
+ * for multicast queries/announcements on all interfaces.
+ * Since this socket needs to listen for multicast queries
+ * and announcements, this socket joins the multicast
+ * group on *all* interfaces (0.0.0.0).
+ */
+ _getBroadcastReceiverSocket() {
+ return new Promise((resolve, reject) => {
+ if (!this._broadcastReceiverSocket) {
+ this._broadcastReceiverSocket = _openSocket(
+ "0.0.0.0",
+ MDNS_PORT,
+ {
+ onPacketReceived: this._onPacketReceived.bind(this),
+ onStopListening: this._onStopListening.bind(this),
+ },
+ /* multicastInterface = */ "0.0.0.0"
+ );
+ }
+ resolve(this._broadcastReceiverSocket);
+ });
+ }
+
+ /**
+ * Gets a non-exclusive socket for each interface on
+ * {iface-ip}:5353 for sending query responses as
+ * well as for listening for unicast queries. These
+ * sockets do not need to join a multicast group
+ * since they are still able to *send* multicast
+ * query responses, but they do not need to *listen*
+ * for multicast queries since the `_querySocket` is
+ * already handling them.
+ */
+ _getSockets() {
+ return new Promise(resolve => {
+ if (this._sockets.size > 0) {
+ resolve(this._sockets);
+ return;
+ }
+
+ Promise.all([getAddresses(), getHostname()]).then(() => {
+ _addresses.forEach(address => {
+ let socket = _openSocket(address, MDNS_PORT, null);
+ this._sockets.set(address, socket);
+ });
+
+ resolve(this._sockets);
+ });
+ });
+ }
+
+ _checkCloseSockets() {
+ // Nothing to do if no sockets to close.
+ if (this._sockets.size == 0) {
+ return;
+ }
+
+ // Don't close sockets if discovery listeners are still present.
+ if (this._listeners.size > 0) {
+ return;
+ }
+
+ // Don't close sockets if advertised services are present.
+ // Since we need to listen for service queries and respond to them.
+ if (this._services.size > 0) {
+ return;
+ }
+
+ this._closeSockets();
+ }
+
+ _closeSockets() {
+ this._sockets.forEach(socket => socket.close());
+ this._sockets.clear();
+ }
+
+ _onPacketReceived(socket, message) {
+ let packet = DNSPacket.parse(message.rawData);
+
+ switch (packet.getFlag("QR")) {
+ case DNS_QUERY_RESPONSE_CODES.QUERY:
+ this._handleQueryPacket(packet, message);
+ break;
+ case DNS_QUERY_RESPONSE_CODES.RESPONSE:
+ this._handleResponsePacket(packet, message);
+ break;
+ default:
+ break;
+ }
+ }
+
+ _onStopListening(socket, status) {
+ DEBUG && debug("_onStopListening() " + status);
+ }
+
+ _addServiceListener(serviceType, listener) {
+ let listeners = this._listeners.get(serviceType);
+ if (!listeners) {
+ listeners = [];
+ this._listeners.set(serviceType, listeners);
+ }
+
+ if (!listeners.find(l => l === listener)) {
+ listeners.push(listener);
+ }
+ }
+
+ _removeServiceListener(serviceType, listener) {
+ let listeners = this._listeners.get(serviceType);
+ if (!listeners) {
+ return;
+ }
+
+ let index = listeners.findIndex(l => l === listener);
+ if (index >= 0) {
+ listeners.splice(index, 1);
+ }
+
+ if (listeners.length === 0) {
+ this._listeners.delete(serviceType);
+ }
+ }
+}
+
+let _addresses;
+
+/**
+ * @private
+ */
+function getAddresses() {
+ return new Promise((resolve, reject) => {
+ if (_addresses) {
+ resolve(_addresses);
+ return;
+ }
+
+ networkInfoService.listNetworkAddresses({
+ onListedNetworkAddresses(aAddressArray) {
+ _addresses = aAddressArray.filter(address => {
+ return (
+ !address.includes("%p2p") && // No WiFi Direct interfaces
+ !address.includes(":") && // XXX: No IPv6 for now
+ address != "127.0.0.1"
+ ); // No ipv4 loopback addresses.
+ });
+
+ DEBUG && debug("getAddresses(): " + _addresses);
+ resolve(_addresses);
+ },
+
+ onListNetworkAddressesFailed() {
+ DEBUG && debug("getAddresses() FAILED!");
+ resolve([]);
+ },
+ });
+ });
+}
+
+let _hostname;
+
+/**
+ * @private
+ */
+function getHostname() {
+ return new Promise(resolve => {
+ if (_hostname) {
+ resolve(_hostname);
+ return;
+ }
+
+ networkInfoService.getHostname({
+ onGotHostname(aHostname) {
+ _hostname = aHostname.replace(/\s/g, "-") + ".local";
+
+ DEBUG && debug("getHostname(): " + _hostname);
+ resolve(_hostname);
+ },
+
+ onGetHostnameFailed() {
+ DEBUG && debug("getHostname() FAILED");
+ resolve("localhost");
+ },
+ });
+ });
+}
+
+/**
+ * Parse fully qualified domain name to service name, instance name,
+ * and domain name. See https://tools.ietf.org/html/rfc6763#section-7.
+ *
+ * Example: 'foo-bar-service._http._tcp.local' -> {
+ * serviceName: 'foo-bar-service',
+ * serviceType: '_http._tcp',
+ * domainName: 'local'
+ * }
+ *
+ * @private
+ */
+function _parseServiceDomainName(serviceDomainName) {
+ let parts = serviceDomainName.split(".");
+ let index = Math.max(parts.lastIndexOf("_tcp"), parts.lastIndexOf("_udp"));
+
+ return {
+ serviceName: parts.splice(0, index - 1).join("."),
+ serviceType: parts.splice(0, 2).join("."),
+ domainName: parts.join("."),
+ };
+}
+
+/**
+ * @private
+ */
+function _propertyBagToObject(propBag) {
+ let result = {};
+ if (propBag.QueryInterface) {
+ propBag.QueryInterface(Ci.nsIPropertyBag2);
+ for (let prop of propBag.enumerator) {
+ result[prop.name] = prop.value.toString();
+ }
+ } else {
+ for (let name in propBag) {
+ result[name] = propBag[name].toString();
+ }
+ }
+ return result;
+}
+
+/**
+ * @private
+ */
+function _openSocket(addr, port, handler, multicastInterface) {
+ let socket = Cc["@mozilla.org/network/udp-socket;1"].createInstance(
+ Ci.nsIUDPSocket
+ );
+ socket.init2(
+ addr,
+ port,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ true
+ );
+
+ if (handler) {
+ socket.asyncListen({
+ onPacketReceived: handler.onPacketReceived,
+ onStopListening: handler.onStopListening,
+ });
+ }
+
+ if (multicastInterface) {
+ socket.joinMulticast(MDNS_MULTICAST_GROUP, multicastInterface);
+ }
+
+ return socket;
+}
diff --git a/netwerk/dns/mdns/libmdns/moz.build b/netwerk/dns/mdns/libmdns/moz.build
new file mode 100644
index 0000000000..f9c025fa82
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/moz.build
@@ -0,0 +1,51 @@
+# -*- 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["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ UNIFIED_SOURCES += [
+ "MDNSResponderOperator.cpp",
+ "MDNSResponderReply.cpp",
+ "nsDNSServiceDiscovery.cpp",
+ ]
+
+ LOCAL_INCLUDES += [
+ "/netwerk/base",
+ ]
+
+else:
+ EXTRA_JS_MODULES += [
+ "DNSServiceDiscovery.jsm",
+ "fallback/DataReader.jsm",
+ "fallback/DataWriter.jsm",
+ "fallback/DNSPacket.jsm",
+ "fallback/DNSRecord.jsm",
+ "fallback/DNSResourceRecord.jsm",
+ "fallback/DNSTypes.jsm",
+ "fallback/MulticastDNS.jsm",
+ ]
+
+ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ EXTRA_JS_MODULES += [
+ "MulticastDNSAndroid.jsm",
+ ]
+
+UNIFIED_SOURCES += [
+ "nsDNSServiceInfo.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+FINAL_LIBRARY = "xul"
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp
new file mode 100644
index 0000000000..12a23e56a4
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp
@@ -0,0 +1,228 @@
+/* -*- 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 "nsDNSServiceDiscovery.h"
+#include "MDNSResponderOperator.h"
+#include "nsICancelable.h"
+#include "nsXULAppAPI.h"
+#include "private/pprio.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+inline void StartService() {}
+
+inline void StopService() {}
+
+class ServiceCounter {
+ public:
+ static bool IsServiceRunning() { return !!sUseCount; }
+
+ private:
+ static uint32_t sUseCount;
+
+ protected:
+ ServiceCounter() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!sUseCount++) {
+ StartService();
+ }
+ }
+
+ virtual ~ServiceCounter() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!--sUseCount) {
+ StopService();
+ }
+ }
+};
+
+uint32_t ServiceCounter::sUseCount = 0;
+
+class DiscoveryRequest final : public nsICancelable, private ServiceCounter {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ explicit DiscoveryRequest(nsDNSServiceDiscovery* aService,
+ nsIDNSServiceDiscoveryListener* aListener);
+
+ private:
+ virtual ~DiscoveryRequest() { Cancel(NS_OK); }
+
+ RefPtr<nsDNSServiceDiscovery> mService;
+ nsIDNSServiceDiscoveryListener* mListener;
+};
+
+NS_IMPL_ISUPPORTS(DiscoveryRequest, nsICancelable)
+
+DiscoveryRequest::DiscoveryRequest(nsDNSServiceDiscovery* aService,
+ nsIDNSServiceDiscoveryListener* aListener)
+ : mService(aService), mListener(aListener) {}
+
+NS_IMETHODIMP
+DiscoveryRequest::Cancel(nsresult aReason) {
+ if (mService) {
+ mService->StopDiscovery(mListener);
+ }
+
+ mService = nullptr;
+ return NS_OK;
+}
+
+class RegisterRequest final : public nsICancelable, private ServiceCounter {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ explicit RegisterRequest(nsDNSServiceDiscovery* aService,
+ nsIDNSRegistrationListener* aListener);
+
+ private:
+ virtual ~RegisterRequest() { Cancel(NS_OK); }
+
+ RefPtr<nsDNSServiceDiscovery> mService;
+ nsIDNSRegistrationListener* mListener;
+};
+
+NS_IMPL_ISUPPORTS(RegisterRequest, nsICancelable)
+
+RegisterRequest::RegisterRequest(nsDNSServiceDiscovery* aService,
+ nsIDNSRegistrationListener* aListener)
+ : mService(aService), mListener(aListener) {}
+
+NS_IMETHODIMP
+RegisterRequest::Cancel(nsresult aReason) {
+ if (mService) {
+ mService->UnregisterService(mListener);
+ }
+
+ mService = nullptr;
+ return NS_OK;
+}
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(nsDNSServiceDiscovery, nsIDNSServiceDiscovery)
+
+nsresult nsDNSServiceDiscovery::Init() {
+ if (!XRE_IsParentProcess()) {
+ MOZ_ASSERT(false,
+ "nsDNSServiceDiscovery can only be used in parent process");
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::StartDiscovery(const nsACString& aServiceType,
+ nsIDNSServiceDiscoveryListener* aListener,
+ nsICancelable** aRetVal) {
+ MOZ_ASSERT(aRetVal);
+
+ nsresult rv;
+ if (NS_WARN_IF(NS_FAILED(rv = StopDiscovery(aListener)))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsICancelable> req = new DiscoveryRequest(this, aListener);
+ RefPtr<BrowseOperator> browserOp =
+ new BrowseOperator(aServiceType, aListener);
+ if (NS_WARN_IF(NS_FAILED(rv = browserOp->Start()))) {
+ return rv;
+ }
+
+ mDiscoveryMap.Put(aListener, std::move(browserOp));
+
+ req.forget(aRetVal);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::StopDiscovery(
+ nsIDNSServiceDiscoveryListener* aListener) {
+ nsresult rv;
+
+ RefPtr<BrowseOperator> browserOp;
+ if (!mDiscoveryMap.Get(aListener, getter_AddRefs(browserOp))) {
+ return NS_OK;
+ }
+
+ browserOp->Cancel(); // cancel non-started operation
+ if (NS_WARN_IF(NS_FAILED(rv = browserOp->Stop()))) {
+ return rv;
+ }
+
+ mDiscoveryMap.Remove(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::RegisterService(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSRegistrationListener* aListener,
+ nsICancelable** aRetVal) {
+ MOZ_ASSERT(aRetVal);
+
+ nsresult rv;
+ if (NS_WARN_IF(NS_FAILED(rv = UnregisterService(aListener)))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsICancelable> req = new RegisterRequest(this, aListener);
+ RefPtr<RegisterOperator> registerOp =
+ new RegisterOperator(aServiceInfo, aListener);
+ if (NS_WARN_IF(NS_FAILED(rv = registerOp->Start()))) {
+ return rv;
+ }
+
+ mRegisterMap.Put(aListener, std::move(registerOp));
+
+ req.forget(aRetVal);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::UnregisterService(
+ nsIDNSRegistrationListener* aListener) {
+ nsresult rv;
+
+ RefPtr<RegisterOperator> registerOp;
+ if (!mRegisterMap.Get(aListener, getter_AddRefs(registerOp))) {
+ return NS_OK;
+ }
+
+ registerOp->Cancel(); // cancel non-started operation
+ if (NS_WARN_IF(NS_FAILED(rv = registerOp->Stop()))) {
+ return rv;
+ }
+
+ mRegisterMap.Remove(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::ResolveService(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSServiceResolveListener* aListener) {
+ if (!ServiceCounter::IsServiceRunning()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+ RefPtr<ResolveOperator> resolveOp =
+ new ResolveOperator(aServiceInfo, aListener);
+ if (NS_WARN_IF(NS_FAILED(rv = resolveOp->Start()))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h
new file mode 100644
index 0000000000..0acc9198bd
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h
@@ -0,0 +1,47 @@
+/* -*- 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_dns_mdns_libmdns_nsDNSServiceDiscovery_h
+#define mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceDiscovery_h
+
+#include "nsIDNSServiceDiscovery.h"
+#include "nsCOMPtr.h"
+#include "mozilla/RefPtr.h"
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla {
+namespace net {
+
+class BrowseOperator;
+class RegisterOperator;
+
+class nsDNSServiceDiscovery final : public nsIDNSServiceDiscovery {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSSERVICEDISCOVERY
+
+ explicit nsDNSServiceDiscovery() = default;
+
+ /*
+ ** The mDNS service is started on demand. If no one uses, mDNS service will
+ ** not start. Therefore, all operations before service started will fail
+ ** and get error code |kDNSServiceErr_ServiceNotRunning| defined in dns_sd.h.
+ **/
+ nsresult Init();
+
+ nsresult StopDiscovery(nsIDNSServiceDiscoveryListener* aListener);
+ nsresult UnregisterService(nsIDNSRegistrationListener* aListener);
+
+ private:
+ virtual ~nsDNSServiceDiscovery() = default;
+
+ nsRefPtrHashtable<nsISupportsHashKey, BrowseOperator> mDiscoveryMap;
+ nsRefPtrHashtable<nsISupportsHashKey, RegisterOperator> mRegisterMap;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceDiscovery_h
diff --git a/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp b/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp
new file mode 100644
index 0000000000..a6825a8d31
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp
@@ -0,0 +1,170 @@
+/* -*- 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 "nsDNSServiceInfo.h"
+#include "nsHashPropertyBag.h"
+#include "nsIProperty.h"
+#include "nsISimpleEnumerator.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsDNSServiceInfo, nsIDNSServiceInfo)
+
+nsDNSServiceInfo::nsDNSServiceInfo(nsIDNSServiceInfo* aServiceInfo) {
+ if (NS_WARN_IF(!aServiceInfo)) {
+ return;
+ }
+
+ nsAutoCString str;
+ uint16_t value;
+
+ if (NS_SUCCEEDED(aServiceInfo->GetHost(str))) {
+ Unused << NS_WARN_IF(NS_FAILED(SetHost(str)));
+ }
+ if (NS_SUCCEEDED(aServiceInfo->GetAddress(str))) {
+ Unused << NS_WARN_IF(NS_FAILED(SetAddress(str)));
+ }
+ if (NS_SUCCEEDED(aServiceInfo->GetPort(&value))) {
+ Unused << NS_WARN_IF(NS_FAILED(SetPort(value)));
+ }
+ if (NS_SUCCEEDED(aServiceInfo->GetServiceName(str))) {
+ Unused << NS_WARN_IF(NS_FAILED(SetServiceName(str)));
+ }
+ if (NS_SUCCEEDED(aServiceInfo->GetServiceType(str))) {
+ Unused << NS_WARN_IF(NS_FAILED(SetServiceType(str)));
+ }
+ if (NS_SUCCEEDED(aServiceInfo->GetDomainName(str))) {
+ Unused << NS_WARN_IF(NS_FAILED(SetDomainName(str)));
+ }
+
+ nsCOMPtr<nsIPropertyBag2> attributes; // deep copy
+ if (NS_SUCCEEDED(aServiceInfo->GetAttributes(getter_AddRefs(attributes)))) {
+ RefPtr<nsHashPropertyBag> newAttributes = new nsHashPropertyBag();
+ newAttributes->CopyFrom(attributes);
+ Unused << NS_WARN_IF(NS_FAILED(SetAttributes(newAttributes)));
+ }
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::GetHost(nsACString& aHost) {
+ if (!mIsHostSet) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ aHost = mHost;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::SetHost(const nsACString& aHost) {
+ mHost = aHost;
+ mIsHostSet = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::GetAddress(nsACString& aAddress) {
+ if (!mIsAddressSet) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ aAddress = mAddress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::SetAddress(const nsACString& aAddress) {
+ mAddress = aAddress;
+ mIsAddressSet = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::GetPort(uint16_t* aPort) {
+ if (NS_WARN_IF(!aPort)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (!mIsPortSet) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ *aPort = mPort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::SetPort(uint16_t aPort) {
+ mPort = aPort;
+ mIsPortSet = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::GetServiceName(nsACString& aServiceName) {
+ if (!mIsServiceNameSet) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ aServiceName = mServiceName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::SetServiceName(const nsACString& aServiceName) {
+ mServiceName = aServiceName;
+ mIsServiceNameSet = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::GetServiceType(nsACString& aServiceType) {
+ if (!mIsServiceTypeSet) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ aServiceType = mServiceType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::SetServiceType(const nsACString& aServiceType) {
+ mServiceType = aServiceType;
+ mIsServiceTypeSet = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::GetDomainName(nsACString& aDomainName) {
+ if (!mIsDomainNameSet) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ aDomainName = mDomainName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::SetDomainName(const nsACString& aDomainName) {
+ mDomainName = aDomainName;
+ mIsDomainNameSet = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::GetAttributes(nsIPropertyBag2** aAttributes) {
+ if (!mIsAttributesSet) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ nsCOMPtr<nsIPropertyBag2> attributes(mAttributes);
+ attributes.forget(aAttributes);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::SetAttributes(nsIPropertyBag2* aAttributes) {
+ mAttributes = aAttributes;
+ mIsAttributesSet = aAttributes ? true : false;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.h b/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.h
new file mode 100644
index 0000000000..31fffcc74e
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.h
@@ -0,0 +1,49 @@
+/* -*- 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_dns_mdns_libmdns_nsDNSServiceInfo_h
+#define mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceInfo_h
+
+#include "nsCOMPtr.h"
+#include "nsIDNSServiceDiscovery.h"
+#include "nsIPropertyBag2.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+class nsDNSServiceInfo final : public nsIDNSServiceInfo {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDNSSERVICEINFO
+
+ explicit nsDNSServiceInfo() = default;
+ explicit nsDNSServiceInfo(nsIDNSServiceInfo* aServiceInfo);
+
+ private:
+ virtual ~nsDNSServiceInfo() = default;
+
+ private:
+ nsCString mHost;
+ nsCString mAddress;
+ uint16_t mPort = 0;
+ nsCString mServiceName;
+ nsCString mServiceType;
+ nsCString mDomainName;
+ nsCOMPtr<nsIPropertyBag2> mAttributes;
+
+ bool mIsHostSet = false;
+ bool mIsAddressSet = false;
+ bool mIsPortSet = false;
+ bool mIsServiceNameSet = false;
+ bool mIsServiceTypeSet = false;
+ bool mIsDomainNameSet = false;
+ bool mIsAttributesSet = false;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceInfo_h
diff --git a/netwerk/dns/mdns/moz.build b/netwerk/dns/mdns/moz.build
new file mode 100644
index 0000000000..561031af96
--- /dev/null
+++ b/netwerk/dns/mdns/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/.
+
+DIRS += ["libmdns"]
+
+XPIDL_SOURCES += [
+ "nsIDNSServiceDiscovery.idl",
+]
+
+XPIDL_MODULE = "necko_mdns"
diff --git a/netwerk/dns/mdns/nsIDNSServiceDiscovery.idl b/netwerk/dns/mdns/nsIDNSServiceDiscovery.idl
new file mode 100644
index 0000000000..23c678eccc
--- /dev/null
+++ b/netwerk/dns/mdns/nsIDNSServiceDiscovery.idl
@@ -0,0 +1,219 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 nsIPropertyBag2;
+
+/**
+ * Service information
+ */
+[scriptable, uuid(670ed0f9-2fa5-4544-bf1e-ea58ac179374)]
+interface nsIDNSServiceInfo : nsISupports
+{
+ /**
+ * The host name of the service. (E.g. "Android.local.")
+ * @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
+ */
+ attribute AUTF8String host;
+
+ /**
+ * The IP address of the service.
+ * @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
+ */
+ attribute AUTF8String address;
+
+ /**
+ * The port number of the service. (E.g. 80)
+ * @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
+ */
+ attribute unsigned short port;
+
+ /**
+ * The service name of the service for display. (E.g. "My TV")
+ * @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
+ */
+ attribute AUTF8String serviceName;
+
+ /**
+ * The type of the service. (E.g. "_http._tcp")
+ * @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
+ */
+ attribute AUTF8String serviceType;
+
+ /**
+ * The domain name of the service. (E.g. "local.")
+ * @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
+ */
+ attribute AUTF8String domainName;
+
+ /**
+ * The attributes of the service.
+ */
+ attribute nsIPropertyBag2 attributes;
+};
+
+/**
+ * The callback interface for service discovery
+ */
+[scriptable, uuid(3025b7f2-97bb-435b-b43d-26731b3f5fc4)]
+interface nsIDNSServiceDiscoveryListener : nsISupports
+{
+ /**
+ * Callback when the discovery begins.
+ * @param aServiceType
+ * the service type of |startDiscovery|.
+ */
+ void onDiscoveryStarted(in AUTF8String aServiceType);
+
+ /**
+ * Callback when the discovery ends.
+ * @param aServiceType
+ * the service type of |startDiscovery|.
+ */
+ void onDiscoveryStopped(in AUTF8String aServiceType);
+
+ /**
+ * Callback when the a service is found.
+ * @param aServiceInfo
+ * the info about the found service, where |serviceName|, |aServiceType|, and |domainName| are set.
+ */
+ void onServiceFound(in nsIDNSServiceInfo aServiceInfo);
+
+ /**
+ * Callback when the a service is lost.
+ * @param aServiceInfo
+ * the info about the lost service, where |serviceName|, |aServiceType|, and |domainName| are set.
+ */
+ void onServiceLost(in nsIDNSServiceInfo aServiceInfo);
+
+ /**
+ * Callback when the discovery cannot start.
+ * @param aServiceType
+ * the service type of |startDiscovery|.
+ * @param aErrorCode
+ * the error code.
+ */
+ void onStartDiscoveryFailed(in AUTF8String aServiceType, in long aErrorCode);
+
+ /**
+ * Callback when the discovery cannot stop.
+ * @param aServiceType
+ * the service type of |startDiscovery|.
+ * @param aErrorCode
+ * the error code.
+ */
+ void onStopDiscoveryFailed(in AUTF8String aServiceType, in long aErrorCode);
+};
+
+/**
+ * The callback interface for service registration
+ */
+[scriptable, uuid(e165e4be-abf4-4963-a66d-ed3ca116e5e4)]
+interface nsIDNSRegistrationListener : nsISupports
+{
+ const long ERROR_SERVICE_NOT_RUNNING = -65563;
+
+ /**
+ * Callback when the service is registered successfully.
+ * @param aServiceInfo
+ * the info about the registered service,
+ * where |serviceName|, |aServiceType|, and |domainName| are set.
+ */
+ void onServiceRegistered(in nsIDNSServiceInfo aServiceInfo);
+
+ /**
+ * Callback when the service is unregistered successfully.
+ * @param aServiceInfo
+ * the info about the unregistered service.
+ */
+ void onServiceUnregistered(in nsIDNSServiceInfo aServiceInfo);
+
+ /**
+ * Callback when the service cannot be registered.
+ * @param aServiceInfo
+ * the info about the service to be registered.
+ * @param aErrorCode
+ * the error code.
+ */
+ void onRegistrationFailed(in nsIDNSServiceInfo aServiceInfo, in long aErrorCode);
+
+ /**
+ * Callback when the service cannot be unregistered.
+ * @param aServiceInfo
+ * the info about the registered service.
+ * @param aErrorCode
+ * the error code.
+ */
+ void onUnregistrationFailed(in nsIDNSServiceInfo aServiceInfo, in long aErrorCode);
+};
+
+/**
+ * The callback interface for service resolve
+ */
+[scriptable, uuid(24ee6408-648e-421d-accf-c6e5adeccf97)]
+interface nsIDNSServiceResolveListener : nsISupports
+{
+ /**
+ * Callback when the service is resolved successfully.
+ * @param aServiceInfo
+ * the info about the resolved service, where |host| and |port| are set.
+ */
+ void onServiceResolved(in nsIDNSServiceInfo aServiceInfo);
+
+ /**
+ * Callback when the service cannot be resolved.
+ * @param aServiceInfo
+ * the info about the service to be resolved.
+ * @param aErrorCode
+ * the error code.
+ */
+ void onResolveFailed(in nsIDNSServiceInfo aServiceInfo, in long aErrorCode);
+};
+
+/**
+ * The interface for DNS service discovery/registration/resolve
+ */
+[scriptable, uuid(6487899b-beb1-455a-ba65-e4fd465066d7)]
+interface nsIDNSServiceDiscovery : nsISupports
+{
+ /**
+ * Browse for instances of a service.
+ * @param aServiceType
+ * the service type to be discovered, E.g. "_http._tcp".
+ * @param aListener
+ * callback interface for discovery notifications.
+ * @return An object that can be used to cancel the service discovery.
+ */
+ nsICancelable startDiscovery(in AUTF8String aServiceType, in nsIDNSServiceDiscoveryListener aListener);
+
+ /**
+ * Register a service that is discovered via |startDiscovery| and |resolveService| calls.
+ * @param aServiceInfo
+ * the service information to be registered.
+ * |port| and |aServiceType| are required attributes.
+ * @param aListener
+ * callback interface for registration notifications.
+ * @return An object that can be used to cancel the service registration.
+ */
+ nsICancelable registerService(in nsIDNSServiceInfo aServiceInfo, in nsIDNSRegistrationListener aListener);
+
+ /**
+ * Resolve a service name discovered via |startDiscovery| to a target host name, port number.
+ * @param aServiceInfo
+ * the service information to be registered.
+ * |serviceName|, |aServiceType|, and |domainName| are required attributes as reported to the |onServiceFound| callback.
+ * @param aListener
+ * callback interface for registration notifications.
+ */
+ void resolveService(in nsIDNSServiceInfo aServiceInfo, in nsIDNSServiceResolveListener aListener);
+};
+
+%{ C++
+#define DNSSERVICEDISCOVERY_CONTRACT_ID \
+ "@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1"
+#define DNSSERVICEINFO_CONTRACT_ID \
+ "@mozilla.org/toolkit/components/mdnsresponder/dns-info;1"
+%}
diff --git a/netwerk/dns/moz.build b/netwerk/dns/moz.build
new file mode 100644
index 0000000000..bc5ff1e58c
--- /dev/null
+++ b/netwerk/dns/moz.build
@@ -0,0 +1,107 @@
+# -*- 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 += ["mdns", "tests"]
+
+XPIDL_SOURCES += [
+ "nsIDNSByTypeRecord.idl",
+ "nsIDNSListener.idl",
+ "nsIDNSRecord.idl",
+ "nsIDNSResolverInfo.idl",
+ "nsIDNSService.idl",
+ "nsIEffectiveTLDService.idl",
+ "nsIIDNService.idl",
+ "nsINativeDNSResolverOverride.idl",
+ "nsPIDNSService.idl",
+]
+
+XPIDL_MODULE = "necko_dns"
+
+EXTRA_JS_MODULES["netwerk-dns"] += [
+ "PublicSuffixList.jsm",
+]
+
+XPCSHELL_TESTS_MANIFESTS += ["tests/unit/xpcshell.ini"]
+
+EXPORTS += [
+ "nsEffectiveTLDService.h",
+]
+
+EXPORTS.mozilla.net += [
+ "ChildDNSService.h",
+ "DNS.h",
+ "DNSByTypeRecord.h",
+ "DNSListenerProxy.h",
+ "DNSRequestBase.h",
+ "DNSRequestChild.h",
+ "DNSRequestParent.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.
+ "nsHostResolver.cpp", # Redefines LOG
+]
+
+UNIFIED_SOURCES += [
+ "ChildDNSService.cpp",
+ "DNS.cpp",
+ "DNSListenerProxy.cpp",
+ "DNSPacket.cpp",
+ "DNSRequestChild.cpp",
+ "DNSRequestParent.cpp",
+ "DNSResolverInfo.cpp",
+ "HTTPSSVC.cpp",
+ "IDNBlocklistUtils.cpp",
+ "NativeDNSResolverOverrideChild.cpp",
+ "NativeDNSResolverOverrideParent.cpp",
+ "nsDNSService2.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/protocol/http",
+]
+
+USE_LIBS += ["icu"]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/netwerk/dns/nsDNSService2.cpp b/netwerk/dns/nsDNSService2.cpp
new file mode 100644
index 0000000000..443c226116
--- /dev/null
+++ b/netwerk/dns/nsDNSService2.cpp
@@ -0,0 +1,1453 @@
+/* -*- 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 "nsNetCID.h"
+#include "nsError.h"
+#include "nsDNSPrefetch.h"
+#include "nsThreadUtils.h"
+#include "nsIProtocolProxyService.h"
+#include "prsystem.h"
+#include "prnetdb.h"
+#include "prmon.h"
+#include "prio.h"
+#include "plstr.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsNetAddr.h"
+#include "nsProxyRelease.h"
+#include "nsQueryObject.h"
+#include "nsIObserverService.h"
+#include "nsINetworkLinkService.h"
+#include "DNSResolverInfo.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 kPrefDisableIPv6[] = "network.dns.disableIPv6";
+static const char kPrefDisablePrefetch[] = "network.dns.disablePrefetch";
+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";
+static const char kPrefNetworkProxySOCKS[] = "network.proxy.socks";
+
+//-----------------------------------------------------------------------------
+
+class nsDNSRecord : public nsIDNSAddrRecord {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSRECORD
+ NS_DECL_NSIDNSADDRRECORD
+
+ explicit nsDNSRecord(nsHostRecord* hostRecord)
+ : mIterGenCnt(-1), mDone(false) {
+ 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; // the generation count of
+ // mHostRecord->addr_info when we
+ // start iterating
+ bool mDone;
+};
+
+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::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(uint32_t* aMode) {
+ *aMode = mHostRecord->EffectiveTRRMode();
+ return NS_OK;
+}
+
+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;
+}
+
+//-----------------------------------------------------------------------------
+
+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,
+ uint16_t 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;
+ const OriginAttributes
+ mOriginAttributes; // The originAttributes for this resolving
+ nsCOMPtr<nsIDNSListener> mListener;
+ uint16_t mFlags;
+ uint16_t mAF;
+
+ 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)) {
+ 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));
+ 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)
+ : mDone(false), mStatus(NS_OK), mMonitor(mon) {}
+
+ void OnResolveHostComplete(nsHostResolver*, nsHostRecord*, nsresult) override;
+ bool EqualsAsyncListener(nsIDNSListener* aListener) override;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const override;
+
+ bool mDone;
+ nsresult mStatus;
+ RefPtr<nsHostRecord> mHostRecord;
+
+ private:
+ virtual ~nsDNSSyncRequest() = default;
+
+ PRMonitor* mMonitor;
+};
+
+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;
+};
+
+//-----------------------------------------------------------------------------
+
+nsDNSService::nsDNSService()
+ : mLock("nsDNSServer.mLock"),
+ mDisableIPv6(false),
+ mDisablePrefetch(false),
+ mBlockDotOnion(false),
+ mNotifyResolution(false),
+ mOfflineLocalhost(false),
+ mForceResolveOn(false),
+ mTrrService(nullptr),
+ mHasSocksProxy(false),
+ mResCacheEntries(0),
+ mResCacheExpiration(0),
+ mResCacheGrace(0),
+ mResolverPrefsUpdated(false) {}
+
+nsDNSService::~nsDNSService() = default;
+
+NS_IMPL_ISUPPORTS(nsDNSService, nsIDNSService, nsPIDNSService, nsIObserver,
+ nsIMemoryReporter)
+
+/******************************************************************************
+ * nsDNSService impl:
+ * singleton instance ctor/dtor methods
+ ******************************************************************************/
+static StaticRefPtr<nsDNSService> gDNSService;
+
+already_AddRefed<nsIDNSService> nsDNSService::GetXPCOMSingleton() {
+ if (nsIOService::UseSocketProcess()) {
+ if (XRE_IsSocketProcess()) {
+ return GetSingleton();
+ }
+
+ if (XRE_IsContentProcess() || XRE_IsParentProcess()) {
+ return ChildDNSService::GetSingleton();
+ }
+
+ return nullptr;
+ }
+
+ if (XRE_IsParentProcess()) {
+ return GetSingleton();
+ }
+
+ if (XRE_IsContentProcess() || XRE_IsSocketProcess()) {
+ return ChildDNSService::GetSingleton();
+ }
+
+ return nullptr;
+}
+
+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);
+}
+
+nsresult nsDNSService::ReadPrefs(const char* 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, kPrefDisableIPv6)) {
+ if (NS_SUCCEEDED(Preferences::GetBool(kPrefDisableIPv6, &tmpbool))) {
+ mDisableIPv6 = tmpbool;
+ }
+ }
+ if (!name || !strcmp(name, kPrefDnsOfflineLocalhost)) {
+ if (NS_SUCCEEDED(
+ Preferences::GetBool(kPrefDnsOfflineLocalhost, &tmpbool))) {
+ mOfflineLocalhost = tmpbool;
+ }
+ }
+ if (!name || !strcmp(name, kPrefDisablePrefetch)) {
+ if (NS_SUCCEEDED(Preferences::GetBool(kPrefDisablePrefetch, &tmpbool))) {
+ mDisablePrefetch = 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, kPrefNetworkProxySOCKS)) {
+ nsAutoCString socks;
+ if (NS_SUCCEEDED(Preferences::GetCString(kPrefNetworkProxySOCKS, socks))) {
+ mHasSocksProxy = !socks.IsEmpty();
+ }
+ }
+ 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.PutEntry(token);
+ }
+ }
+ if (!name || !strcmp(name, kPrefDnsForceResolve)) {
+ Preferences::GetCString(kPrefDnsForceResolve, mForceResolve);
+ mForceResolveOn = !mForceResolve.IsEmpty();
+ }
+
+ if (StaticPrefs::network_proxy_type() ==
+ nsIProtocolProxyService::PROXYCONFIG_MANUAL) {
+ // Disable prefetching either by explicit preference or if a
+ // manual proxy is configured
+ mDisablePrefetch = true;
+ }
+ return NS_OK;
+}
+
+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(kPrefDisableIPv6, this, false);
+ prefs->AddObserver(kPrefDnsOfflineLocalhost, this, false);
+ prefs->AddObserver(kPrefDisablePrefetch, this, false);
+ prefs->AddObserver(kPrefBlockDotOnion, this, false);
+ prefs->AddObserver(kPrefDnsNotifyResolution, this, false);
+
+ // Monitor these to see if there is a change in proxy configuration
+ prefs->AddObserver(kPrefNetworkProxySOCKS, this, false);
+ }
+
+ nsDNSPrefetch::Initialize(this);
+
+ RegisterWeakMemoryReporter(this);
+
+ 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 = mResolver;
+ mResolver = nullptr;
+ }
+ if (res) {
+ 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;
+}
+
+bool nsDNSService::DNSForbiddenByActiveProxy(const nsACString& aHostname,
+ uint32_t flags) {
+ if (flags & nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS) {
+ return false;
+ }
+
+ // We should avoid doing DNS when a proxy is in use.
+ PRNetAddr tempAddr;
+ if (StaticPrefs::network_proxy_type() ==
+ nsIProtocolProxyService::PROXYCONFIG_MANUAL &&
+ mHasSocksProxy && StaticPrefs::network_proxy_socks_remote_dns()) {
+ // Allow IP lookups through, but nothing else.
+ if (PR_StringToNetAddr(nsCString(aHostname).get(), &tempAddr) !=
+ PR_SUCCESS) {
+ return true;
+ }
+ }
+ return false;
+}
+
+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, uint32_t flags,
+ nsIDNSResolverInfo* aResolver, 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.GetEntry(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 = GetMainThreadEventTarget();
+ }
+
+ 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, DNSResolverInfo::URL(aResolver),
+ type, aOriginAttributes, listener, flags, af);
+ if (!req) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = res->ResolveHost(req->mHost, DNSResolverInfo::URL(aResolver), type,
+ req->mOriginAttributes, flags, af, req);
+ req.forget(result);
+ return rv;
+}
+
+nsresult nsDNSService::CancelAsyncResolveInternal(
+ const nsACString& aHostname, uint16_t aType, uint32_t aFlags,
+ nsIDNSResolverInfo* aResolver, 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.GetEntry(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, DNSResolverInfo::URL(aResolver), aType,
+ aOriginAttributes, aFlags, af, aListener, aReason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::AsyncResolve(const nsACString& aHostname,
+ nsIDNSService::ResolveType aType, uint32_t flags,
+ nsIDNSResolverInfo* aResolver,
+ nsIDNSListener* listener, nsIEventTarget* target_,
+ JS::HandleValue 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, aResolver, listener,
+ target_, attrs, result);
+}
+
+NS_IMETHODIMP
+nsDNSService::AsyncResolveNative(const nsACString& aHostname,
+ nsIDNSService::ResolveType aType,
+ uint32_t flags, nsIDNSResolverInfo* aResolver,
+ nsIDNSListener* aListener,
+ nsIEventTarget* target_,
+ const OriginAttributes& aOriginAttributes,
+ nsICancelable** result) {
+ return AsyncResolveInternal(aHostname, aType, flags, aResolver, aListener,
+ target_, aOriginAttributes, result);
+}
+
+NS_IMETHODIMP
+nsDNSService::NewTRRResolverInfo(const nsACString& aTrrURL,
+ nsIDNSResolverInfo** aResolver) {
+ RefPtr<DNSResolverInfo> res = new DNSResolverInfo(aTrrURL);
+ res.forget(aResolver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::CancelAsyncResolve(const nsACString& aHostname,
+ nsIDNSService::ResolveType aType,
+ uint32_t aFlags, nsIDNSResolverInfo* aResolver,
+ nsIDNSListener* aListener, nsresult aReason,
+ JS::HandleValue 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, aResolver,
+ aListener, aReason, attrs);
+}
+
+NS_IMETHODIMP
+nsDNSService::CancelAsyncResolveNative(
+ const nsACString& aHostname, nsIDNSService::ResolveType aType,
+ uint32_t aFlags, nsIDNSResolverInfo* aResolver, nsIDNSListener* aListener,
+ nsresult aReason, const OriginAttributes& aOriginAttributes) {
+ return CancelAsyncResolveInternal(aHostname, aType, aFlags, aResolver,
+ aListener, aReason, aOriginAttributes);
+}
+
+NS_IMETHODIMP
+nsDNSService::Resolve(const nsACString& aHostname, uint32_t flags,
+ JS::HandleValue 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, uint32_t 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, uint32_t flags,
+ const OriginAttributes& aOriginAttributes, nsIDNSRecord** result) {
+ return ResolveInternal(aHostname, flags, aOriginAttributes, result);
+}
+
+nsresult nsDNSService::ResolveInternal(
+ const nsACString& aHostname, uint32_t 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.GetEntry(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, 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;
+ if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
+ nsAutoCString converted = NS_ConvertUTF16toUTF8(data);
+ if (mResolver && !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(mResolver, NS_ERROR_NOT_INITIALIZED);
+ if (mResolverPrefsUpdated && mResolver) {
+ mResolver->SetCacheLimits(mResCacheEntries, mResCacheExpiration,
+ mResCacheGrace);
+ }
+ } else if (!strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ Shutdown();
+ }
+
+ if (flushCache && mResolver) {
+ mResolver->FlushCache(false);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+uint16_t nsDNSService::GetAFForLookup(const nsACString& host, uint32_t flags) {
+ if (mDisableIPv6 || (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 (PL_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) {
+ NS_ENSURE_TRUE(mResolver, NS_ERROR_NOT_INITIALIZED);
+ mResolver->GetDNSCacheEntries(args);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::ClearCache(bool aTrrToo) {
+ NS_ENSURE_TRUE(mResolver, NS_ERROR_NOT_INITIALIZED);
+ mResolver->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::GetCurrentTrrURI(nsACString& aURI) {
+ if (mTrrService) {
+ return mTrrService->GetURI(aURI);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSService::GetCurrentTrrMode(uint32_t* aMode) {
+ *aMode = 0; // The default mode.
+ if (mTrrService) {
+ *aMode = mTrrService->Mode();
+ }
+ 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 (auto iter = mFailedSVCDomainNames.ConstIter(); !iter.Done();
+ iter.Next()) {
+ n += iter.UserData()->ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (const auto& name : *iter.UserData()) {
+ 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);
+
+ nsTArray<nsCString>* failedList = mFailedSVCDomainNames.Get(aOwnerName);
+ if (!failedList) {
+ failedList = new nsTArray<nsCString>(1);
+ mFailedSVCDomainNames.Put(aOwnerName, failedList);
+ }
+
+ failedList->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;
+}
diff --git a/netwerk/dns/nsDNSService2.h b/netwerk/dns/nsDNSService2.h
new file mode 100644
index 0000000000..06e901ddbb
--- /dev/null
+++ b/netwerk/dns/nsDNSService2.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/. */
+
+#ifndef nsDNSService2_h__
+#define nsDNSService2_h__
+
+#include "nsClassHashtable.h"
+#include "nsPIDNSService.h"
+#include "nsIIDNService.h"
+#include "nsIMemoryReporter.h"
+#include "nsIObserver.h"
+#include "nsHostResolver.h"
+#include "nsString.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Attributes.h"
+#include "TRRService.h"
+
+class nsAuthSSPI;
+
+class nsDNSService final : public nsPIDNSService,
+ public nsIObserver,
+ public nsIMemoryReporter {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSPIDNSSERVICE
+ NS_DECL_NSIDNSSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIMEMORYREPORTER
+
+ nsDNSService();
+
+ static already_AddRefed<nsIDNSService> GetXPCOMSingleton();
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
+
+ bool GetOffline() const;
+
+ protected:
+ friend class nsAuthSSPI;
+
+ nsresult DeprecatedSyncResolve(
+ const nsACString& aHostname, uint32_t flags,
+ const mozilla::OriginAttributes& aOriginAttributes,
+ nsIDNSRecord** result);
+
+ private:
+ ~nsDNSService();
+
+ nsresult ReadPrefs(const char* name);
+ static already_AddRefed<nsDNSService> GetSingleton();
+
+ uint16_t GetAFForLookup(const nsACString& host, uint32_t flags);
+
+ nsresult PreprocessHostname(bool aLocalDomain, const nsACString& aInput,
+ nsIIDNService* aIDN, nsACString& aACE);
+
+ nsresult AsyncResolveInternal(
+ const nsACString& aHostname, uint16_t type, uint32_t flags,
+ nsIDNSResolverInfo* aResolver, nsIDNSListener* aListener,
+ nsIEventTarget* target_,
+ const mozilla::OriginAttributes& aOriginAttributes,
+ nsICancelable** result);
+
+ nsresult CancelAsyncResolveInternal(
+ const nsACString& aHostname, uint16_t aType, uint32_t aFlags,
+ nsIDNSResolverInfo* aResolver, nsIDNSListener* aListener,
+ nsresult aReason, const mozilla::OriginAttributes& aOriginAttributes);
+
+ nsresult ResolveInternal(const nsACString& aHostname, uint32_t flags,
+ const mozilla::OriginAttributes& aOriginAttributes,
+ nsIDNSRecord** result);
+
+ bool DNSForbiddenByActiveProxy(const nsACString& aHostname, uint32_t flags);
+
+ RefPtr<nsHostResolver> mResolver;
+ nsCOMPtr<nsIIDNService> mIDN;
+
+ // mLock protects access to mResolver, mLocalDomains, mIPv4OnlyDomains and
+ // mFailedSVCDomainNames
+ mozilla::Mutex 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 mDisableIPv6;
+ bool mDisablePrefetch;
+ bool mBlockDotOnion;
+ bool mNotifyResolution;
+ bool mOfflineLocalhost;
+ bool mForceResolveOn;
+ nsTHashtable<nsCStringHashKey> mLocalDomains;
+ RefPtr<mozilla::net::TRRService> mTrrService;
+ mozilla::Atomic<bool, mozilla::Relaxed> mHasSocksProxy;
+
+ uint32_t mResCacheEntries;
+ uint32_t mResCacheExpiration;
+ uint32_t mResCacheGrace;
+ bool mResolverPrefsUpdated;
+ nsClassHashtable<nsCStringHashKey, nsTArray<nsCString>> mFailedSVCDomainNames;
+};
+
+#endif // nsDNSService2_h__
diff --git a/netwerk/dns/nsEffectiveTLDService.cpp b/netwerk/dns/nsEffectiveTLDService.cpp
new file mode 100644
index 0000000000..3a2270a212
--- /dev/null
+++ b/netwerk/dns/nsEffectiveTLDService.cpp
@@ -0,0 +1,492 @@
+/* -*- 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 "MainThreadUtils.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 "prnetdb.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()
+ : mIDNService(), mGraphLock("nsEffectiveTLDService::mGraph") {
+ mGraph.emplace(etld_dafsa::kDafsa);
+}
+
+nsresult nsEffectiveTLDService::Init() {
+ 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 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
+ PRNetAddr addr;
+ PRStatus result = PR_StringToNetAddr(aHostname.get(), &addr);
+ if (result == PR_SUCCESS) {
+ // 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) {
+ 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.BeginReading());
+ 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;
+}
diff --git a/netwerk/dns/nsEffectiveTLDService.h b/netwerk/dns/nsEffectiveTLDService.h
new file mode 100644
index 0000000000..9a9ad22977
--- /dev/null
+++ b/netwerk/dns/nsEffectiveTLDService.h
@@ -0,0 +1,92 @@
+//* -*- 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_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();
+
+ nsCOMPtr<nsIIDNService> mIDNService;
+
+ // The DAFSA provides a compact encoding of the rather large eTLD list.
+ mozilla::Maybe<mozilla::Dafsa> mGraph;
+
+ // Memory map used for a new updated dafsa
+ mozilla::loader::AutoMemMap mDafsaMap;
+
+ // 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;
+ }
+ };
+
+ TldCache mMruTable;
+};
+
+#endif // EffectiveTLDService_h
diff --git a/netwerk/dns/nsHostResolver.cpp b/netwerk/dns/nsHostResolver.cpp
new file mode 100644
index 0000000000..268e2ec38b
--- /dev/null
+++ b/netwerk/dns/nsHostResolver.cpp
@@ -0,0 +1,2325 @@
+/* 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 "nsISupportsBase.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 "plstr.h"
+#include "nsQueryObject.h"
+#include "nsURLHelper.h"
+#include "nsThreadUtils.h"
+#include "nsThreadPool.h"
+#include "GetAddrInfo.h"
+#include "GeckoProfiler.h"
+#include "TRR.h"
+#include "TRRQuery.h"
+#include "TRRService.h"
+
+#include "mozilla/Atomics.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"
+
+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 MAX_RESOLVER_THREADS in size. New
+// requests go first to an idle thread. If that cannot be found and there are
+// fewer than MAX_RESOLVER_THREADS 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 HighThreadThreshold
+// 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 HighThreadThreshold in size a thread will be
+// destroyed after ShortIdleTimeoutSeconds of idle time. Smaller pools use
+// LongIdleTimeoutSeconds for a timeout period.
+
+#define HighThreadThreshold MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY
+#define LongIdleTimeoutSeconds 300 // for threads 1 -> HighThreadThreshold
+#define ShortIdleTimeoutSeconds \
+ 60 // for threads HighThreadThreshold+1 -> MAX_RESOLVER_THREADS
+
+static_assert(
+ HighThreadThreshold <= MAX_RESOLVER_THREADS,
+ "High Thread Threshold should be less equal Maximum allowed thread");
+
+//----------------------------------------------------------------------------
+
+namespace mozilla::net {
+LazyLogModule gHostResolverLog("nsHostResolver");
+#define LOG(args) \
+ MOZ_LOG(mozilla::net::gHostResolverLog, mozilla::LogLevel::Debug, args)
+#define LOG1(args) \
+ MOZ_LOG(mozilla::net::gHostResolverLog, mozilla::LogLevel::Error, args)
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(mozilla::net::gHostResolverLog, mozilla::LogLevel::Debug)
+} // 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;
+ }
+
+ LOG(("Calling 'res_ninit'.\n"));
+
+ mLastReset = PR_IntervalNow();
+ return (res_ninit(&_res) == 0);
+ }
+
+ private:
+ PRIntervalTime mLastReset;
+};
+
+#endif // RES_RETRY_ON_FAILURE
+
+//----------------------------------------------------------------------------
+
+static inline bool IsHighPriority(uint16_t flags) {
+ return !(flags & (nsHostResolver::RES_PRIORITY_LOW |
+ nsHostResolver::RES_PRIORITY_MEDIUM));
+}
+
+static inline bool IsMediumPriority(uint16_t flags) {
+ return flags & nsHostResolver::RES_PRIORITY_MEDIUM;
+}
+
+static inline bool IsLowPriority(uint16_t flags) {
+ return flags & nsHostResolver::RES_PRIORITY_LOW;
+}
+
+//----------------------------------------------------------------------------
+// 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)
+
+nsHostKey::nsHostKey(const nsACString& aHost, const nsACString& aTrrServer,
+ uint16_t aType, uint16_t 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;
+}
+
+NS_IMPL_ISUPPORTS0(nsHostRecord)
+
+nsHostRecord::nsHostRecord(const nsHostKey& key)
+ : nsHostKey(key),
+ mEffectiveTRRMode(nsIRequest::TRR_DEFAULT_MODE),
+ mTRRQuery("nsHostRecord.mTRRQuery"),
+ mResolving(0),
+ negative(false),
+ mDoomed(false) {}
+
+void nsHostRecord::Invalidate() { mDoomed = true; }
+
+void nsHostRecord::Cancel() {
+ RefPtr<TRRQuery> query;
+ {
+ auto lock = mTRRQuery.Lock();
+ query.swap(lock.ref());
+ }
+
+ if (query) {
+ query->Cancel();
+ }
+}
+
+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);
+}
+
+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;
+}
+
+bool nsHostRecord::HasUsableResult(const mozilla::TimeStamp& now,
+ uint16_t queryFlags) const {
+ if (mDoomed) {
+ return false;
+ }
+
+ // 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 HasUsableResultInternal();
+}
+
+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),
+ addr_info_lock("AddrHostRecord.addr_info_lock"),
+ addr_info_gencnt(0),
+ addr_info(nullptr),
+ addr(nullptr),
+ mTRRUsed(false),
+ mTRRSuccess(0),
+ mNativeSuccess(0) {}
+
+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 {
+ 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::ResolveComplete() {
+ if (LoadNativeUsed()) {
+ if (mNativeSuccess) {
+ uint32_t millis = static_cast<uint32_t>(mNativeDuration.ToMilliseconds());
+ Telemetry::Accumulate(Telemetry::DNS_NATIVE_LOOKUP_TIME, millis);
+ }
+ AccumulateCategoricalKeyed(
+ TRRService::AutoDetectedKey(),
+ mNativeSuccess ? Telemetry::LABELS_DNS_LOOKUP_DISPOSITION2::osOK
+ : Telemetry::LABELS_DNS_LOOKUP_DISPOSITION2::osFail);
+ }
+
+ if (mTRRUsed) {
+ if (mTRRSuccess) {
+ uint32_t millis = static_cast<uint32_t>(mTrrDuration.ToMilliseconds());
+ Telemetry::Accumulate(Telemetry::DNS_TRR_LOOKUP_TIME2,
+ TRRService::AutoDetectedKey(), millis);
+ }
+ AccumulateCategoricalKeyed(
+ TRRService::AutoDetectedKey(),
+ mTRRSuccess ? Telemetry::LABELS_DNS_LOOKUP_DISPOSITION2::trrOK
+ : Telemetry::LABELS_DNS_LOOKUP_DISPOSITION2::trrFail);
+ }
+
+ if (nsHostResolver::Mode() == MODE_TRRFIRST) {
+ Telemetry::Accumulate(Telemetry::TRR_SKIP_REASON_TRR_FIRST,
+ mTRRTRRSkippedReason);
+
+ if (mNativeSuccess) {
+ Telemetry::Accumulate(Telemetry::TRR_SKIP_REASON_DNS_WORKED,
+ mTRRTRRSkippedReason);
+ }
+ }
+
+ 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_DISABLED2,
+ TRRService::AutoDetectedKey(), mNativeSuccess);
+ } else {
+ if (mTRRSuccess) {
+ AccumulateCategoricalKeyed(TRRService::AutoDetectedKey(),
+ Telemetry::LABELS_DNS_TRR_FIRST3::TRR);
+ } else if (mNativeSuccess) {
+ if (mTRRUsed) {
+ AccumulateCategoricalKeyed(
+ TRRService::AutoDetectedKey(),
+ Telemetry::LABELS_DNS_TRR_FIRST3::NativeAfterTRR);
+ } else {
+ AccumulateCategoricalKeyed(TRRService::AutoDetectedKey(),
+ Telemetry::LABELS_DNS_TRR_FIRST3::Native);
+ }
+ } else {
+ AccumulateCategoricalKeyed(
+ TRRService::AutoDetectedKey(),
+ Telemetry::LABELS_DNS_TRR_FIRST3::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 (mTRRUsed && !mTRRSuccess && mNativeSuccess && gTRRService) {
+ gTRRService->AddToBlocklist(nsCString(host), originSuffix, pb, true);
+ }
+}
+
+AddrHostRecord::DnsPriority AddrHostRecord::GetPriority(uint16_t aFlags) {
+ if (IsHighPriority(aFlags)) {
+ return AddrHostRecord::DNS_PRIORITY_HIGH;
+ }
+ if (IsMediumPriority(aFlags)) {
+ return AddrHostRecord::DNS_PRIORITY_MEDIUM;
+ }
+
+ return AddrHostRecord::DNS_PRIORITY_LOW;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(TypeHostRecord, nsHostRecord, TypeHostRecord,
+ nsIDNSTXTRecord, nsIDNSHTTPSSVCRecord)
+
+TypeHostRecord::TypeHostRecord(const nsHostKey& key)
+ : nsHostRecord(key),
+ DNSHTTPSSVCRecordBase(key.host),
+ mResultsLock("TypeHostRecord.mResultsLock"),
+ mAllRecordsExcluded(false) {}
+
+TypeHostRecord::~TypeHostRecord() { mCallbacks.clear(); }
+
+bool TypeHostRecord::HasUsableResultInternal() const {
+ return !mResults.is<Nothing>();
+}
+
+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);
+
+ 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);
+
+ if (!mResults.is<TypeRecordHTTPSSVC>()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aResult = mAllRecordsExcluded;
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------------
+
+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;
+
+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),
+ mLock("nsHostResolver.mLock"),
+ mIdleTaskCV(mLock, "nsHostResolver.mIdleTaskCV"),
+ mEvictionQSize(0),
+ mShutdown(true),
+ mNumIdleTasks(0),
+ mActiveTaskCount(0),
+ mActiveAnyThreadCount(0),
+ mPendingCount(0) {
+ mCreationTime = PR_Now();
+
+ mLongIdleTimeout = TimeDuration::FromSeconds(LongIdleTimeoutSeconds);
+ mShortIdleTimeout = TimeDuration::FromSeconds(ShortIdleTimeoutSeconds);
+}
+
+nsHostResolver::~nsHostResolver() = default;
+
+nsresult nsHostResolver::Init() {
+ 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) {
+ LOG(("Calling 'res_ninit'.\n"));
+ res_ninit(&_res);
+ }
+#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);
+ }
+
+ nsCOMPtr<nsIThreadPool> threadPool = new nsThreadPool();
+ MOZ_ALWAYS_SUCCEEDS(threadPool->SetThreadLimit(MAX_RESOLVER_THREADS));
+ MOZ_ALWAYS_SUCCEEDS(threadPool->SetIdleThreadLimit(MAX_RESOLVER_THREADS));
+ 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->mTRRTRRSkippedReason);
+ } else {
+ mozilla::net::TypeRecordResultType empty(Nothing{});
+ CompleteLookupByType(rec, NS_ERROR_ABORT, empty, 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);
+ 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();
+ mRecordDB.Remove(*static_cast<nsHostKey*>(rec));
+ }
+ mEvictionQ.clear();
+ }
+
+ // 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)) {
+ if (record->isInList()) {
+ record->remove();
+ }
+ 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;
+
+ // Move queues to temporary lists.
+ pendingQHigh = std::move(mHighQ);
+ pendingQMed = std::move(mMediumQ);
+ pendingQLow = std::move(mLowQ);
+ evictionQ = std::move(mEvictionQ);
+
+ mEvictionQSize = 0;
+ mPendingCount = 0;
+
+ if (mNumIdleTasks) {
+ mIdleTaskCV.NotifyAll();
+ }
+
+ for (auto iter = mRecordDB.Iter(); !iter.Done(); iter.Next()) {
+ iter.UserData()->Cancel();
+ }
+ // empty host database
+ mRecordDB.Clear();
+
+ mNCS = nullptr;
+ }
+
+ ClearPendingQueue(pendingQHigh);
+ ClearPendingQueue(pendingQMed);
+ ClearPendingQueue(pendingQLow);
+
+ if (!evictionQ.isEmpty()) {
+ for (const RefPtr<nsHostRecord>& rec : evictionQ) {
+ rec->Cancel();
+ }
+ }
+
+ pendingQHigh.clear();
+ pendingQMed.clear();
+ pendingQLow.clear();
+ evictionQ.clear();
+
+ // 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, uint16_t 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>& entry = mRecordDB.GetOrInsert(key);
+ if (!entry) {
+ entry = InitRecord(key);
+ }
+
+ RefPtr<nsHostRecord> rec = entry;
+ 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;
+ PRNetAddr prAddr;
+ memset(&prAddr, 0, sizeof(prAddr));
+ if (key.af == PR_AF_INET || key.af == PR_AF_UNSPEC) {
+ MOZ_RELEASE_ASSERT(PR_StringToNetAddr("127.0.0.1", &prAddr) == PR_SUCCESS);
+ addresses.AppendElement(NetAddr(&prAddr));
+ }
+ if (key.af == PR_AF_INET6 || key.af == PR_AF_UNSPEC) {
+ MOZ_RELEASE_ASSERT(PR_StringToNetAddr("::1", &prAddr) == PR_SUCCESS);
+ addresses.AppendElement(NetAddr(&prAddr));
+ }
+
+ RefPtr<AddrInfo> ai = new AddrInfo(rec->host, 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();
+}
+
+nsresult nsHostResolver::ResolveHost(const nsACString& aHost,
+ const nsACString& aTrrServer,
+ uint16_t type,
+ const OriginAttributes& aOriginAttributes,
+ uint16_t 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;
+ }
+
+ // By-Type requests use only TRR. If TRR is disabled we can return
+ // immediately.
+ if (IS_OTHER_TYPE(type) && Mode() == MODE_TRROFF) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ // Used to try to parse to an IP address literal.
+ PRNetAddr tempAddr;
+ // Unfortunately, PR_StringToNetAddr does not properly initialize
+ // the output buffer in the case of IPv6 input. See bug 223145.
+ memset(&tempAddr, 0, sizeof(PRNetAddr));
+
+ if (IS_OTHER_TYPE(type) &&
+ (PR_StringToNetAddr(host.get(), &tempAddr) == PR_SUCCESS)) {
+ // For by-type queries the host cannot be IP literal.
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ memset(&tempAddr, 0, sizeof(PRNetAddr));
+
+ 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.
+
+ bool excludedFromTRR = false;
+
+ if (gTRRService && gTRRService->IsExcludedFromTRR(host)) {
+ flags |= RES_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>& entry = mRecordDB.GetOrInsert(key);
+ if (!entry) {
+ entry = InitRecord(key);
+ }
+
+ RefPtr<nsHostRecord> rec = entry;
+ 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 (excludedFromTRR) {
+ rec->RecordReason(nsHostRecord::TRR_EXCLUDED);
+ }
+
+ if (!(flags & RES_BYPASS_CACHE) &&
+ rec->HasUsableResult(TimeStamp::NowLoRes(), flags)) {
+ LOG((" Using cached record for host [%s].\n", host.get()));
+ // put reference to host record on stack...
+ result = rec;
+ if (IS_ADDR_TYPE(type)) {
+ 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(rec, host);
+
+ if (rec->negative) {
+ LOG((" Negative cache entry for host [%s].\n", host.get()));
+ if (IS_ADDR_TYPE(type)) {
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+ METHOD_NEGATIVE_HIT);
+ }
+ status = NS_ERROR_UNKNOWN_HOST;
+ }
+
+ // Check whether host is a IP address for A/AAAA queries.
+ // For by-type records we have already checked at the beginning of
+ // this function.
+ } 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()));
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_LITERAL);
+ result = rec;
+ } else if (addrRec &&
+ PR_StringToNetAddr(host.get(), &tempAddr) == PR_SUCCESS) {
+ // 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()));
+
+ // ok, just copy the result into the host record, and be
+ // done with it! ;-)
+ addrRec->addr = MakeUnique<NetAddr>();
+ PRNetAddrToNetAddr(&tempAddr, addrRec->addr.get());
+ // put reference to host record on stack...
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_LITERAL);
+ result = rec;
+
+ // Check if we have received too many requests.
+ } else if (mPendingCount >= 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) {
+ // If this is an IPV4 or IPV6 specific request, check if there is
+ // an AF_UNSPEC entry we can use. Otherwise, hit the resolver...
+ if (addrRec && !(flags & RES_BYPASS_CACHE) &&
+ ((af == PR_AF_INET) || (af == PR_AF_INET6))) {
+ // Check for an AF_UNSPEC entry.
+
+ const nsHostKey unspecKey(
+ host, aTrrServer, nsIDNSService::RESOLVE_TYPE_DEFAULT, flags,
+ PR_AF_UNSPEC, (aOriginAttributes.mPrivateBrowsingId > 0),
+ originSuffix);
+ RefPtr<nsHostRecord> unspecRec = mRecordDB.Get(unspecKey);
+
+ TimeStamp now = TimeStamp::NowLoRes();
+ if (unspecRec && unspecRec->HasUsableResult(now, flags)) {
+ 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", host.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) {
+ rec->negative = unspecRec->negative;
+ rec->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->IsTRR(), std::move(addresses));
+ addrRec->addr_info_gencnt++;
+ rec->CopyExpirationTimesAndFlagsFrom(unspecRec);
+ }
+ }
+ }
+ // Now check if we have a new record.
+ if (rec->HasUsableResult(now, flags)) {
+ result = rec;
+ if (rec->negative) {
+ status = NS_ERROR_UNKNOWN_HOST;
+ }
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2, METHOD_HIT);
+ ConditionallyRefreshRecord(rec, host);
+ } 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.",
+ host.get()));
+ result = rec;
+ rec->negative = true;
+ status = NS_ERROR_UNKNOWN_HOST;
+ Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
+ METHOD_NEGATIVE_HIT);
+ }
+ }
+ }
+
+ // 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);
+ 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);
+
+ // Only A/AAAA records are place in a queue. The queues are for
+ // the native resolver, therefore by-type request are never put
+ // into a queue.
+ if (addrRec && addrRec->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.
+ rec->remove();
+ mHighQ.insertBack(rec);
+ rec->flags = flags;
+ ConditionallyCreateThread(rec);
+ } else if (IsMediumPriority(flags) && IsLowPriority(rec->flags)) {
+ // Move from low to med.
+ rec->remove();
+ mMediumQ.insertBack(rec);
+ rec->flags = flags;
+ mIdleTaskCV.Notify();
+ }
+ }
+ }
+
+ if (result && callback->isInList()) {
+ callback->remove();
+ }
+ } // lock
+
+ if (result) {
+ callback->OnResolveHostComplete(this, result, status);
+ }
+
+ return rv;
+}
+
+void nsHostResolver::DetachCallback(
+ const nsACString& host, const nsACString& aTrrServer, uint16_t aType,
+ const OriginAttributes& aOriginAttributes, uint16_t 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 < HighThreadThreshold) ||
+ (IsHighPriority(rec->flags) &&
+ mActiveTaskCount < MAX_RESOLVER_THREADS)) {
+ 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, pushedTRR);
+}
+
+void nsHostResolver::MaybeRenewHostRecord(nsHostRecord* aRec) {
+ MutexAutoLock lock(mLock);
+ MaybeRenewHostRecordLocked(aRec);
+}
+
+void nsHostResolver::MaybeRenewHostRecordLocked(nsHostRecord* aRec) {
+ mLock.AssertCurrentThreadOwns();
+ if (aRec->isInList()) {
+ // we're already on the eviction queue. This is a renewal
+ MOZ_ASSERT(mEvictionQSize);
+ AssertOnQ(aRec, mEvictionQ);
+ aRec->remove();
+ mEvictionQSize--;
+ }
+}
+
+// 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, TRR* pushedTRR) {
+ if (Mode() == 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);
+
+ auto hasConnectivity = [this]() -> bool {
+ 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;
+ };
+
+ nsIRequest::TRRMode reqMode = rec->mEffectiveTRRMode;
+ if (rec->mTrrServer.IsEmpty() &&
+ (!gTRRService || !gTRRService->Enabled(reqMode))) {
+ 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.
+ rec->RecordReason(nsHostRecord::TRR_IS_OFFLINE);
+ }
+ if (!hasConnectivity()) {
+ rec->RecordReason(nsHostRecord::TRR_NO_CONNECTIVITY);
+ } else {
+ rec->RecordReason(nsHostRecord::TRR_NOT_CONFIRMED);
+ }
+
+ LOG(("TrrLookup:: %s service not enabled\n", rec->host.get()));
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+
+ MaybeRenewHostRecordLocked(rec);
+
+ RefPtr<TRRQuery> query = new TRRQuery(this, rec);
+ nsresult rv = query->DispatchLookup(pushedTRR);
+ if (NS_FAILED(rv)) {
+ rec->RecordReason(nsHostRecord::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++;
+ return NS_OK;
+}
+
+void nsHostResolver::AssertOnQ(nsHostRecord* rec,
+ LinkedList<RefPtr<nsHostRecord>>& q) {
+#ifdef DEBUG
+ MOZ_ASSERT(!q.isEmpty());
+ MOZ_ASSERT(rec->isInList());
+ for (const RefPtr<nsHostRecord>& r : q) {
+ if (rec == r) {
+ return;
+ }
+ }
+ MOZ_ASSERT(false, "Did not find element");
+#endif
+}
+
+nsresult nsHostResolver::NativeLookup(nsHostRecord* aRec) {
+ if (StaticPrefs::network_dns_disabled()) {
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ LOG(("NativeLookup host:%s af:%" PRId16, aRec->host.get(), aRec->af));
+
+ // Only A/AAAA request are resolve natively.
+ MOZ_ASSERT(aRec->IsAddrRecord());
+ mLock.AssertCurrentThreadOwns();
+
+ RefPtr<nsHostRecord> rec(aRec);
+ RefPtr<AddrHostRecord> addrRec;
+ addrRec = do_QueryObject(rec);
+ MOZ_ASSERT(addrRec);
+
+ addrRec->mNativeStart = TimeStamp::Now();
+
+ // Add rec to one of the pending queues, possibly removing it from mEvictionQ.
+ MaybeRenewHostRecordLocked(rec);
+
+ switch (AddrHostRecord::GetPriority(rec->flags)) {
+ case AddrHostRecord::DNS_PRIORITY_HIGH:
+ mHighQ.insertBack(rec);
+ break;
+
+ case AddrHostRecord::DNS_PRIORITY_MEDIUM:
+ mMediumQ.insertBack(rec);
+ break;
+
+ case AddrHostRecord::DNS_PRIORITY_LOW:
+ mLowQ.insertBack(rec);
+ break;
+ }
+ mPendingCount++;
+
+ addrRec->StoreNative(true);
+ addrRec->StoreNativeUsed(true);
+ addrRec->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),
+ static_cast<uint32_t>(mPendingCount)));
+
+ return rv;
+}
+
+// static
+ResolverMode nsHostResolver::Mode() {
+ if (gTRRService) {
+ return static_cast<ResolverMode>(gTRRService->Mode());
+ }
+
+ // If we don't have a TRR service just return MODE_TRROFF so we don't make
+ // any TRR requests by mistake.
+ return MODE_TRROFF;
+}
+
+nsIRequest::TRRMode nsHostRecord::TRRMode() {
+ return nsIDNSService::GetTRRModeFromFlags(flags);
+}
+
+// static
+void nsHostResolver::ComputeEffectiveTRRMode(nsHostRecord* aRec) {
+ 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 (!gTRRService) {
+ aRec->RecordReason(nsHostRecord::TRR_NO_GSERVICE);
+ aRec->mEffectiveTRRMode = requestMode;
+ return;
+ }
+
+ if (!aRec->mTrrServer.IsEmpty()) {
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_ONLY_MODE;
+ return;
+ }
+
+ if (gTRRService->IsExcludedFromTRR(aRec->host)) {
+ aRec->RecordReason(nsHostRecord::TRR_EXCLUDED);
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE;
+ return;
+ }
+
+ if (StaticPrefs::network_dns_skipTRR_when_parental_control_enabled() &&
+ gTRRService->ParentalControlEnabled()) {
+ aRec->RecordReason(nsHostRecord::TRR_PARENTAL_CONTROL);
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE;
+ return;
+ }
+
+ if (resolverMode == MODE_TRROFF) {
+ aRec->RecordReason(nsHostRecord::TRR_OFF_EXPLICIT);
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE;
+ return;
+ }
+
+ if (requestMode == nsIRequest::TRR_DISABLED_MODE) {
+ aRec->RecordReason(nsHostRecord::TRR_REQ_MODE_DISABLED);
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE;
+ return;
+ }
+
+ if ((requestMode == nsIRequest::TRR_DEFAULT_MODE &&
+ resolverMode == MODE_NATIVEONLY)) {
+ aRec->RecordReason(nsHostRecord::TRR_MODE_NOT_ENABLED);
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_DISABLED_MODE;
+ return;
+ }
+
+ if (requestMode == nsIRequest::TRR_DEFAULT_MODE &&
+ resolverMode == MODE_TRRFIRST) {
+ aRec->mEffectiveTRRMode = nsIRequest::TRR_FIRST_MODE;
+ return;
+ }
+
+ if (requestMode == nsIRequest::TRR_DEFAULT_MODE &&
+ resolverMode == 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) {
+ LOG(("NameLookup host:%s af:%" PRId16, rec->host.get(), rec->af));
+
+ 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 wronly report the reason for the previous one.
+ rec->mTRRTRRSkippedReason = nsHostRecord::TRR_UNSET;
+
+ ComputeEffectiveTRRMode(rec);
+
+ if (rec->IsAddrRecord()) {
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec);
+ MOZ_ASSERT(addrRec);
+
+ addrRec->StoreNativeUsed(false);
+ addrRec->mTRRUsed = false;
+ addrRec->mNativeSuccess = false;
+ }
+
+ 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 & RES_DISABLE_TRR) {
+ LOG(("TRR with server and DISABLE_TRR flag. Returning error."));
+ return NS_ERROR_UNKNOWN_HOST;
+ }
+ return TrrLookup(rec);
+ }
+
+ LOG(("NameLookup: %s effectiveTRRmode: %d flags: %X", rec->host.get(),
+ rec->mEffectiveTRRMode, rec->flags));
+
+ if (rec->flags & RES_DISABLE_TRR) {
+ rec->RecordReason(nsHostRecord::TRR_DISABLED_FLAG);
+ }
+
+ if (rec->mEffectiveTRRMode != nsIRequest::TRR_DISABLED_MODE &&
+ !((rec->flags & RES_DISABLE_TRR))) {
+ rv = TrrLookup(rec);
+ }
+
+ bool serviceNotReady = !gTRRService || !gTRRService->IsConfirmed();
+
+ if (rec->mEffectiveTRRMode == nsIRequest::TRR_DISABLED_MODE ||
+ (rec->mEffectiveTRRMode == nsIRequest::TRR_FIRST_MODE &&
+ (rec->flags & RES_DISABLE_TRR || serviceNotReady || NS_FAILED(rv)))) {
+ if (!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(addrRec && !addrRec->mTRRUsed);
+#endif
+
+ rv = NativeLookup(rec);
+ }
+
+ return rv;
+}
+
+nsresult nsHostResolver::ConditionallyRefreshRecord(nsHostRecord* rec,
+ const nsACString& host) {
+ if ((rec->CheckExpiration(TimeStamp::NowLoRes()) != nsHostRecord::EXP_VALID ||
+ rec->negative) &&
+ !rec->mResolving) {
+ LOG((" Using %s cache entry for host [%s] but starting async renewal.",
+ rec->negative ? "negative" : "positive", host.BeginReading()));
+ NameLookup(rec);
+
+ 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;
+}
+
+void nsHostResolver::DeQueue(LinkedList<RefPtr<nsHostRecord>>& aQ,
+ AddrHostRecord** aResult) {
+ RefPtr<nsHostRecord> rec = aQ.popFirst();
+ mPendingCount--;
+ MOZ_ASSERT(rec->IsAddrRecord());
+ RefPtr<AddrHostRecord> addrRec = do_QueryObject(rec);
+ MOZ_ASSERT(addrRec);
+ addrRec.forget(aResult);
+}
+
+bool nsHostResolver::GetHostToLookup(AddrHostRecord** result) {
+ bool timedOut = false;
+ TimeDuration timeout;
+ TimeStamp epoch, now;
+
+ MutexAutoLock lock(mLock);
+
+ timeout = (mNumIdleTasks >= HighThreadThreshold) ? 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))
+
+ if (!mHighQ.isEmpty()) {
+ DeQueue(mHighQ, result);
+ SET_GET_TTL(*result, false);
+ return true;
+ }
+
+ if (mActiveAnyThreadCount < HighThreadThreshold) {
+ if (!mMediumQ.isEmpty()) {
+ DeQueue(mMediumQ, result);
+ mActiveAnyThreadCount++;
+ (*result)->StoreUsingAnyThread(true);
+ SET_GET_TTL(*result, true);
+ return true;
+ }
+
+ if (!mLowQ.isEmpty()) {
+ DeQueue(mLowQ, result);
+ mActiveAnyThreadCount++;
+ (*result)->StoreUsingAnyThread(true);
+ SET_GET_TTL(*result, true);
+ 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->IsTRR() != rrset2->IsTRR()) {
+ 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) {
+ if (rec->isInList()) {
+ MOZ_DIAGNOSTIC_ASSERT(!mEvictionQ.contains(rec),
+ "Already in eviction queue");
+ MOZ_DIAGNOSTIC_ASSERT(!mHighQ.contains(rec), "Already in high queue");
+ MOZ_DIAGNOSTIC_ASSERT(!mMediumQ.contains(rec), "Already in med queue");
+ MOZ_DIAGNOSTIC_ASSERT(!mLowQ.contains(rec), "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.
+ rec->remove();
+ }
+ mEvictionQ.insertBack(rec);
+ if (mEvictionQSize < mMaxCacheEntries) {
+ mEvictionQSize++;
+ } else {
+ // remove first element on mEvictionQ
+ RefPtr<nsHostRecord> head = mEvictionQ.popFirst();
+ mRecordDB.Remove(*static_cast<nsHostKey*>(head.get()));
+
+ if (!head->negative) {
+ // record the age of the entry upon eviction.
+ TimeDuration age = TimeStamp::NowLoRes() - head->mValidStart;
+ if (rec->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 (rec->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));
+ }
+ }
+ }
+ }
+}
+
+//
+// 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, nsHostRecord::TRRSkippedReason aReason) {
+ MutexAutoLock lock(mLock);
+ 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);
+
+ bool trrResult = newRRSet && newRRSet->IsTRR();
+ if (NS_FAILED(status)) {
+ newRRSet = nullptr;
+ }
+
+ if (addrRec->LoadResolveAgain() && (status != NS_ERROR_ABORT) && !trrResult) {
+ 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 trr=%d stillResolving=%d\n",
+ addrRec->host.get(), aNewRRSet, (unsigned int)status,
+ aNewRRSet ? aNewRRSet->IsTRR() : 0, int(addrRec->mResolving)));
+
+ if (trrResult) {
+ 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->mTRRUsed = false;
+ } else {
+ addrRec->mTRRUsed = true;
+ }
+
+ if (NS_FAILED(status)) {
+ if (aReason != nsHostRecord::TRR_UNSET) {
+ addrRec->RecordReason(aReason);
+ } else {
+ // Unknown failed reason.
+ addrRec->RecordReason(nsHostRecord::TRR_FAILED);
+ }
+ } else {
+ addrRec->mTRRSuccess++;
+ addrRec->RecordReason(nsHostRecord::TRR_OK);
+ }
+
+ if (NS_FAILED(status) &&
+ addrRec->mEffectiveTRRMode == nsIRequest::TRR_FIRST_MODE &&
+ status != NS_ERROR_DEFINITIVE_UNKNOWN_HOST) {
+ MOZ_ASSERT(!addrRec->mResolving);
+ NativeLookup(addrRec);
+ 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;
+ }
+ }
+
+ // This should always be cleared when a request is completed.
+ addrRec->StoreNative(false);
+
+ // 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);
+ }
+
+ if (!addrRec->mResolving && !mShutdown) {
+ {
+ auto trrQuery = addrRec->mTRRQuery.Lock();
+ if (trrQuery.ref()) {
+ addrRec->mTrrDuration = trrQuery.ref()->Duration();
+ }
+ trrQuery.ref() = nullptr;
+ }
+ addrRec->ResolveComplete();
+
+ AddToEvictionQ(rec);
+ }
+
+#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 & ~RES_PRIORITY_MEDIUM) | RES_PRIORITY_LOW;
+ DebugOnly<nsresult> rv = NativeLookup(rec);
+ 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, uint32_t aTtl, bool pb) {
+ MutexAutoLock lock(mLock);
+ 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--;
+
+ {
+ auto lock = rec->mTRRQuery.Lock();
+ lock.ref() = nullptr;
+ }
+
+ uint32_t duration = static_cast<uint32_t>(
+ (TimeStamp::Now() - typeRec->mStart).ToMilliseconds());
+
+ 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(), NEGATIVE_RECORD_LIFETIME, 0);
+ MOZ_ASSERT(aResult.is<TypeRecordEmpty>());
+ status = NS_ERROR_UNKNOWN_HOST;
+ typeRec->negative = true;
+ Telemetry::Accumulate(Telemetry::DNS_BY_TYPE_FAILED_LOOKUP_TIME, duration);
+ } 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;
+ Telemetry::Accumulate(Telemetry::DNS_BY_TYPE_SUCCEEDED_LOOKUP_TIME,
+ duration);
+ }
+
+ 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);
+ }
+
+ AddToEvictionQ(rec);
+ return LOOKUP_OK;
+}
+
+void nsHostResolver::CancelAsyncRequest(
+ const nsACString& host, const nsACString& aTrrServer, uint16_t aType,
+ const OriginAttributes& aOriginAttributes, uint16_t 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) {
+ nsHostRecord* recPtr = nullptr;
+
+ for (const RefPtr<nsResolveHostCallback>& c : rec->mCallbacks) {
+ if (c->EqualsAsyncListener(aListener)) {
+ c->remove();
+ recPtr = rec;
+ c->OnResolveHostComplete(this, recPtr, status);
+ break;
+ }
+ }
+
+ // If there are no more callbacks, remove the hash table entry
+ if (recPtr && recPtr->mCallbacks.isEmpty()) {
+ mRecordDB.Remove(*static_cast<nsHostKey*>(recPtr));
+ // If record is on a Queue, remove it and then deref it
+ if (recPtr->isInList()) {
+ recPtr->remove();
+ }
+ }
+ }
+}
+
+size_t nsHostResolver::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const {
+ MutexAutoLock lock(mLock);
+
+ size_t n = mallocSizeOf(this);
+
+ n += mRecordDB.ShallowSizeOfExcludingThis(mallocSizeOf);
+ for (auto iter = mRecordDB.ConstIter(); !iter.Done(); iter.Next()) {
+ auto* entry = iter.UserData();
+ 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<AddrHostRecord> rec;
+ RefPtr<AddrInfo> ai;
+
+ do {
+ if (!rec) {
+ RefPtr<AddrHostRecord> 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);
+ 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
+
+ { // obtain lock to check shutdown and manage inter-module telemetry
+ MutexAutoLock lock(mLock);
+
+ if (!mShutdown) {
+ TimeDuration elapsed = TimeStamp::Now() - startTime;
+ uint32_t millis = static_cast<uint32_t>(elapsed.ToMilliseconds());
+
+ if (NS_SUCCEEDED(status)) {
+ Telemetry::HistogramID histogramID;
+ if (!rec->addr_info_gencnt) {
+ // Time for initial lookup.
+ histogramID = Telemetry::DNS_LOOKUP_TIME;
+ } else if (!getTtl) {
+ // Time for renewal; categorized by expiration strategy.
+ histogramID = Telemetry::DNS_RENEWAL_TIME;
+ } else {
+ // Time to get TTL; categorized by expiration strategy.
+ histogramID = Telemetry::DNS_RENEWAL_TIME_FOR_TTL;
+ }
+ Telemetry::Accumulate(histogramID, millis);
+ } else {
+ Telemetry::Accumulate(Telemetry::DNS_FAILED_LOOKUP_TIME, millis);
+ }
+ }
+ }
+
+ 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->mTRRTRRSkippedReason)) {
+ // 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);
+
+ 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 (auto iter = mRecordDB.Iter(); !iter.Done(); iter.Next()) {
+ // We don't pay attention to address literals, only resolved domains.
+ // Also require a host.
+ nsHostRecord* rec = iter.UserData();
+ 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 = iter.Key().originSuffix;
+
+ 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..798d0d0065
--- /dev/null
+++ b/netwerk/dns/nsHostResolver.h
@@ -0,0 +1,654 @@
+/* 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 "mozilla/Mutex.h"
+#include "nsISupportsImpl.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSService.h"
+#include "nsTArray.h"
+#include "GetAddrInfo.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/DashboardTypes.h"
+#include "mozilla/AtomicBitfields.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "nsRefPtrHashtable.h"
+#include "nsIThreadPool.h"
+#include "mozilla/net/NetworkConnectivityService.h"
+#include "nsIDNSByTypeRecord.h"
+#include "mozilla/net/DNSByTypeRecord.h"
+#include "mozilla/Maybe.h"
+
+class nsHostResolver;
+class nsResolveHostCallback;
+namespace mozilla {
+namespace net {
+class TRR;
+class TRRQuery;
+enum ResolverMode {
+ 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
+};
+} // namespace net
+} // namespace mozilla
+
+#define TRR_DISABLED(x) (((x) == MODE_NATIVEONLY) || ((x) == MODE_TRROFF))
+
+extern mozilla::Atomic<bool, mozilla::Relaxed> gNativeIsLocalhost;
+
+#define MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY 3
+#define MAX_RESOLVER_THREADS_FOR_HIGH_PRIORITY 5
+#define MAX_NON_PRIORITY_REQUESTS 150
+
+#define MAX_RESOLVER_THREADS \
+ (MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY + \
+ MAX_RESOLVER_THREADS_FOR_HIGH_PRIORITY)
+
+struct nsHostKey {
+ const nsCString host;
+ const nsCString mTrrServer;
+ uint16_t type;
+ uint16_t flags;
+ uint16_t af;
+ bool pb;
+ const nsCString originSuffix;
+ explicit nsHostKey(const nsACString& host, const nsACString& aTrrServer,
+ uint16_t type, uint16_t 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 {
+ 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();
+
+ // IMPORTANT: when adding new values, always add them to the end, otherwise
+ // it will mess up telemetry.
+ enum TRRSkippedReason : uint32_t {
+ 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
+ };
+
+ // Records the first reason that caused TRR to be skipped or to fail.
+ void RecordReason(TRRSkippedReason reason) {
+ if (mTRRTRRSkippedReason == TRR_UNSET) {
+ mTRRTRRSkippedReason = reason;
+ }
+ }
+
+ protected:
+ friend class nsHostResolver;
+ 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,
+ uint16_t queryFlags = 0) const;
+
+ enum DnsPriority {
+ DNS_PRIORITY_LOW,
+ DNS_PRIORITY_MEDIUM,
+ DNS_PRIORITY_HIGH,
+ };
+ static DnsPriority GetPriority(uint16_t aFlags);
+
+ virtual void Cancel();
+ virtual bool HasUsableResultInternal() const { return false; }
+
+ mozilla::LinkedList<RefPtr<nsResolveHostCallback>> mCallbacks;
+
+ bool IsAddrRecord() const {
+ return type == nsIDNSService::RESOLVE_TYPE_DEFAULT;
+ }
+
+ // 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;
+
+ // 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.
+ nsIRequest::TRRMode mEffectiveTRRMode;
+
+ TRRSkippedReason mTRRTRRSkippedReason = TRR_UNSET;
+ TRRSkippedReason mTRRAFailReason = TRR_UNSET;
+ TRRSkippedReason mTRRAAAAFailReason = TRR_UNSET;
+
+ mozilla::DataMutex<RefPtr<mozilla::net::TRRQuery>> mTRRQuery;
+
+ mozilla::Atomic<int32_t>
+ mResolving; // counter of outstanding resolving calls
+
+ uint8_t negative : 1; /* 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. */
+ uint8_t mDoomed : 1; // explicitly expired
+};
+
+// b020e996-f6ab-45e5-9bf5-1da71dd0053a
+#define ADDRHOSTRECORD_IID \
+ { \
+ 0xb020e996, 0xf6ab, 0x45e5, { \
+ 0x9b, 0xf5, 0x1d, 0xa7, 0x1d, 0xd0, 0x05, 0x3a \
+ } \
+ }
+
+class AddrHostRecord final : public nsHostRecord {
+ typedef mozilla::Mutex Mutex;
+
+ 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;
+ int addr_info_gencnt; /* generation count of |addr_info| */
+ 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; }
+
+ private:
+ friend class nsHostResolver;
+ 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 override;
+
+ bool RemoveOrRefresh(bool aTrrToo); // Mark records currently being resolved
+ // as needed to resolve again.
+
+ void ResolveComplete();
+
+ enum DnsPriority {
+ DNS_PRIORITY_LOW,
+ DNS_PRIORITY_MEDIUM,
+ DNS_PRIORITY_HIGH,
+ };
+ static DnsPriority GetPriority(uint16_t aFlags);
+
+ // true if pending and on the queue (not yet given to getaddrinfo())
+ bool onQueue() { return LoadNative() && isInList(); }
+
+ // When the lookups of this record started and their durations
+ mozilla::TimeStamp mTrrStart;
+ mozilla::TimeStamp mNativeStart;
+ mozilla::TimeDuration mTrrDuration;
+ mozilla::TimeDuration mNativeDuration;
+
+ mozilla::Atomic<bool> mTRRUsed; // TRR was used on this record
+ uint8_t mTRRSuccess; // number of successful TRR responses
+ uint8_t mNativeSuccess; // number of native lookup responses
+
+ // 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
+
+ // 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::TRRQuery;
+
+ explicit TypeHostRecord(const nsHostKey& key);
+ ~TypeHostRecord();
+
+ // Checks if the record is usable (not expired and has a value)
+ bool HasUsableResultInternal() const override;
+
+ bool HasUsableResult();
+
+ mozilla::net::TypeRecordResultType mResults = AsVariant(mozilla::Nothing());
+ mozilla::Mutex mResultsLock;
+
+ // When the lookups of this record started (for telemetry).
+ mozilla::TimeStamp mStart;
+ bool mAllRecordsExcluded = false;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(TypeHostRecord, TYPEHOSTRECORD_IID)
+
+/**
+ * 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;
+};
+
+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,
+ nsHostRecord::TRRSkippedReason aReason) = 0;
+ virtual LookupStatus CompleteLookupByType(
+ nsHostRecord*, nsresult, mozilla::net::TypeRecordResultType& aResult,
+ uint32_t aTtl, bool pb) = 0;
+ virtual nsresult GetHostRecord(const nsACString& host,
+ const nsACString& aTrrServer, uint16_t type,
+ uint16_t 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,
+ uint16_t type,
+ const mozilla::OriginAttributes& aOriginAttributes,
+ uint16_t flags, uint16_t af,
+ nsResolveHostCallback* callback);
+
+ nsHostRecord* InitRecord(const nsHostKey& key);
+ mozilla::net::NetworkConnectivityService* GetNCS() { return mNCS; }
+
+ /**
+ * 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,
+ uint16_t 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,
+ uint16_t 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 = nsIDNSService::RESOLVE_PRIORITY_MEDIUM,
+ RES_PRIORITY_LOW = nsIDNSService::RESOLVE_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,
+ nsHostRecord::TRRSkippedReason aReason) override;
+ LookupStatus CompleteLookupByType(nsHostRecord*, nsresult,
+ mozilla::net::TypeRecordResultType& aResult,
+ uint32_t aTtl, bool pb) override;
+ nsresult GetHostRecord(const nsACString& host, const nsACString& trrServer,
+ uint16_t type, uint16_t flags, uint16_t af, bool pb,
+ const nsCString& originSuffix,
+ nsHostRecord** result) override;
+ nsresult TrrLookup_unlocked(nsHostRecord*,
+ mozilla::net::TRR* pushedTRR = nullptr) override;
+ static mozilla::net::ResolverMode Mode();
+
+ virtual void MaybeRenewHostRecord(nsHostRecord* aRec) override;
+
+ private:
+ explicit nsHostResolver(uint32_t maxCacheEntries,
+ uint32_t defaultCacheEntryLifetime,
+ uint32_t defaultGracePeriod);
+ virtual ~nsHostResolver();
+
+ nsresult Init();
+ // In debug builds it asserts that the element is in the list.
+ void AssertOnQ(nsHostRecord*, mozilla::LinkedList<RefPtr<nsHostRecord>>&);
+ static void ComputeEffectiveTRRMode(nsHostRecord* aRec);
+ nsresult NativeLookup(nsHostRecord*);
+ nsresult TrrLookup(nsHostRecord*, mozilla::net::TRR* pushedTRR = nullptr);
+
+ // Kick-off a name resolve operation, using native resolver and/or TRR
+ nsresult NameLookup(nsHostRecord*);
+ bool GetHostToLookup(AddrHostRecord** result);
+ void MaybeRenewHostRecordLocked(nsHostRecord* aRec);
+
+ // Removes the first element from the list and returns it AddRef-ed in aResult
+ // Should not be called for an empty linked list.
+ void DeQueue(mozilla::LinkedList<RefPtr<nsHostRecord>>& aQ,
+ AddrHostRecord** aResult);
+ // 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);
+
+ /**
+ * 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);
+
+ void AddToEvictionQ(nsHostRecord* rec);
+
+ void ThreadFunc();
+
+ 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;
+ uint32_t mDefaultCacheLifetime; // granularity seconds
+ uint32_t mDefaultGracePeriod; // granularity seconds
+ mutable Mutex mLock; // mutable so SizeOfIncludingThis can be const
+ CondVar mIdleTaskCV;
+ nsRefPtrHashtable<nsGenericHashKey<nsHostKey>, nsHostRecord> mRecordDB;
+ mozilla::LinkedList<RefPtr<nsHostRecord>> mHighQ;
+ mozilla::LinkedList<RefPtr<nsHostRecord>> mMediumQ;
+ mozilla::LinkedList<RefPtr<nsHostRecord>> mLowQ;
+ mozilla::LinkedList<RefPtr<nsHostRecord>> mEvictionQ;
+ uint32_t mEvictionQSize;
+ PRTime mCreationTime;
+ mozilla::TimeDuration mLongIdleTimeout;
+ mozilla::TimeDuration mShortIdleTimeout;
+
+ RefPtr<nsIThreadPool> mResolverThreads;
+ RefPtr<mozilla::net::NetworkConnectivityService> mNCS;
+
+ mozilla::Atomic<bool> mShutdown;
+ mozilla::Atomic<uint32_t> mNumIdleTasks;
+ mozilla::Atomic<uint32_t> mActiveTaskCount;
+ mozilla::Atomic<uint32_t> mActiveAnyThreadCount;
+ mozilla::Atomic<uint32_t> mPendingCount;
+
+ // 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/nsIDNSByTypeRecord.idl b/netwerk/dns/nsIDNSByTypeRecord.idl
new file mode 100644
index 0000000000..e311b34a1b
--- /dev/null
+++ b/netwerk/dns/nsIDNSByTypeRecord.idl
@@ -0,0 +1,126 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "mozilla/Tuple.h"
+#include "nsTArrayForwardDeclare.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<mozilla::Tuple<nsCString, bool>>);
+
+[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 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;
+
+ 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..4ac3156fb0
--- /dev/null
+++ b/netwerk/dns/nsIDNSRecord.idl
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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;
+}
+}
+#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();
+
+ /**
+ * 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 unsigned long effectiveTRRMode;
+};
diff --git a/netwerk/dns/nsIDNSResolverInfo.idl b/netwerk/dns/nsIDNSResolverInfo.idl
new file mode 100644
index 0000000000..546d2d789c
--- /dev/null
+++ b/netwerk/dns/nsIDNSResolverInfo.idl
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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 nsIDNSResolverInfo : nsISupports
+{
+ readonly attribute ACString URL;
+};
diff --git a/netwerk/dns/nsIDNSService.idl b/netwerk/dns/nsIDNSService.idl
new file mode 100644
index 0000000000..c999385bf7
--- /dev/null
+++ b/netwerk/dns/nsIDNSService.idl
@@ -0,0 +1,341 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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"
+
+%{ C++
+#include "mozilla/BasePrincipal.h"
+%}
+
+interface nsICancelable;
+interface nsIEventTarget;
+interface nsIDNSRecord;
+interface nsIDNSListener;
+interface nsIDNSResolverInfo;
+
+%{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,
+ };
+
+ /**
+ * 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 aResolver
+ * a resolverInfo object that holds information about the resolver
+ * to be used such as TRR URL. 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 unsigned long aFlags,
+ in nsIDNSResolverInfo aResolver,
+ in nsIDNSListener aListener,
+ in nsIEventTarget aListenerTarget,
+ [optional] in jsval aOriginAttributes);
+
+ [notxpcom]
+ nsresult asyncResolveNative(in AUTF8String aHostName,
+ in nsIDNSService_ResolveType aType,
+ in unsigned long aFlags,
+ in nsIDNSResolverInfo aResolver,
+ in nsIDNSListener aListener,
+ in nsIEventTarget aListenerTarget,
+ in OriginAttributes aOriginAttributes,
+ out nsICancelable aResult);
+
+ /**
+ * Returns a new resolverInfo object containing the URL we pass to it.
+ */
+ nsIDNSResolverInfo newTRRResolverInfo(in AUTF8String aTrrURL);
+
+ /**
+ * 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 aResolver
+ * a resolverInfo object that holds information about the resolver
+ * to be used such as TRR URL. 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 unsigned long aFlags,
+ in nsIDNSResolverInfo aResolver,
+ in nsIDNSListener aListener,
+ in nsresult aReason,
+ [optional] in jsval aOriginAttributes);
+
+ [notxpcom]
+ nsresult cancelAsyncResolveNative(in AUTF8String aHostName,
+ in nsIDNSService_ResolveType aType,
+ in unsigned long aFlags,
+ in nsIDNSResolverInfo 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 unsigned long aFlags,
+ [optional] in jsval aOriginAttributes);
+
+ [notxpcom]
+ nsresult resolveNative(in AUTF8String aHostName,
+ in unsigned long 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);
+
+ /**
+ * 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 unsigned long currentTrrMode;
+
+ /**
+ * @return the hostname of the operating system.
+ */
+ readonly attribute AUTF8String myHostName;
+
+ /*************************************************************************
+ * Listed below are the various flags that may be OR'd together to form
+ * the aFlags parameter passed to asyncResolve() and resolve().
+ */
+
+ /**
+ * if set, this flag suppresses the internal DNS lookup cache.
+ */
+ const unsigned long RESOLVE_BYPASS_CACHE = (1 << 0);
+
+ /**
+ * if set, the canonical name of the specified host will be queried.
+ */
+ const unsigned long RESOLVE_CANONICAL_NAME = (1 << 1);
+
+ /**
+ * if set, the query is given lower priority. Medium takes precedence
+ * if both are used.
+ */
+ const unsigned long RESOLVE_PRIORITY_MEDIUM = (1 << 2);
+ const unsigned long RESOLVE_PRIORITY_LOW = (1 << 3);
+
+ /**
+ * if set, indicates request is speculative. Speculative requests
+ * return errors if prefetching is disabled by configuration.
+ */
+ const unsigned long RESOLVE_SPECULATE = (1 << 4);
+
+ /**
+ * If set, only IPv4 addresses will be returned from resolve/asyncResolve.
+ */
+ const unsigned long RESOLVE_DISABLE_IPV6 = (1 << 5);
+
+ /**
+ * If set, only literals and cached entries will be returned from resolve/
+ * asyncResolve.
+ */
+ const unsigned long RESOLVE_OFFLINE = (1 << 6);
+
+ /**
+ * If set, only IPv6 addresses will be returned from resolve/asyncResolve.
+ */
+ const unsigned long RESOLVE_DISABLE_IPV4 = (1 << 7);
+
+ /**
+ * If set, allow name collision results (127.0.53.53) which are normally filtered.
+ */
+ const unsigned long RESOLVE_ALLOW_NAME_COLLISION = (1 << 8);
+
+ /**
+ * If set, do not use TRR for resolving the host name.
+ */
+ const unsigned long 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.
+ */
+ const unsigned long RESOLVE_REFRESH_CACHE = (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 RESOLVE_TRR_MODE_MASK = (1 << 11) | (1 << 12);
+ const unsigned long RESOLVE_TRR_DISABLED_MODE = (1 << 11);
+%{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 & RESOLVE_TRR_MODE_MASK) >> 11);
+ }
+%}
+
+ /**
+ * Force resolution even when SOCKS proxy with DNS forwarding is configured.
+ * Only to be used for the proxy host resolution.
+ */
+ const unsigned long RESOLVE_IGNORE_SOCKS_DNS = 1 << 13;
+
+ /**
+ * If set, only cached IP hint addresses will be returned from
+ * resolve/asyncResolve.
+ */
+ const unsigned long RESOLVE_IP_HINT = 1 << 14;
+};
+
+%{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"
+
+%}
+
diff --git a/netwerk/dns/nsIDNService.cpp b/netwerk/dns/nsIDNService.cpp
new file mode 100644
index 0000000000..2932a28444
--- /dev/null
+++ b/netwerk/dns/nsIDNService.cpp
@@ -0,0 +1,925 @@
+/* -*- 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/Preferences.h"
+#include "nsIDNService.h"
+#include "nsReadableUtils.h"
+#include "nsCRT.h"
+#include "nsServiceManagerUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsUnicodeProperties.h"
+#include "nsUnicodeScriptCodes.h"
+#include "harfbuzz/hb.h"
+#include "punycode.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Utf8.h"
+
+// 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 bool kIDNA2008_TransitionalProcessing = false;
+
+#include "ICUUtils.h"
+#include "unicode/uscript.h"
+
+using namespace mozilla;
+using namespace mozilla::unicode;
+using namespace mozilla::net;
+using mozilla::Preferences;
+
+//-----------------------------------------------------------------------------
+// RFC 1034 - 3.1. Name space specifications and terminology
+static const uint32_t kMaxDNSNodeLen = 63;
+// RFC 3490 - 5. ACE prefix
+static const char kACEPrefix[] = "xn--";
+#define kACEPrefixLen 4
+
+//-----------------------------------------------------------------------------
+
+#define NS_NET_PREF_EXTRAALLOWED "network.IDN.extra_allowed_chars"
+#define NS_NET_PREF_EXTRABLOCKED "network.IDN.extra_blocked_chars"
+#define NS_NET_PREF_SHOWPUNYCODE "network.IDN_show_punycode"
+#define NS_NET_PREF_IDNWHITELIST "network.IDN.whitelist."
+#define NS_NET_PREF_IDNUSEWHITELIST "network.IDN.use_whitelist"
+#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, nsISupportsWeakReference)
+
+static const char* gCallbackPrefs[] = {
+ NS_NET_PREF_EXTRAALLOWED, NS_NET_PREF_EXTRABLOCKED,
+ NS_NET_PREF_SHOWPUNYCODE, NS_NET_PREF_IDNRESTRICTION,
+ NS_NET_PREF_IDNUSEWHITELIST, nullptr,
+};
+
+nsresult nsIDNService::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MutexAutoLock lock(mLock);
+
+ nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ prefs->GetBranch(NS_NET_PREF_IDNWHITELIST,
+ getter_AddRefs(mIDNWhitelistPrefBranch));
+ }
+
+ Preferences::RegisterPrefixCallbacks(PrefChanged, gCallbackPrefs, this);
+ prefsChanged(nullptr);
+ InitializeBlocklist(mIDNBlocklist);
+
+ return NS_OK;
+}
+
+void nsIDNService::prefsChanged(const char* pref) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mLock.AssertCurrentThreadOwns();
+
+ if (pref && nsLiteralCString(NS_NET_PREF_EXTRAALLOWED).Equals(pref)) {
+ InitializeBlocklist(mIDNBlocklist);
+ }
+ if (pref && nsLiteralCString(NS_NET_PREF_EXTRABLOCKED).Equals(pref)) {
+ InitializeBlocklist(mIDNBlocklist);
+ }
+ if (!pref || nsLiteralCString(NS_NET_PREF_SHOWPUNYCODE).Equals(pref)) {
+ bool val;
+ if (NS_SUCCEEDED(Preferences::GetBool(NS_NET_PREF_SHOWPUNYCODE, &val))) {
+ mShowPunycode = val;
+ }
+ }
+ if (!pref || nsLiteralCString(NS_NET_PREF_IDNUSEWHITELIST).Equals(pref)) {
+ bool val;
+ if (NS_SUCCEEDED(Preferences::GetBool(NS_NET_PREF_IDNUSEWHITELIST, &val))) {
+ mIDNUseWhitelist = val;
+ }
+ }
+ 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()
+ : mLock("DNService pref value lock"),
+ mShowPunycode(false),
+ mRestrictionProfile(static_cast<restrictionProfile>(0)),
+ mIDNUseWhitelist(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ uint32_t IDNAOptions = UIDNA_CHECK_BIDI | UIDNA_CHECK_CONTEXTJ;
+ if (!kIDNA2008_TransitionalProcessing) {
+ IDNAOptions |= UIDNA_NONTRANSITIONAL_TO_UNICODE;
+ }
+ UErrorCode errorCode = U_ZERO_ERROR;
+ mIDNA = uidna_openUTS46(IDNAOptions, &errorCode);
+}
+
+nsIDNService::~nsIDNService() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Preferences::UnregisterPrefixCallbacks(PrefChanged, gCallbackPrefs, this);
+
+ uidna_close(mIDNA);
+}
+
+nsresult nsIDNService::IDNA2008ToUnicode(const nsACString& input,
+ nsAString& output) {
+ NS_ConvertUTF8toUTF16 inputStr(input);
+ UIDNAInfo info = UIDNA_INFO_INITIALIZER;
+ UErrorCode errorCode = U_ZERO_ERROR;
+ int32_t inLen = inputStr.Length();
+ int32_t outMaxLen = kMaxDNSNodeLen + 1;
+ UChar outputBuffer[kMaxDNSNodeLen + 1];
+
+ int32_t outLen =
+ uidna_labelToUnicode(mIDNA, (const UChar*)inputStr.get(), inLen,
+ outputBuffer, outMaxLen, &info, &errorCode);
+ if (info.errors != 0) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (U_SUCCESS(errorCode)) {
+ ICUUtils::AssignUCharArrayToString(outputBuffer, outLen, output);
+ }
+
+ nsresult rv = ICUUtils::UErrorToNsResult(errorCode);
+ if (rv == NS_ERROR_FAILURE) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+ return rv;
+}
+
+nsresult nsIDNService::IDNA2008StringPrep(const nsAString& input,
+ nsAString& output,
+ stringPrepFlag flag) {
+ UIDNAInfo info = UIDNA_INFO_INITIALIZER;
+ UErrorCode errorCode = U_ZERO_ERROR;
+ int32_t inLen = input.Length();
+ int32_t outMaxLen = kMaxDNSNodeLen + 1;
+ UChar outputBuffer[kMaxDNSNodeLen + 1];
+
+ int32_t outLen =
+ uidna_labelToUnicode(mIDNA, (const UChar*)PromiseFlatString(input).get(),
+ inLen, outputBuffer, outMaxLen, &info, &errorCode);
+ nsresult rv = ICUUtils::UErrorToNsResult(errorCode);
+ if (rv == NS_ERROR_FAILURE) {
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 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.errors & UIDNA_ERROR_PUNYCODE) && outLen > 0 &&
+ outputBuffer[outLen - 1] == 0xfffd) {
+ --outLen;
+ }
+ ICUUtils::AssignUCharArrayToString(outputBuffer, outLen, output);
+
+ if (flag == eStringPrepIgnoreErrors) {
+ return NS_OK;
+ }
+
+ if (info.errors != 0) {
+ if (flag == eStringPrepForDNS) {
+ output.Truncate();
+ }
+ 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) {
+ const char* data = input.BeginReading();
+ uint32_t dataLen = input.Length();
+
+ // 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"
+
+ const char* p = PL_strncasestr(data, kACEPrefix, dataLen);
+
+ *_retval = p && (p == data || *(p - 1) == '.');
+ 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;
+}
+
+namespace {
+
+class MOZ_STACK_CLASS MutexSettableAutoUnlock final {
+ Mutex* mMutex;
+
+ public:
+ MutexSettableAutoUnlock() : mMutex(nullptr) {}
+
+ void Acquire(mozilla::Mutex& aMutex) {
+ MOZ_ASSERT(!mMutex);
+ mMutex = &aMutex;
+ mMutex->Lock();
+ }
+
+ ~MutexSettableAutoUnlock() {
+ if (mMutex) {
+ mMutex->Unlock();
+ }
+ }
+};
+
+} // anonymous namespace
+
+NS_IMETHODIMP nsIDNService::ConvertToDisplayIDN(const nsACString& input,
+ bool* _isASCII,
+ nsACString& _retval) {
+ MutexSettableAutoUnlock lock;
+ if (!NS_IsMainThread()) {
+ lock.Acquire(mLock);
+ }
+
+ // 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 && !mShowPunycode) {
+ // ACEtoUTF8() can't fail, but might return the original ACE string
+ nsAutoCString temp(_retval);
+ // If the domain is in the whitelist, return the host in UTF-8.
+ // Otherwise convert from ACE to UTF8 only those labels which are
+ // considered safe for display
+ ACEtoUTF8(
+ temp, _retval,
+ isInWhitelist(temp) ? eStringPrepIgnoreErrors : 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 (mShowPunycode &&
+ 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 && !isInWhitelist(_retval)) {
+ // 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;
+}
+
+//-----------------------------------------------------------------------------
+
+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[kMaxDNSNodeLen + 1];
+ uint32_t ucs4Len = 0u;
+ nsresult rv = utf16ToUcs4(in, ucs4Buf, kMaxDNSNodeLen, &ucs4Len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // need maximum 20 bits to encode 16 bit Unicode character
+ // (include null terminator)
+ const uint32_t kEncodedBufSize = kMaxDNSNodeLen * 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 (in.Length() > kMaxDNSNodeLen) {
+ NS_WARNING("IDN node too large");
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (IsAscii(in)) {
+ LossyCopyUTF16toASCII(in, out);
+ 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;
+ }
+
+ rv = punycode(strPrep, out);
+ // Check that the encoded output isn't larger than the maximum length
+ // of a DNS node per RFC 1034.
+ // This test isn't necessary in the code paths above where the input
+ // is ASCII (since the output will be the same length as the input) or
+ // where we convert to UTF-8 (since the output is only used for
+ // display in the UI and not passed to DNS and can legitimately be
+ // longer than the limit).
+ if (out.Length() > kMaxDNSNodeLen) {
+ NS_WARNING("IDN node too large");
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ return rv;
+}
+
+// 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;
+}
+
+bool nsIDNService::isInWhitelist(const nsACString& host) {
+ if (!NS_IsMainThread()) {
+ mLock.AssertCurrentThreadOwns();
+ }
+
+ if (mIDNUseWhitelist && mIDNWhitelistPrefBranch) {
+ nsAutoCString tld(host);
+ // make sure the host is ACE for lookup and check that there are no
+ // unassigned codepoints
+ if (!IsAscii(tld) && NS_FAILED(UTF8toACE(tld, tld, eStringPrepForDNS))) {
+ return false;
+ }
+
+ // truncate trailing dots first
+ tld.Trim(".");
+ int32_t pos = tld.RFind(".");
+ if (pos == kNotFound) {
+ return false;
+ }
+
+ tld.Cut(0, pos + 1);
+
+ bool safe;
+ if (NS_SUCCEEDED(mIDNWhitelistPrefBranch->GetBoolPref(tld.get(), &safe))) {
+ return safe;
+ }
+ }
+
+ return false;
+}
+
+bool nsIDNService::isLabelSafe(const nsAString& label) {
+ if (!NS_IsMainThread()) {
+ mLock.AssertCurrentThreadOwns();
+ }
+
+ 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
+
+ int32_t savedScript = -1;
+
+ 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 = GetScriptCode(ch);
+ if (script != Script::COMMON && script != Script::INHERITED &&
+ script != lastScript) {
+ if (illegalScriptCombo(script, savedScript)) {
+ return false;
+ }
+ }
+
+ // Check for mixed numbering systems
+ auto genCat = GetGeneralCategory(ch);
+ if (genCat == HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER) {
+ uint32_t zeroCharacter = ch - 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) {
+ const size_t kMaxScripts = 32; // more than ample for current values
+ // of ScriptExtensions property
+ UScriptCode scripts[kMaxScripts];
+ UErrorCode errorCode = U_ZERO_ERROR;
+ int nScripts =
+ uscript_getScriptExtensions(ch, scripts, kMaxScripts, &errorCode);
+ MOZ_ASSERT(U_SUCCESS(errorCode), "uscript_getScriptExtensions failed");
+ if (U_FAILURE(errorCode)) {
+ return false;
+ }
+ // nScripts will always be >= 1, because even for undefined characters
+ // uscript_getScriptExtensions 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 const Script scriptTable[] = {
+ Script::BOPOMOFO, Script::CYRILLIC, Script::GREEK, Script::HANGUL,
+ Script::HAN, Script::HIRAGANA, Script::KATAKANA, Script::LATIN};
+
+#define BOPO 0
+#define CYRL 1
+#define GREK 2
+#define HANG 3
+#define HANI 4
+#define HIRA 5
+#define KATA 6
+#define LATN 7
+#define OTHR 8
+#define JPAN 9 // Latin + Han + Hiragana + Katakana
+#define CHNA 10 // Latin + Han + Bopomofo
+#define KORE 11 // Latin + Han + Hangul
+#define HNLT 12 // Latin + Han (could be any of the above combinations)
+#define FAIL 13
+
+static inline int32_t findScriptIndex(Script aScript) {
+ int32_t tableLength = mozilla::ArrayLength(scriptTable);
+ for (int32_t index = 0; index < tableLength; ++index) {
+ if (aScript == scriptTable[index]) {
+ return index;
+ }
+ }
+ return OTHR;
+}
+
+static const int32_t 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, int32_t& savedScript) {
+ if (!NS_IsMainThread()) {
+ mLock.AssertCurrentThreadOwns();
+ }
+
+ if (savedScript == -1) {
+ 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);
+}
+
+#undef BOPO
+#undef CYRL
+#undef GREK
+#undef HANG
+#undef HANI
+#undef HIRA
+#undef KATA
+#undef LATN
+#undef OTHR
+#undef JPAN
+#undef CHNA
+#undef KORE
+#undef HNLT
+#undef FAIL
diff --git a/netwerk/dns/nsIDNService.h b/netwerk/dns/nsIDNService.h
new file mode 100644
index 0000000000..6fef8e7670
--- /dev/null
+++ b/netwerk/dns/nsIDNService.h
@@ -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/. */
+
+#ifndef nsIDNService_h__
+#define nsIDNService_h__
+
+#include "nsIIDNService.h"
+#include "nsCOMPtr.h"
+#include "nsUnicodeScriptCodes.h"
+#include "nsWeakReference.h"
+
+#include "unicode/uidna.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/net/IDNBlocklistUtils.h"
+
+#include "nsString.h"
+
+class nsIPrefBranch;
+
+//-----------------------------------------------------------------------------
+// nsIDNService
+//-----------------------------------------------------------------------------
+
+class nsIDNService final : public nsIIDNService,
+ public nsSupportsWeakReference {
+ 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);
+
+ bool isInWhitelist(const nsACString& host);
+ void prefsChanged(const char* pref);
+
+ static void PrefChanged(const char* aPref, void* aSelf) {
+ auto* self = static_cast<nsIDNService*>(aSelf);
+ mozilla::MutexAutoLock lock(self->mLock);
+ 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);
+
+ /**
+ * 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::unicode::Script script,
+ int32_t& savedScript);
+
+ /**
+ * 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);
+
+ UIDNA* mIDNA;
+
+ // We use this mutex to guard access to:
+ // |mIDNBlocklist|, |mShowPunycode|, |mRestrictionProfile|,
+ // |mIDNUseWhitelist|.
+ //
+ // These members can only be updated on the main thread and
+ // read on any thread. Therefore, acquiring the mutex is required
+ // only for threads other than the main thread.
+ mozilla::Mutex mLock;
+
+ // guarded by mLock
+ nsTArray<mozilla::net::BlocklistRange> mIDNBlocklist;
+
+ /**
+ * Flag set by the pref network.IDN_show_punycode. When it is true,
+ * IDNs containing non-ASCII characters are always displayed to the
+ * user in punycode
+ *
+ * guarded by mLock
+ */
+ bool mShowPunycode;
+
+ /**
+ * 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;
+ // guarded by mLock;
+ nsCOMPtr<nsIPrefBranch> mIDNWhitelistPrefBranch;
+ // guarded by mLock
+ bool mIDNUseWhitelist;
+};
+
+#endif // nsIDNService_h__
diff --git a/netwerk/dns/nsIEffectiveTLDService.idl b/netwerk/dns/nsIEffectiveTLDService.idl
new file mode 100644
index 0000000000..32addd31fe
--- /dev/null
+++ b/netwerk/dns/nsIEffectiveTLDService.idl
@@ -0,0 +1,158 @@
+/* -*- 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);
+
+ /**
+ * 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..874328fabc
--- /dev/null
+++ b/netwerk/dns/nsINativeDNSResolverOverride.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"
+
+[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);
+
+ /**
+ * 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/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..53d0bf526d
--- /dev/null
+++ b/netwerk/dns/prepare_tlds.py
@@ -0,0 +1,152 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 imp
+import os
+import re
+import sys
+from make_dafsa import words_to_cxx, words_to_bin
+
+"""
+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")
+ entries = []
+ 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..6e0a9400cd
--- /dev/null
+++ b/netwerk/dns/tests/unit/test_PublicSuffixList.js
@@ -0,0 +1,176 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { PublicSuffixList } = ChromeUtils.import(
+ "resource://gre/modules/netwerk-dns/PublicSuffixList.jsm"
+);
+const { TestUtils } = ChromeUtils.import(
+ "resource://testing-common/TestUtils.jsm"
+);
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+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");
+});
+
+/**
+ * downloadCalled is used by mockDownload() and resetMockDownload()
+ * to keep track weather CLIENT.attachments.download is called or not
+ * downloadBackup will help restore CLIENT.attachments.download to original definition
+ * notifyUpdateBackup will help restore PublicSuffixList.notifyUpdate to original definition
+ */
+let downloadCalled = false;
+const downloadBackup = CLIENT.attachments.download;
+
+// returns a fake fileURI and sends a signal with filePath and no nsifile
+const mockDownload = () => {
+ downloadCalled = false;
+ CLIENT.attachments.download = async rec => {
+ downloadCalled = true;
+ return `file://${mockedFilePath}`; // Create a fake file URI
+ };
+};
+
+// resetMockDownload() must be run at the end of the test that uses mockDownload()
+const resetMockDownload = () => {
+ CLIENT.attachments.download = downloadBackup;
+};
+
+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: {},
+ });
+
+ mockDownload();
+
+ 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
+ resetMockDownload();
+});
+
+add_task(async () => {
+ info("File path sent when record updated.");
+
+ mockDownload();
+
+ 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."
+ );
+ resetMockDownload();
+});
+
+add_task(async () => {
+ info("Attachment downloaded when record created.");
+
+ mockDownload();
+
+ await PublicSuffixList.init();
+ await CLIENT.emit("sync", { data: PAYLOAD_CREATED_RECORDS });
+
+ Assert.equal(
+ downloadCalled,
+ true,
+ "Attachment downloaded when record created."
+ );
+ resetMockDownload();
+});
+
+add_task(async () => {
+ info("Attachment downloaded when record updated.");
+
+ mockDownload();
+
+ await PublicSuffixList.init();
+ await CLIENT.emit("sync", { data: PAYLOAD_UPDATED_RECORDS });
+
+ Assert.equal(
+ downloadCalled,
+ true,
+ "Attachment downloaded when record updated."
+ );
+ resetMockDownload();
+});
+
+add_task(async () => {
+ info("No download when more than one record is changed.");
+
+ mockDownload();
+
+ await PublicSuffixList.init();
+ await CLIENT.emit("sync", { data: PAYLOAD_UPDATED_AND_CREATED_RECORDS });
+
+ Assert.equal(
+ downloadCalled,
+ false,
+ "No download when more than one record is changed."
+ );
+ resetMockDownload();
+});
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..2758d2627f
--- /dev/null
+++ b/netwerk/dns/tests/unit/test_nsEffectiveTLDService_Reload_DAFSA.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+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..3d63fc29b4
--- /dev/null
+++ b/netwerk/dns/tests/unit/test_nsEffectiveTLDService_getKnownPublicSuffix.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests getPublicSuffix with the validate argument.
+ */
+
+"use strict";
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+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/xpcshell.ini b/netwerk/dns/tests/unit/xpcshell.ini
new file mode 100644
index 0000000000..1b72bc700a
--- /dev/null
+++ b/netwerk/dns/tests/unit/xpcshell.ini
@@ -0,0 +1,12 @@
+[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_getKnownPublicSuffix.js]
+[test_nsEffectiveTLDService_Reload_DAFSA.js]
diff --git a/netwerk/ipc/ChannelEventQueue.cpp b/netwerk/ipc/ChannelEventQueue.cpp
new file mode 100644
index 0000000000..0aa4cf71f4
--- /dev/null
+++ b/netwerk/ipc/ChannelEventQueue.cpp
@@ -0,0 +1,218 @@
+/* -*- 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(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;
+ };
+
+ // 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;
+ }
+
+ 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..75df2d15a0
--- /dev/null
+++ b/netwerk/ipc/ChannelEventQueue.h
@@ -0,0 +1,354 @@
+/* -*- 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(GetMainThreadEventTarget());
+ }
+};
+
+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.
+
+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();
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ bool IsEmpty() const { return mEventQueue.IsEmpty(); }
+#endif
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~ChannelEventQueue() = default;
+
+ void SuspendInternal();
+ void ResumeInternal();
+
+ bool MaybeSuspendIfEventsAreSuppressed();
+
+ inline void MaybeFlushQueue();
+ void FlushQueue();
+ inline void CompleteResume();
+
+ ChannelEvent* TakeEvent();
+
+ nsTArray<UniquePtr<ChannelEvent>> mEventQueue;
+
+ uint32_t mSuspendCount;
+ bool mSuspended;
+ uint32_t mForcedCount; // Support ForcedQueueing on multiple thread.
+ bool mFlushing;
+
+ // Whether the queue is associated with an XHR. This is lazily instantiated
+ // the first time it is needed.
+ bool mHasCheckedForXMLHttpRequest;
+ bool mForXMLHttpRequest;
+
+ // Keep ptr to avoid refcount cycle: only grab ref during flushing.
+ nsISupports* mOwner;
+
+ // For atomic mEventQueue operation and state update
+ Mutex mMutex;
+
+ // To guarantee event execution order among threads
+ RecursiveMutex mRunningMutex;
+
+ 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(mOwner);
+ Unused << kungFuDeathGrip; // Not used in this function
+
+ // 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);
+
+ bool enqueue = !!mForcedCount || mSuspended || mFlushing ||
+ !mEventQueue.IsEmpty() ||
+ MaybeSuspendIfEventsAreSuppressed();
+
+ 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.
+ SuspendInternal();
+ mEventQueue.AppendElement(std::move(event));
+ ResumeInternal();
+ return;
+ }
+ }
+
+ MOZ_RELEASE_ASSERT(!aAssertionWhenNotQueued);
+ 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), 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..4dfa0eddf8
--- /dev/null
+++ b/netwerk/ipc/DocumentChannel.cpp
@@ -0,0 +1,468 @@
+/* -*- 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 aIsXFOError)
+ : mAsyncOpenTime(TimeStamp::Now()),
+ mLoadState(aLoadState),
+ mCacheKey(aCacheKey),
+ mLoadFlags(aLoadFlags),
+ mURI(aLoadState->URI()),
+ mLoadInfo(aLoadInfo),
+ mUriModified(aUriModified),
+ mIsXFOError(aIsXFOError) {
+ 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);
+}
+
+// Changes here should also be made in
+// E10SUtils.documentChannelPermittedForURI().
+static bool URIUsesDocChannel(nsIURI* aURI) {
+ if (SchemeIsJavascript(aURI)) {
+ return false;
+ }
+
+ nsCString spec = aURI->GetSpecOrDefault();
+ return !spec.EqualsLiteral("about:printpreview") &&
+ !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 aIsXFOError) {
+ RefPtr<DocumentChannel> channel;
+ if (XRE_IsContentProcess()) {
+ channel = new DocumentChannelChild(aLoadState, aLoadInfo, aLoadFlags,
+ aCacheKey, aUriModified, aIsXFOError);
+ } else {
+ channel =
+ new ParentProcessDocumentChannel(aLoadState, aLoadInfo, aLoadFlags,
+ aCacheKey, aUriModified, aIsXFOError);
+ }
+ 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::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("DocumentChannel::SetLoadFlags: Don't set flags after creation");
+}
+
+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(nsISupports** 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..03a84c7237
--- /dev/null
+++ b/netwerk/ipc/DocumentChannel.h
@@ -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 : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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;
+ }
+
+ /**
+ * 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 aIsXFOError);
+ 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 aIsXFOError);
+
+ void ShutdownListeners(nsresult aStatusCode);
+ void DisconnectChildListeners(const nsresult& aStatus,
+ const nsresult& aLoadGroupStatus);
+ virtual void DeleteIPDL() {}
+
+ nsDocShell* GetDocShell();
+
+ virtual ~DocumentChannel() = default;
+
+ const TimeStamp mAsyncOpenTime;
+ 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;
+ // mIsXFOError is true if we're handling a load error and the status of the
+ // failed channel is NS_ERROR_XFO_VIOLATION.
+ bool mIsXFOError = 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..527e27987a
--- /dev/null
+++ b/netwerk/ipc/DocumentChannelChild.cpp
@@ -0,0 +1,427 @@
+/* -*- 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/extensions/StreamFilterParent.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/ScopeExit.h"
+#include "nsHashPropertyBag.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsIWritablePropertyBag.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 aIsXFOError)
+ : DocumentChannel(aLoadState, aLoadInfo, aLoadFlags, aCacheKey,
+ aUriModified, aIsXFOError) {
+ 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;
+ }
+
+ DocumentChannelCreationArgs args;
+
+ args.loadState() = mLoadState->Serialize();
+ args.cacheKey() = mCacheKey;
+ args.channelId() = mChannelId;
+ args.asyncOpenTime() = mAsyncOpenTime;
+
+ Maybe<IPCClientInfo> ipcClientInfo;
+ if (mInitialClientInfo.isSome()) {
+ ipcClientInfo.emplace(mInitialClientInfo.ref().ToIPC());
+ }
+ args.initialClientInfo() = ipcClientInfo;
+
+ if (mTiming) {
+ args.timing() = Some(mTiming);
+ }
+
+ switch (mLoadInfo->GetExternalContentPolicyType()) {
+ case ExtContentPolicy::TYPE_DOCUMENT:
+ case ExtContentPolicy::TYPE_SUBDOCUMENT: {
+ DocumentCreationArgs docArgs;
+ docArgs.uriModified() = mUriModified;
+ docArgs.isXFOError() = mIsXFOError;
+
+ args.elementCreationArgs() = docArgs;
+ break;
+ }
+
+ case ExtContentPolicy::TYPE_OBJECT: {
+ ObjectCreationArgs objectArgs;
+ objectArgs.embedderInnerWindowId() = InnerWindowIDForExtantDoc(docShell);
+ objectArgs.loadFlags() = mLoadFlags;
+ objectArgs.contentPolicyType() = mLoadInfo->InternalContentPolicyType();
+ objectArgs.isUrgentStart() = UserActivation::IsHandlingUserInput();
+
+ args.elementCreationArgs() = 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;
+ }
+
+ gNeckoChild->SendPDocumentChannelConstructor(this, loadingContext, args);
+
+ mIsPending = true;
+ mWasOpened = true;
+ mListener = listener;
+
+ return NS_OK;
+}
+
+IPCResult DocumentChannelChild::RecvFailedAsyncOpen(
+ const nsresult& aStatusCode) {
+ ShutdownListeners(aStatusCode);
+ return IPC_OK();
+}
+
+IPCResult DocumentChannelChild::RecvDisconnectChildListeners(
+ const nsresult& aStatus, const nsresult& aLoadGroupStatus,
+ bool aSwitchedProcess) {
+ // 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 (!aSwitchedProcess) {
+ DisconnectChildListeners(aStatus, aLoadGroupStatus);
+ }
+ 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(), cspToInheritLoadingDocument, getter_AddRefs(loadInfo)));
+
+ mRedirectResolver = std::move(aResolve);
+
+ nsCOMPtr<nsIChannel> newChannel;
+ MOZ_ASSERT((aArgs.loadStateLoadFlags() &
+ 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);
+ }
+
+ // 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(), GetMainThreadEventTarget());
+
+ 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) {
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ if (CanSend()) {
+ SendCancel(aStatusCode);
+ }
+
+ 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..b7a962b2ee
--- /dev/null
+++ b/netwerk/ipc/DocumentChannelChild.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_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 aIsXFOError);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+ NS_IMETHOD Cancel(nsresult aStatusCode) 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;
+};
+
+} // 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..9eec9a1515
--- /dev/null
+++ b/netwerk/ipc/DocumentChannelParent.cpp
@@ -0,0 +1,145 @@
+/* -*- 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 "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 =
+ new nsDocShellLoadState(aArgs.loadState());
+ LOG(("DocumentChannelParent Init [this=%p, uri=%s]", this,
+ loadState->URI()->GetSpecOrDefault().get()));
+
+ RefPtr<DocumentLoadListener::OpenPromise> promise;
+ if (loadState->GetChannelInitialized()) {
+ promise = DocumentLoadListener::ClaimParentLoad(
+ getter_AddRefs(mDocumentLoadListener), loadState->GetLoadIdentifier());
+ }
+ 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().refOr(nullptr),
+ std::move(clientInfo), Some(docArgs.uriModified()),
+ Some(docArgs.isXFOError()), IProtocol::OtherPid(), &rv);
+ } else {
+ const ObjectCreationArgs& objectArgs = aArgs.elementCreationArgs();
+
+ promise = mDocumentLoadListener->OpenObject(
+ loadState, aArgs.cacheKey(), Some(aArgs.channelId()),
+ aArgs.asyncOpenTime(), aArgs.timing().refOr(nullptr),
+ std::move(clientInfo), objectArgs.embedderInnerWindowId(),
+ objectArgs.loadFlags(), objectArgs.contentPolicyType(),
+ objectArgs.isUrgentStart(), IProtocol::OtherPid(),
+ 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);
+ // 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.mSwitchedProcess);
+ }
+ 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) {
+ if (!CanSend()) {
+ return PDocumentChannelParent::RedirectToRealChannelPromise::
+ CreateAndReject(ResponseRejectReason::ChannelClosed, __func__);
+ }
+ RedirectToRealChannelArgs args;
+ mDocumentLoadListener->SerializeRedirectData(
+ args, false, aRedirectFlags, aLoadFlags,
+ static_cast<ContentParent*>(Manager()->Manager()));
+ 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..5bbdcca039
--- /dev/null
+++ b/netwerk/ipc/DocumentChannelParent.h
@@ -0,0 +1,64 @@
+/* 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 {
+
+/**
+ * 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
+ bool RecvCancel(const nsresult& aStatus) {
+ if (mDocumentLoadListener) {
+ mDocumentLoadListener->Cancel(aStatus);
+ }
+ return true;
+ }
+ void ActorDestroy(ActorDestroyReason aWhy) override {
+ if (mDocumentLoadListener) {
+ mDocumentLoadListener->Cancel(NS_BINDING_ABORTED);
+ }
+ }
+
+ private:
+ RefPtr<ObjectUpgradePromise> UpgradeObjectLoad() override;
+
+ RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
+ RedirectToRealChannel(
+ nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&&
+ aStreamFilterEndpoints,
+ uint32_t aRedirectFlags, uint32_t aLoadFlags);
+
+ 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..3d7611ba7a
--- /dev/null
+++ b/netwerk/ipc/DocumentLoadListener.cpp
@@ -0,0 +1,2544 @@
+/* -*- 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 "mozilla/AntiTrackingUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/MozPromiseInlines.h" // For MozPromise::FromDomPromise
+#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/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 "nsDocShell.h"
+#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsExternalHelperAppService.h"
+#include "nsHttpChannel.h"
+#include "nsIBrowser.h"
+#include "nsIE10SUtils.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 "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/RemoteWebProgress.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)
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace net {
+
+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->HasLoadFlags(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(),
+ securityFlags, sandboxFlags);
+ } else {
+ OriginAttributes attrs;
+ aBrowsingContext->GetOriginAttributes(attrs);
+ loadInfo = LoadInfo::CreateForDocument(aBrowsingContext,
+ aLoadState->TriggeringPrincipal(),
+ attrs, securityFlags, sandboxFlags);
+ }
+
+ loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags());
+ loadInfo->SetHasValidUserGestureActivation(
+ aLoadState->HasValidUserGestureActivation());
+
+ 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());
+
+ return loadInfo.forget();
+}
+
+static auto WebProgressForBrowsingContext(
+ CanonicalBrowsingContext* aBrowsingContext)
+ -> already_AddRefed<BrowsingContextWebProgress> {
+ return RefPtr<BrowsingContextWebProgress>(aBrowsingContext->GetWebProgress())
+ .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, mBrowsingContext->GetAllowPlugins());
+ 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)) {
+ 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_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,
+ aLoadingBrowsingContext->UsePrivateBrowsing());
+}
+
+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();
+
+ nsDocShell::InternalAddURIVisit(uri, previousURI, previousFlags,
+ responseStatus, browsingContext, widget,
+ mLoadStateLoadType);
+}
+
+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;
+}
+
+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,
+ base::ProcessId aPid, nsresult* aRv)
+ -> RefPtr<OpenPromise> {
+ auto* loadingContext = GetLoadingBrowsingContext();
+
+ MOZ_DIAGNOSTIC_ASSERT_IF(loadingContext->GetParent(),
+ loadingContext->GetParentWindowContext());
+
+ OriginAttributes attrs;
+ loadingContext->GetOriginAttributes(attrs);
+
+ mLoadIdentifier = aLoadState->GetLoadIdentifier();
+
+ 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;
+ }
+
+ auto* documentContext = GetDocumentBrowsingContext();
+ if (documentContext && 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,
+ mChannel);
+ if (!mLoadingSessionHistoryInfo) {
+ *aRv = NS_BINDING_ABORTED;
+ mParentChannelListener = nullptr;
+ mChannel = nullptr;
+ return nullptr;
+ }
+ }
+
+ 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);
+ }
+
+ RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
+ if (httpChannelImpl) {
+ httpChannelImpl->SetWarningReporter(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->HasLoadFlags(
+ 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->LoadFlags(), 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);
+
+ mOtherPid = aPid;
+ mChannelCreationURI = aLoadState->URI();
+ mLoadStateLoadFlags = aLoadState->LoadFlags();
+ 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?");
+ }
+
+ *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> aIsXFOError, base::ProcessId aPid,
+ 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(aIsXFOError));
+
+ return Open(aLoadState, loadInfo, loadFlags, aCacheKey, aChannelId,
+ aAsyncOpenTime, aTiming, std::move(aInfo), false, aPid, 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,
+ base::ProcessId aPid, ObjectUpgradeHandler* aObjectUpgradeHandler,
+ nsresult* aRv) -> RefPtr<OpenPromise> {
+ LOG(("DocumentLoadListener [%p] OpenObject [uri=%s]", this,
+ aLoadState->URI()->GetSpecOrDefault().get()));
+
+ MOZ_ASSERT(!mIsDocumentLoad);
+
+ auto sandboxFlags = GetLoadingBrowsingContext()->GetSandboxFlags();
+
+ RefPtr<LoadInfo> loadInfo = CreateObjectLoadInfo(
+ aLoadState, aInnerWindowId, aContentPolicyType, sandboxFlags);
+
+ mObjectUpgradeHandler = aObjectUpgradeHandler;
+
+ return Open(aLoadState, loadInfo, aLoadFlags, aCacheKey, aChannelId,
+ aAsyncOpenTime, aTiming, std::move(aInfo), aUrgentStart, aPid,
+ 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;
+ }
+
+ if (nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp()) {
+ // Check CSP navigate-to
+ bool allowsNavigateTo = false;
+ nsresult rv = csp->GetAllowsNavigateTo(aLoadState->URI(),
+ aLoadState->IsFormSubmission(),
+ false, /* aWasRedirected */
+ false, /* aEnforceWhitelist */
+ &allowsNavigateTo);
+ if (NS_FAILED(rv) || !allowsNavigateTo) {
+ 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()->OtherPid(), &rv);
+}
+
+void DocumentLoadListener::FireStateChange(uint32_t aStateFlags,
+ nsresult aStatus) {
+ nsCOMPtr<nsIChannel> request = GetChannel();
+ nsCOMPtr<nsIWebProgress> webProgress =
+ new RemoteWebProgress(GetLoadType(), true, true);
+
+ RefPtr<BrowsingContextWebProgress> loadingWebProgress =
+ WebProgressForBrowsingContext(GetLoadingBrowsingContext());
+
+ if (loadingWebProgress) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("DocumentLoadListener::FireStateChange", [=]() {
+ loadingWebProgress->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.mSwitchedProcess) {
+ // 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)
+ -> 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;
+ }
+
+ 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);
+}
+
+void DocumentLoadListener::Disconnect() {
+ LOG(("DocumentLoadListener Disconnect [this=%p]", this));
+ // 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 (auto* ctx = GetDocumentBrowsingContext()) {
+ ctx->EndDocumentLoad(mDoingProcessSwitch);
+ }
+}
+
+void DocumentLoadListener::Cancel(const nsresult& aStatusCode) {
+ LOG(
+ ("DocumentLoadListener Cancel [this=%p, "
+ "aStatusCode=%" PRIx32 " ]",
+ this, static_cast<uint32_t>(aStatusCode)));
+ if (mOpenPromiseResolved) {
+ return;
+ }
+ if (mChannel) {
+ mChannel->Cancel(aStatusCode);
+ }
+
+ DisconnectListeners(aStatusCode, aStatusCode);
+}
+
+void DocumentLoadListener::DisconnectListeners(nsresult aStatus,
+ nsresult aLoadGroupStatus,
+ bool aSwitchedProcess) {
+ LOG(
+ ("DocumentLoadListener DisconnectListener [this=%p, "
+ "aStatus=%" PRIx32 " aLoadGroupStatus=%" PRIx32 " ]",
+ this, static_cast<uint32_t>(aStatus),
+ static_cast<uint32_t>(aLoadGroupStatus)));
+
+ RejectOpenPromise(aStatus, aLoadGroupStatus, aSwitchedProcess, __func__);
+
+ Disconnect();
+
+ if (!aSwitchedProcess) {
+ // If we're not going to send anything else to the content process, and
+ // we haven't yet consumed a stream filter promise, then we're never going
+ // to. If we're disconnecting the old content process due to a proces
+ // switch, then we can rely on FinishReplacementChannelSetup being called
+ // (even if the switch failed), so we clear at that point instead.
+ // TODO: This might be because we retargeted the stream to the download
+ // handler or similar. Do we need to attach a stream filter to that?
+ 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 nsIHttpChannel::FlashPluginState& aState) {
+ parentChannel->NotifyFlashPluginStateChanged(aState);
+ },
+ [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);
+ },
+ [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();
+ }
+ nsresult rv = NS_OK;
+ for (auto& variant : streamListenerFunctions) {
+ variant.match(
+ [&](const OnStartRequestParams& aParams) {
+ rv = aListener->OnStartRequest(aParams.request);
+ if (NS_FAILED(rv)) {
+ aParams.request->Cancel(rv);
+ }
+ },
+ [&](const OnDataAvailableParams& aParams) {
+ // Don't deliver OnDataAvailable if we've
+ // already failed.
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ nsCOMPtr<nsIInputStream> stringStream;
+ rv = NS_NewByteInputStream(
+ getter_AddRefs(stringStream),
+ Span<const char>(aParams.data.get(), aParams.count),
+ NS_ASSIGNMENT_DEPEND);
+ if (NS_SUCCEEDED(rv)) {
+ rv = aListener->OnDataAvailable(aParams.request, stringStream,
+ aParams.offset, aParams.count);
+ }
+ if (NS_FAILED(rv)) {
+ aParams.request->Cancel(rv);
+ }
+ },
+ [&](const OnStopRequestParams& aParams) {
+ if (NS_SUCCEEDED(rv)) {
+ aListener->OnStopRequest(aParams.request, aParams.status);
+ } else {
+ aListener->OnStopRequest(aParams.request, rv);
+ }
+ rv = NS_OK;
+ },
+ [&](const OnAfterLastPartParams& aParams) {
+ nsCOMPtr<nsIMultiPartChannelListener> multiListener =
+ do_QueryInterface(aListener);
+ if (multiListener) {
+ if (NS_SUCCEEDED(rv)) {
+ multiListener->OnAfterLastPart(aParams.status);
+ } else {
+ multiListener->OnAfterLastPart(rv);
+ }
+ }
+ });
+ }
+ // 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();
+
+ if (auto* ctx = GetDocumentBrowsingContext()) {
+ ctx->EndDocumentLoad(mDoingProcessSwitch);
+ }
+
+ return !mIsFinished;
+}
+
+void DocumentLoadListener::SerializeRedirectData(
+ RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess,
+ uint32_t aRedirectFlags, uint32_t aLoadFlags,
+ ContentParent* aParent) const {
+ // Use the original URI of the current channel, as this is what
+ // we'll use to construct the channel in the content process.
+ aArgs.uri() = mChannelCreationURI;
+ if (!aArgs.uri()) {
+ mChannel->GetOriginalURI(getter_AddRefs(aArgs.uri()));
+ }
+
+ aArgs.loadIdentifier() = mLoadIdentifier;
+
+ // 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();
+
+ nsCOMPtr<nsIPrincipal> uriPrincipal;
+ nsIScriptSecurityManager* sm = nsContentUtils::GetSecurityManager();
+ sm->GetChannelURIPrincipal(mChannel, getter_AddRefs(uriPrincipal));
+
+ nsCOMPtr<nsIRedirectHistoryEntry> entry =
+ new nsRedirectHistoryEntry(uriPrincipal, nullptr, ""_ns);
+
+ redirectLoadInfo->AppendRedirectHistoryEntry(entry, true);
+ }
+
+ const Maybe<ClientInfo>& reservedClientInfo =
+ channelLoadInfo->GetReservedClientInfo();
+ if (reservedClientInfo) {
+ redirectLoadInfo->SetReservedClientInfo(*reservedClientInfo);
+ }
+
+ aArgs.registrarId() = mRedirectChannelId;
+
+ 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(aParent));
+ }
+
+ 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.loadStateLoadFlags() = mLoadStateLoadFlags;
+ aArgs.loadStateLoadType() = mLoadStateLoadType;
+ aArgs.originalUriString() = mOriginalUriString;
+ if (mLoadingSessionHistoryInfo) {
+ aArgs.loadingSessionHistoryInfo().emplace(*mLoadingSessionHistoryInfo);
+ }
+}
+
+static bool IsLargeAllocationLoad(CanonicalBrowsingContext* aBrowsingContext,
+ nsIChannel* aChannel) {
+ if (!StaticPrefs::dom_largeAllocationHeader_enabled() ||
+ aBrowsingContext->UseRemoteSubframes()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ if (!httpChannel) {
+ return false;
+ }
+
+ nsAutoCString ignoredHeaderValue;
+ nsresult rv =
+ httpChannel->GetResponseHeader("Large-Allocation"_ns, ignoredHeaderValue);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ // On all platforms other than win32, LargeAllocation is disabled by default,
+ // and has to be force-enabled using `dom.largeAllocation.forceEnable`.
+#if defined(XP_WIN) && defined(_X86_)
+ return true;
+#else
+ return StaticPrefs::dom_largeAllocation_forceEnable();
+#endif
+}
+
+static bool ContextCanProcessSwitch(CanonicalBrowsingContext* aBrowsingContext,
+ WindowGlobalParent* aParentWindow) {
+ if (NS_WARN_IF(!aBrowsingContext)) {
+ LOG(("Process Switch Abort: no browsing context"));
+ return false;
+ }
+ if (!aBrowsingContext->IsContent()) {
+ LOG(("Process Switch Abort: non-content browsing context"));
+ return false;
+ }
+
+ if (aParentWindow && !aBrowsingContext->UseRemoteSubframes()) {
+ LOG(("Process Switch Abort: remote subframes disabled"));
+ return false;
+ }
+
+ if (aParentWindow && aParentWindow->IsInProcess()) {
+ LOG(("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) {
+ LOG(("Process Switch Abort: cannot get embedder element"));
+ return false;
+ }
+ nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser();
+ if (!browser) {
+ LOG(("Process Switch Abort: not loaded within nsIBrowser"));
+ return false;
+ }
+
+ nsIBrowser::ProcessBehavior processBehavior =
+ nsIBrowser::PROCESS_BEHAVIOR_DISABLED;
+ nsresult rv = browser->GetProcessSwitchBehavior(&processBehavior);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT_UNREACHABLE(
+ "nsIBrowser::GetProcessSwitchBehavior shouldn't fail");
+ LOG(("Process Switch Abort: failed to get process switch behavior"));
+ return false;
+ }
+
+ // Check if the process switch we're considering is disabled by the
+ // <browser>'s process behavior.
+ if (processBehavior == nsIBrowser::PROCESS_BEHAVIOR_DISABLED) {
+ LOG(("Process Switch Abort: switch disabled by <browser>"));
+ return false;
+ }
+ if (!aParentWindow &&
+ processBehavior == nsIBrowser::PROCESS_BEHAVIOR_SUBFRAME_ONLY) {
+ LOG(("Process Switch Abort: toplevel switch disabled by <browser>"));
+ return false;
+ }
+
+ return true;
+}
+
+bool DocumentLoadListener::MaybeTriggerProcessSwitch(
+ bool* aWillSwitchToRemote) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_DIAGNOSTIC_ASSERT(!mDoingProcessSwitch,
+ "Already in the middle of switching?");
+ MOZ_DIAGNOSTIC_ASSERT(mChannel);
+ MOZ_DIAGNOSTIC_ASSERT(mParentChannelListener);
+ MOZ_DIAGNOSTIC_ASSERT(aWillSwitchToRemote);
+
+ LOG(("DocumentLoadListener MaybeTriggerProcessSwitch [this=%p]", this));
+
+ // 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 && !mChannel->IsDocument()) {
+ LOG(("Process Switch Abort: non-document load"));
+ return false;
+ }
+
+ // Get the loading BrowsingContext. This may not be the context which will be
+ // switching processes 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();
+ RefPtr<WindowGlobalParent> parentWindow = GetParentWindowContext();
+ if (!ContextCanProcessSwitch(browsingContext, parentWindow)) {
+ return false;
+ }
+
+ // Get the final principal, used to select which process to load into.
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ mChannel, getter_AddRefs(resultPrincipal));
+ if (NS_FAILED(rv)) {
+ LOG(("Process Switch Abort: failed to get channel result principal"));
+ return false;
+ }
+
+ nsAutoCString currentRemoteType(NOT_REMOTE_TYPE);
+ if (RefPtr<ContentParent> contentParent =
+ browsingContext->GetContentParent()) {
+ currentRemoteType = contentParent->GetRemoteType();
+ }
+ MOZ_ASSERT_IF(currentRemoteType.IsEmpty(), !OtherPid());
+
+ // Determine what type of content process this load should finish in.
+ nsAutoCString preferredRemoteType(currentRemoteType);
+ bool replaceBrowsingContext = false;
+ uint64_t specificGroupId = 0;
+
+ // If we're in a preloaded browser, force browsing context replacement to
+ // ensure the current process is re-selected.
+ {
+ Element* browserElement = browsingContext->Top()->GetEmbedderElement();
+
+ nsAutoString isPreloadBrowserStr;
+ if (browserElement->GetAttr(kNameSpaceID_None, nsGkAtoms::preloadedState,
+ isPreloadBrowserStr) &&
+ isPreloadBrowserStr.EqualsLiteral("consumed")) {
+ nsCOMPtr<nsIURI> originalURI;
+ if (NS_SUCCEEDED(mChannel->GetOriginalURI(getter_AddRefs(originalURI))) &&
+ !originalURI->GetSpecOrDefault().EqualsLiteral("about:newtab")) {
+ LOG(("Process Switch: leaving preloaded browser"));
+ replaceBrowsingContext = true;
+ browserElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::preloadedState,
+ true);
+ }
+ }
+ }
+
+ // Update the preferred final process for our load based on the
+ // Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers.
+ {
+ bool isCOOPSwitch = HasCrossOriginOpenerPolicyMismatch();
+ replaceBrowsingContext |= isCOOPSwitch;
+
+ // Determine our COOP status, which will be used to determine our preferred
+ // remote type.
+ nsILoadInfo::CrossOriginOpenerPolicy coop =
+ nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
+ if (parentWindow) {
+ coop = browsingContext->Top()->GetOpenerPolicy();
+ } else if (nsCOMPtr<nsIHttpChannelInternal> httpChannel =
+ do_QueryInterface(mChannel)) {
+ MOZ_ALWAYS_SUCCEEDS(httpChannel->GetCrossOriginOpenerPolicy(&coop));
+ }
+
+ if (coop ==
+ nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP) {
+ // We want documents with SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP COOP
+ // policy to be loaded in a separate process in which we can enable
+ // high-resolution timers.
+ nsAutoCString siteOrigin;
+ resultPrincipal->GetSiteOrigin(siteOrigin);
+ preferredRemoteType = WITH_COOP_COEP_REMOTE_TYPE_PREFIX;
+ preferredRemoteType.Append(siteOrigin);
+ } else if (isCOOPSwitch) {
+ // If we're doing a COOP switch, we do not need any affinity to the
+ // current remote type. Clear it back to the default value.
+ preferredRemoteType = DEFAULT_REMOTE_TYPE;
+ }
+ }
+
+ // If we're performing a large allocation load, override the remote type
+ // with `LARGE_ALLOCATION_REMOTE_TYPE` to move it into an exclusive content
+ // process. If we're already in one, and don't otherwise we force ourselves
+ // out of that content process.
+ if (!parentWindow && browsingContext->Group()->Toplevels().Length() == 1) {
+ if (IsLargeAllocationLoad(browsingContext, mChannel)) {
+ preferredRemoteType = LARGE_ALLOCATION_REMOTE_TYPE;
+ replaceBrowsingContext = true;
+ } else if (preferredRemoteType == LARGE_ALLOCATION_REMOTE_TYPE) {
+ preferredRemoteType = DEFAULT_REMOTE_TYPE;
+ replaceBrowsingContext = true;
+ }
+ }
+
+ // Put toplevel BrowsingContexts which load within the extension process into
+ // a specific BrowsingContextGroup.
+ if (auto* addonPolicy = BasePrincipal::Cast(resultPrincipal)->AddonPolicy()) {
+ if (!parentWindow) {
+ // Toplevel extension BrowsingContexts must be loaded in the extension
+ // browsing context group, within the extension content process.
+ if (ExtensionPolicyService::GetSingleton().UseRemoteExtensions()) {
+ preferredRemoteType = EXTENSION_REMOTE_TYPE;
+ } else {
+ preferredRemoteType = NOT_REMOTE_TYPE;
+ }
+
+ if (browsingContext->Group()->Id() !=
+ addonPolicy->GetBrowsingContextGroupId()) {
+ replaceBrowsingContext = true;
+ specificGroupId = addonPolicy->GetBrowsingContextGroupId();
+ }
+ } else {
+ // As a temporary measure, extension iframes must be loaded within the
+ // same process as their parent document.
+ preferredRemoteType = parentWindow->GetRemoteType();
+ }
+ }
+
+ LOG(
+ ("DocumentLoadListener GetRemoteTypeForPrincipal "
+ "[this=%p, contentParent=%s, preferredRemoteType=%s]",
+ this, currentRemoteType.get(), preferredRemoteType.get()));
+
+ nsCOMPtr<nsIE10SUtils> e10sUtils =
+ do_ImportModule("resource://gre/modules/E10SUtils.jsm", "E10SUtils");
+ if (!e10sUtils) {
+ LOG(("Process Switch Abort: Could not import E10SUtils"));
+ return false;
+ }
+
+ // Get information about the current document loaded in our BrowsingContext.
+ nsCOMPtr<nsIPrincipal> currentPrincipal;
+ RefPtr<WindowGlobalParent> wgp = browsingContext->GetCurrentWindowGlobal();
+ if (wgp) {
+ currentPrincipal = wgp->DocumentPrincipal();
+ }
+
+ nsAutoCString remoteType;
+ rv = e10sUtils->GetRemoteTypeForPrincipal(
+ resultPrincipal, mChannelCreationURI, browsingContext->UseRemoteTabs(),
+ browsingContext->UseRemoteSubframes(), preferredRemoteType,
+ currentPrincipal, parentWindow, remoteType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("Process Switch Abort: getRemoteTypeForPrincipal threw an exception"));
+ return false;
+ }
+
+ // If the final decision is to switch from an 'extension' remote type to any
+ // other remote type, ensure the browsing context is replaced so that we leave
+ // the extension-specific BrowsingContextGroup.
+ if (!parentWindow && currentRemoteType != remoteType &&
+ currentRemoteType == EXTENSION_REMOTE_TYPE) {
+ replaceBrowsingContext = true;
+ }
+
+ LOG(("GetRemoteTypeForPrincipal -> current:%s remoteType:%s",
+ currentRemoteType.get(), remoteType.get()));
+
+ // Check if a process switch is needed.
+ if (currentRemoteType == remoteType && !replaceBrowsingContext) {
+ LOG(("Process Switch Abort: type (%s) is compatible", remoteType.get()));
+ return false;
+ }
+
+ if (NS_WARN_IF(parentWindow && remoteType.IsEmpty())) {
+ LOG(("Process Switch Abort: non-remote target process for subframe"));
+ return false;
+ }
+
+ *aWillSwitchToRemote = !remoteType.IsEmpty();
+
+ // If we're doing a document load, we can immediately perform a process
+ // switch.
+ if (mIsDocumentLoad) {
+ TriggerProcessSwitch(browsingContext, remoteType, replaceBrowsingContext,
+ specificGroupId);
+ 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) {
+ LOG(("Process Switch Abort: no object upgrade handler"));
+ return false;
+ }
+
+ if (!StaticPrefs::fission_remoteObjectEmbed()) {
+ LOG(("Process Switch Abort: remote <object>/<embed> disabled"));
+ return false;
+ }
+
+ mObjectUpgradeHandler->UpgradeObjectLoad()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr{this}, remoteType, replaceBrowsingContext, specificGroupId,
+ wgp](const RefPtr<CanonicalBrowsingContext>& aBrowsingContext) {
+ if (aBrowsingContext->IsDiscarded() ||
+ wgp != aBrowsingContext->GetParentWindowContext()) {
+ LOG(
+ ("Process Switch: Got invalid BrowsingContext from object "
+ "upgrade!"));
+ self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
+ return;
+ }
+
+ LOG(("Process Switch: Upgraded Object to Document Load"));
+ self->TriggerProcessSwitch(aBrowsingContext, remoteType,
+ replaceBrowsingContext, specificGroupId);
+ },
+ [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 nsCString& aRemoteType,
+ bool aReplaceBrowsingContext, uint64_t aSpecificGroupId) {
+ nsAutoCString currentRemoteType(NOT_REMOTE_TYPE);
+ if (RefPtr<ContentParent> contentParent = aContext->GetContentParent()) {
+ currentRemoteType = contentParent->GetRemoteType();
+ }
+ MOZ_ASSERT_IF(currentRemoteType.IsEmpty(), !OtherPid());
+
+ LOG(("Process Switch: Changing Remoteness from '%s' to '%s'",
+ currentRemoteType.get(), aRemoteType.get()));
+
+ // We're now committing to a process switch, so we can disconnect from
+ // the listeners in the old process.
+ mDoingProcessSwitch = true;
+
+ RefPtr<WindowGlobalParent> wgp = aContext->GetCurrentWindowGlobal();
+ if (wgp && wgp->IsProcessRoot()) {
+ if (RefPtr<BrowserParent> browserParent = wgp->GetBrowserParent()) {
+ // This load has already started, so we want to filter out any 'stop'
+ // progress events coming from the old process as a result of us
+ // disconnecting from it.
+ browserParent->SuspendProgressEvents();
+ }
+ }
+ DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED, true);
+
+ LOG(("Process Switch: Calling ChangeRemoteness"));
+ aContext
+ ->ChangeRemoteness(aRemoteType, mLoadIdentifier, aReplaceBrowsingContext,
+ aSpecificGroupId)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr{this}](BrowserParent* aBrowserParent) {
+ MOZ_ASSERT(self->mChannel,
+ "Something went wrong, channel got cancelled");
+ self->TriggerRedirectToRealChannel(Some(
+ aBrowserParent ? aBrowserParent->Manager()->ChildID() : 0));
+ },
+ [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(mLoadStateLoadFlags);
+ 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<uint64_t>& 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) {
+ if (!*aDestinationProcess) {
+ MOZ_ASSERT(aStreamFilterEndpoints.IsEmpty());
+ return RedirectToParentProcess(aRedirectFlags, aLoadFlags);
+ }
+ dom::ContentParent* cp =
+ dom::ContentProcessManager::GetSingleton()->GetContentProcessById(
+ ContentParentId{*aDestinationProcess});
+ if (!cp) {
+ return PDocumentChannelParent::RedirectToRealChannelPromise::
+ CreateAndReject(ipc::ResponseRejectReason::SendError, __func__);
+ }
+
+ RedirectToRealChannelArgs args;
+ SerializeRedirectData(args, !!aDestinationProcess, aRedirectFlags,
+ aLoadFlags, cp);
+ if (mTiming) {
+ mTiming->Anonymize(args.uri());
+ args.timing() = Some(std::move(mTiming));
+ }
+
+ auto loadInfo = args.loadInfo();
+
+ if (loadInfo.isNothing()) {
+ return PDocumentChannelParent::RedirectToRealChannelPromise::
+ CreateAndReject(ipc::ResponseRejectReason::SendError, __func__);
+ }
+
+ auto triggeringPrincipalOrErr =
+ PrincipalInfoToPrincipal(loadInfo.ref().triggeringPrincipalInfo());
+
+ if (triggeringPrincipalOrErr.isOk()) {
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ triggeringPrincipalOrErr.unwrap();
+ cp->TransmitBlobDataIfBlobURL(args.uri(), triggeringPrincipal);
+ }
+
+ 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, 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<uint64_t>& aDestinationProcess) {
+ 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(mStreamFilterRequests.Length());
+ if (!mStreamFilterRequests.IsEmpty()) {
+ base::ProcessId pid = OtherPid();
+ if (aDestinationProcess) {
+ if (*aDestinationProcess) {
+ dom::ContentParent* cp =
+ dom::ContentProcessManager::GetSingleton()->GetContentProcessById(
+ ContentParentId(*aDestinationProcess));
+ if (cp) {
+ pid = cp->OtherPid();
+ }
+ } else {
+ pid = 0;
+ }
+ }
+
+ for (StreamFilterRequest& request : mStreamFilterRequests) {
+ if (!pid) {
+ request.mPromise->Reject(false, __func__);
+ request.mPromise = nullptr;
+ continue;
+ }
+ ParentEndpoint parent;
+ nsresult rv = extensions::PStreamFilter::CreateEndpoints(
+ pid, request.mChildProcessId, &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(mStreamFilterRequests)](
+ 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 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) {
+ auto* bc = GetDocumentBrowsingContext();
+ if (!bc) {
+ return false;
+ }
+
+ nsCOMPtr<nsIInputStream> newPostData;
+ nsCOMPtr<nsIURI> newURI = nsDocShell::AttemptURIFixup(
+ mChannel, aStatus, mOriginalUriString, mLoadStateLoadType, bc->IsTop(),
+ mLoadStateLoadFlags &
+ nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP,
+ bc->UsePrivateBrowsing(), true, getter_AddRefs(newPostData));
+ 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);
+
+ 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);
+
+ 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);
+ }
+ }
+
+ auto* loadingContext = GetLoadingBrowsingContext();
+ if (!loadingContext || loadingContext->IsDiscarded()) {
+ DisconnectListeners(NS_ERROR_UNEXPECTED, NS_ERROR_UNEXPECTED);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // 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;
+ }
+
+ // 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;
+ }
+
+ 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)) {
+ if (!mSupportsRedirectToRealChannel) {
+ // 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.
+ mDoingProcessSwitch = true;
+
+ // If we're not going to process switch, then we must have an existing
+ // window global, right?
+ MOZ_ASSERT(loadingContext->GetCurrentWindowGlobal());
+
+ RefPtr<BrowserParent> browserParent =
+ loadingContext->GetCurrentWindowGlobal()->GetBrowserParent();
+
+ // XXX(anny) This is currently a dead code path because parent-controlled
+ // DC pref is off. When we enable the pref, we might get extra STATE_START
+ // progress events
+
+ // 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(loadingContext->OwnerProcessId()));
+ } else {
+ TriggerRedirectToRealChannel(Nothing());
+ }
+
+ // If we're not switching, then check if we're currently remote.
+ if (loadingContext->GetContentParent()) {
+ willBeRemote = true;
+ }
+ }
+
+ // 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, 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;
+}
+
+// Rather than forwarding all these nsIParentChannel functions to the child,
+// we cache a list of them, and then ask the 'real' channel to forward them
+// for us after it's created.
+NS_IMETHODIMP
+DocumentLoadListener::NotifyFlashPluginStateChanged(
+ nsIHttpChannel::FlashPluginState aState) {
+ mIParentChannelFunctions.AppendElement(
+ IParentChannelFunction{VariantIndex<0>{}, aState});
+ 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<1>{}, 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<2>{}, std::move(params)});
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DocumentLoadListener::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ mIParentChannelFunctions.AppendElement(IParentChannelFunction{
+ VariantIndex<3>{},
+ 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;
+
+ // 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;
+ }
+
+ if (GetDocumentBrowsingContext()) {
+ if (mLoadingSessionHistoryInfo) {
+ mLoadingSessionHistoryInfo =
+ GetDocumentBrowsingContext()
+ ->ReplaceLoadingSessionHistoryEntryForLoad(
+ mLoadingSessionHistoryInfo.get(), aNewChannel);
+ }
+ if (!net::ChannelIsPost(aOldChannel)) {
+ AddURIVisit(aOldChannel, 0);
+
+ nsCOMPtr<nsIURI> oldURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ nsDocShell::SaveLastVisit(aNewChannel, oldURI, aFlags);
+ }
+ }
+ mHaveVisibleRedirect |= true;
+
+ LOG(
+ ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
+ "mHaveVisibleRedirect=%c",
+ this, mHaveVisibleRedirect ? 'T' : 'F'));
+
+ // If this is a cross-origin redirect, then we should no longer allow
+ // mixed content. The destination docshell checks this in its redirect
+ // handling, but if we deliver to a new docshell (with a process switch)
+ // then this doesn't happen.
+ // Manually remove the allow mixed content flags.
+ nsresult rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel);
+ if (NS_FAILED(rv)) {
+ if (mLoadStateLoadType == LOAD_NORMAL_ALLOW_MIXED_CONTENT) {
+ mLoadStateLoadType = LOAD_NORMAL;
+ } else if (mLoadStateLoadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) {
+ mLoadStateLoadType = LOAD_RELOAD_NORMAL;
+ }
+ MOZ_ASSERT(!LOAD_TYPE_HAS_FLAGS(
+ mLoadStateLoadType, nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT));
+ }
+
+ // 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.
+ aNewChannel->GetOriginalURI(getter_AddRefs(mChannelCreationURI));
+
+ // 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();
+
+#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;
+}
+
+// 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(base::ProcessId aChildProcessId)
+ -> RefPtr<ChildEndpointPromise> {
+ LOG(("DocumentLoadListener AttachStreamFilter [this=%p]", this));
+
+ StreamFilterRequest* request = mStreamFilterRequests.AppendElement();
+ request->mPromise = new ChildEndpointPromise::Private(__func__);
+ request->mChildProcessId = aChildProcessId;
+ 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;
+ nsCOMPtr<nsIWebProgress> webProgress =
+ new RemoteWebProgress(mLoadStateLoadType, true, true);
+
+ RefPtr<BrowsingContextWebProgress> topWebProgress =
+ WebProgressForBrowsingContext(GetTopBrowsingContext());
+ const nsString message(aStatusArg);
+
+ if (topWebProgress) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("DocumentLoadListener::OnStatus", [=]() {
+ topWebProgress->OnStatusChange(webProgress, channel, aStatus,
+ message.get());
+ }));
+ }
+ 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..a77bfa9285
--- /dev/null
+++ b/netwerk/ipc/DocumentLoadListener.h
@@ -0,0 +1,575 @@
+/* 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 "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 "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;
+}
+namespace net {
+using ChildEndpointPromise =
+ MozPromise<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;
+ base::ProcessId mChildProcessId;
+ 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:
+ // See the comment on GetLoadingBrowsingContext for explanation of
+ // aLoadingBrowsingContext.
+ DocumentLoadListener(dom::CanonicalBrowsingContext* aLoadingBrowsingContext,
+ bool aIsDocumentLoad);
+
+ struct OpenPromiseSucceededType {
+ nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>
+ mStreamFilterEndpoints;
+ uint32_t mRedirectFlags;
+ uint32_t mLoadFlags;
+ RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>
+ mPromise;
+ };
+ struct OpenPromiseFailedType {
+ nsresult mStatus;
+ nsresult mLoadGroupStatus;
+ // This is set to true if we're rejecting the promise because we
+ // switched to load away to a new process.
+ bool mSwitchedProcess = false;
+ };
+
+ typedef MozPromise<OpenPromiseSucceededType, OpenPromiseFailedType,
+ true /* isExclusive */>
+ OpenPromise;
+
+ // 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,
+ base::ProcessId aPid, 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> aIsXFOError, base::ProcessId aPid,
+ 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,
+ base::ProcessId aPid, ObjectUpgradeHandler* aUpgradeHandler,
+ 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);
+
+ // 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
+
+ // 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);
+
+ 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) override {
+ LogBlockedCORSRequestParams params;
+ params.mMessage = aMessage;
+ params.mCategory = aCategory;
+ 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;
+ }
+
+ base::ProcessId OtherPid() const { return mOtherPid; }
+
+ [[nodiscard]] RefPtr<ChildEndpointPromise> AttachStreamFilter(
+ base::ProcessId aChildProcessId);
+
+ // 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,
+ dom::ContentParent* aParent) const;
+
+ uint64_t GetLoadIdentifier() const { return mLoadIdentifier; }
+ uint32_t GetLoadType() const { return mLoadStateLoadType; }
+
+ mozilla::dom::LoadingSessionHistoryInfo* GetLoadingSessionHistoryInfo() {
+ return mLoadingSessionHistoryInfo.get();
+ }
+
+ bool IsDocumentLoad() const { return mIsDocumentLoad; }
+
+ 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.
+ void DisconnectListeners(nsresult aStatus, nsresult aLoadGroupStatus,
+ bool aSwitchedProcess = 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<uint64_t>& aDestinationProcess);
+
+ // 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);
+ void TriggerProcessSwitch(dom::CanonicalBrowsingContext* aContext,
+ const nsCString& aRemoteType,
+ bool aReplaceBrowsingContext,
+ uint64_t aSpecificGroupId);
+
+ // 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 = ipc::Endpoint<extensions::PStreamFilterParent>;
+ RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
+ RedirectToRealChannel(uint32_t aRedirectFlags, uint32_t aLoadFlags,
+ const Maybe<uint64_t>& 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();
+
+ 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
+ //
+ // NotifyFlashPluginStateChanged(nsIHttpChannel::FlashPluginState aState) = 0;
+ // 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;
+ };
+
+ typedef mozilla::Variant<
+ nsIHttpChannel::FlashPluginState, ClassifierMatchedInfoParams,
+ ClassifierMatchedTrackingInfoParams, ClassificationFlagsParams>
+ IParentChannelFunction;
+
+ // 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;
+ };
+
+ struct LogMimeTypeMismatchParams {
+ nsCString mMessageName;
+ bool mWarning;
+ nsString mURL;
+ nsString mContentType;
+ };
+
+ typedef mozilla::Variant<ReportSecurityMessageParams,
+ LogBlockedCORSRequestParams,
+ LogMimeTypeMismatchParams>
+ SecurityWarningFunction;
+ nsTArray<SecurityWarningFunction> mSecurityWarningFunctions;
+
+ 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;
+ };
+ typedef mozilla::Variant<OnStartRequestParams, OnDataAvailableParams,
+ OnStopRequestParams, OnAfterLastPartParams>
+ StreamListenerFunction;
+ // 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;
+
+ // 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;
+
+ // The original URI of the current channel. If there are redirects,
+ // then the value on the channel gets overwritten with the original
+ // URI of the first channel in the redirect chain, so we cache the
+ // value we need here.
+ nsCOMPtr<nsIURI> mChannelCreationURI;
+
+ // 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;
+
+ // 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;
+
+ 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 mLoadStateLoadFlags = 0;
+ uint32_t mLoadStateLoadType = 0;
+
+ // 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;
+ // Set to true if we're currently in the middle of replacing this with
+ // a new channel connected a different process.
+ bool mDoingProcessSwitch = 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;
+
+ bool mSupportsRedirectToRealChannel = true;
+
+ // The process id of the content process that we are being called from
+ // or 0 initiated from a parent process load.
+ base::ProcessId mOtherPid = 0;
+
+ void RejectOpenPromise(nsresult aStatus, nsresult aLoadGroupStatus,
+ bool aSwitchedProcess, 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, aSwitchedProcess}),
+ 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..30a406facb
--- /dev/null
+++ b/netwerk/ipc/InputChannelThrottleQueueParent.cpp
@@ -0,0 +1,128 @@
+/* -*- 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 (!mRefCnt.isThreadSafe) {
+ NS_ASSERT_OWNINGTHREAD(InputChannelThrottleQueueParent);
+ }
+
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "InputChannelThrottleQueueParent");
+
+ if (count == 0) {
+ if (!mRefCnt.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;
+}
+
+InputChannelThrottleQueueParent::InputChannelThrottleQueueParent()
+ : mBytesProcessed(0), mMeanBytesPerSecond(0), mMaxBytesPerSecond(0) {}
+
+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..a81424fd30
--- /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();
+ mozilla::ipc::IPCResult RecvRecordRead(const uint32_t& aBytesRead);
+ void ActorDestroy(ActorDestroyReason aWhy) override {}
+
+ private:
+ virtual ~InputChannelThrottleQueueParent() = default;
+
+ uint64_t mBytesProcessed;
+ uint32_t mMeanBytesPerSecond;
+ uint32_t mMaxBytesPerSecond;
+};
+
+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..b6a51b279a
--- /dev/null
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -0,0 +1,546 @@
+/* -*- 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 protocol PFTPChannel;
+include protocol PChildToParentStream;
+include BlobTypes;
+include ClientIPCTypes;
+include URIParams;
+include IPCServiceWorkerDescriptor;
+include IPCStream;
+include PBackgroundSharedTypes;
+include DOMTypes;
+
+include "mozilla/dom/PropertyBagUtils.h";
+include "mozilla/dom/ReferrerInfoUtils.h";
+include "mozilla/ipc/URIUtils.h";
+
+using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+using 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";
+using refcounted class nsIPropertyBag2 from "nsIPropertyBag2.h";
+using refcounted class nsDOMNavigationTiming from "nsDOMNavigationTiming.h";
+using nsContentPolicyType from "nsIContentPolicy.h";
+using nsILoadInfo::CrossOriginEmbedderPolicy from "nsILoadInfo.h";
+using class mozilla::dom::LoadingSessionHistoryInfo from "mozilla/dom/SessionHistoryEntry.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 isOnContentBlockingAllowList;
+ CookiePermissionData[] cookiePermissions;
+ bool isFixed;
+ nsString partitionKey;
+};
+
+//-----------------------------------------------------------------------------
+// Preferrer alternative data type
+//-----------------------------------------------------------------------------
+
+struct PreferredAlternativeDataTypeParams
+{
+ nsCString type;
+ nsCString contentType;
+ bool deliverAltData;
+};
+
+//-----------------------------------------------------------------------------
+// LoadInfo IPDL structs
+//-----------------------------------------------------------------------------
+
+struct RedirectHistoryEntryInfo
+{
+ PrincipalInfo principalInfo;
+ URIParams? referrerUri;
+ nsCString remoteAddress;
+};
+
+struct LoadInfoArgs
+{
+ PrincipalInfo? requestingPrincipalInfo;
+ PrincipalInfo triggeringPrincipalInfo;
+ PrincipalInfo? principalToInheritInfo;
+ PrincipalInfo? sandboxedLoadingPrincipalInfo;
+ PrincipalInfo? topLevelPrincipalInfo;
+ PrincipalInfo? topLevelStorageAreaPrincipalInfo;
+ URIParams? resultPrincipalURI;
+ uint32_t securityFlags;
+ uint32_t sandboxFlags;
+ uint32_t triggeringSandboxFlags;
+ nsContentPolicyType contentPolicyType;
+ uint32_t tainting;
+ bool blockAllMixedContent;
+ bool upgradeInsecureRequests;
+ bool browserUpgradeInsecureRequests;
+ bool browserDidUpgradeInsecureRequests;
+ bool browserWouldUpgradeInsecureRequests;
+ bool forceAllowDataURI;
+ bool allowInsecureRedirectToDataURI;
+ bool bypassCORSChecks;
+ 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;
+
+ /**
+ * 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;
+ nsString cspNonce;
+ bool skipContentSniffing;
+ uint32_t httpsOnlyStatus;
+ bool hasValidUserGestureActivation;
+ bool allowDeprecatedSystemRequests;
+ bool isInDevToolsContext;
+ bool parserCreatedScript;
+ bool isFromProcessingFrameAttributes;
+ CookieJarSettingsArgs cookieJarSettings;
+ uint32_t requestBlockingReason;
+ CSPInfo? cspToInheritInfo;
+ bool hasStoragePermission;
+ CrossOriginEmbedderPolicy loadingEmbedderPolicy;
+};
+
+/**
+ * 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;
+
+ // WebExtextensions WebRequest API allows extentions to redirect a channel
+ // Before the CORS-preflight was fetched, which can lead into unexpeced CORS blocks.
+ // We can set this to skip the Cors Checks for the old Channel.
+ bool bypassCORSChecks;
+
+ // 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;
+
+ // 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;
+
+ // 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;
+
+ bool hasStoragePermission;
+
+ bool isThirdPartyContextToTopWindow;
+
+ bool isInThirdPartyContext;
+
+ // 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
+{
+ URIParams 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?
+ URIParams? original;
+ URIParams? doc;
+ nsIReferrerInfo referrerInfo;
+ URIParams? apiRedirectTo;
+ URIParams? topWindowURI;
+ uint32_t loadFlags;
+ RequestHeaderTuples requestHeaders;
+ nsCString requestMethod;
+ IPCStream? uploadStream;
+ bool uploadStreamHasHeaders;
+ int16_t priority;
+ uint32_t classOfService;
+ uint8_t redirectionLimit;
+ bool allowSTS;
+ uint32_t thirdPartyFlags;
+ bool resumeAt;
+ uint64_t startPos;
+ nsCString entityID;
+ bool chooseApplicationCache;
+ nsCString appCacheClientID;
+ bool allowSpdy;
+ bool allowHttp3;
+ bool allowAltSvc;
+ bool beConservative;
+ uint32_t tlsFlags;
+ LoadInfoArgs? loadInfo;
+ uint32_t cacheKey;
+ uint64_t requestContextID;
+ CorsPreflightArgs? preflightArgs;
+ uint32_t initialRwin;
+ bool blockAuthPrompt;
+ bool allowStaleCacheContent;
+ bool preferCacheLoadOverBypass;
+ nsCString contentTypeHint;
+ uint32_t corsMode;
+ uint32_t redirectMode;
+ uint64_t channelId;
+ nsString integrityMetadata;
+ uint64_t contentWindowId;
+ PreferredAlternativeDataTypeParams[] preferredAlternativeTypes;
+ uint64_t topLevelOuterContentWindowId;
+ TimeStamp launchServiceWorkerStart;
+ TimeStamp launchServiceWorkerEnd;
+ TimeStamp dispatchFetchEventStart;
+ TimeStamp dispatchFetchEventEnd;
+ TimeStamp handleFetchEventStart;
+ TimeStamp handleFetchEventEnd;
+ bool forceMainDocumentChannel;
+ TimeStamp navigationStartTimeStamp;
+ bool hasNonEmptySandboxingFlag;
+};
+
+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;
+ uint32_t tlsFlags;
+ bool isolated;
+ bool isTrrServiceChannel;
+ uint8_t trrMode;
+ bool isIPv4Disabled;
+ bool isIPv6Disabled;
+ nsCString topWindowOrigin;
+ bool isHttp3;
+ 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;
+};
+
+//-----------------------------------------------------------------------------
+// FTP IPDL structs
+//-----------------------------------------------------------------------------
+
+struct FTPChannelOpenArgs
+{
+ URIParams uri;
+ uint64_t startPos;
+ nsCString entityID;
+ IPCStream? uploadStream;
+ LoadInfoArgs? loadInfo;
+ uint32_t loadFlags;
+};
+
+struct FTPChannelConnectArgs
+{
+ uint32_t channelId;
+};
+
+union FTPChannelCreationArgs
+{
+ FTPChannelOpenArgs; // For AsyncOpen: the common case.
+ FTPChannelConnectArgs; // Used for redirected-to channels
+};
+
+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;
+ int32_t sameSite;
+ int32_t rawSameSite;
+ uint8_t schemeMap;
+};
+
+struct DocumentCreationArgs {
+ bool uriModified;
+ bool isXFOError;
+};
+
+struct ObjectCreationArgs {
+ uint64_t embedderInnerWindowId;
+ uint32_t loadFlags;
+ nsContentPolicyType contentPolicyType;
+ bool isUrgentStart;
+};
+
+union DocumentChannelElementCreationArgs {
+ DocumentCreationArgs;
+ ObjectCreationArgs;
+};
+
+struct DocumentChannelCreationArgs {
+ DocShellLoadStateInit loadState;
+ TimeStamp asyncOpenTime;
+ uint64_t channelId;
+ uint32_t cacheKey;
+ nsDOMNavigationTiming? timing;
+ IPCClientInfo? initialClientInfo;
+ DocumentChannelElementCreationArgs elementCreationArgs;
+};
+
+struct RedirectToRealChannelArgs {
+ uint32_t registrarId;
+ nsIURI uri;
+ uint32_t newLoadFlags;
+ ReplacementChannelConfigInit? init;
+ LoadInfoArgs? loadInfo;
+ uint64_t channelId;
+ nsIURI originalURI;
+ uint32_t redirectMode;
+ uint32_t redirectFlags;
+ uint32_t? contentDisposition;
+ nsString? contentDispositionFilename;
+ nsIPropertyBag2 properties;
+ uint32_t loadStateLoadFlags;
+ uint32_t loadStateLoadType;
+ nsDOMNavigationTiming? timing;
+ nsString srcdocData;
+ nsIURI baseUri;
+ LoadingSessionHistoryInfo? loadingSessionHistoryInfo;
+ uint64_t loadIdentifier;
+ nsCString? originalUriString;
+};
+
+struct TimingStructArgs {
+ TimeStamp domainLookupStart;
+ TimeStamp domainLookupEnd;
+ TimeStamp connectStart;
+ TimeStamp tcpConnectEnd;
+ TimeStamp secureConnectionStart;
+ TimeStamp connectEnd;
+ TimeStamp requestStart;
+ TimeStamp responseStart;
+ TimeStamp responseEnd;
+};
+
+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;
+ nsCString protocolVersion;
+
+ // 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;
+};
+
+struct HttpActivity
+{
+ nsCString host;
+ int32_t port;
+ bool endToEndSSL;
+};
+
+union HttpActivityArgs
+{
+ uint64_t;
+ HttpActivity;
+};
+
+struct TransactionObserverResult
+{
+ bool versionOk;
+ bool authOk;
+ nsresult closeReason;
+};
+
+struct SpeculativeConnectionOverriderArgs {
+ uint32_t parallelSpeculativeConnectLimit;
+ bool ignoreIdle;
+ bool isFromPredictor;
+ bool allow1918;
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/NeckoChild.cpp b/netwerk/ipc/NeckoChild.cpp
new file mode 100644
index 0000000000..401af4c719
--- /dev/null
+++ b/netwerk/ipc/NeckoChild.cpp
@@ -0,0 +1,360 @@
+
+/* 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/CookieServiceChild.h"
+#include "mozilla/net/FTPChannelChild.h"
+#include "mozilla/net/DataChannelChild.h"
+#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/ClassifierDummyChannelChild.h"
+#include "mozilla/net/SocketProcessBridgeChild.h"
+#ifdef MOZ_WEBRTC
+# include "mozilla/net/StunAddrsRequestChild.h"
+# include "mozilla/net/WebrtcTCPSocketChild.h"
+#endif
+
+#include "SerializedLoadContext.h"
+#include "nsGlobalWindow.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;
+ }
+ gNeckoChild = cpc->SendPNeckoConstructor();
+ NS_ASSERTION(gNeckoChild, "PNecko Protocol init failed!");
+ SocketProcessBridgeChild::GetSocketProcessBridge();
+ }
+}
+
+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 nsCString& 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;
+}
+
+PFTPChannelChild* NeckoChild::AllocPFTPChannelChild(
+ PBrowserChild* aBrowser, const SerializedLoadContext& aSerialized,
+ const FTPChannelCreationArgs& aOpenArgs) {
+ // We don't allocate here: see FTPChannelChild::AsyncOpen()
+ MOZ_CRASH("AllocPFTPChannelChild should not be called");
+ return nullptr;
+}
+
+bool NeckoChild::DeallocPFTPChannelChild(PFTPChannelChild* channel) {
+ MOZ_ASSERT(IsNeckoChild(), "DeallocPFTPChannelChild called by non-child!");
+
+ FTPChannelChild* child = static_cast<FTPChannelChild*>(channel);
+ child->ReleaseIPDLReference();
+ return true;
+}
+
+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) {
+ nsCOMPtr<nsISerialEventTarget> target;
+ if (nsGlobalWindowInner* win =
+ nsGlobalWindowInner::GetInnerWindowWithId(aInnerWindowID)) {
+ target = win->EventTargetFor(TaskCategory::Other);
+ }
+
+ RefPtr<WebSocketEventListenerChild> c =
+ new WebSocketEventListenerChild(aInnerWindowID, target);
+
+ if (target) {
+ gNeckoChild->SetEventTargetForActor(c, target);
+ }
+
+ 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 nsString& 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 nsCString& 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;
+}
+
+mozilla::ipc::IPCResult NeckoChild::RecvAsyncAuthPromptForNestedFrame(
+ const TabId& aNestedFrameId, const nsCString& aUri, const nsString& aRealm,
+ const uint64_t& aCallbackId) {
+ RefPtr<dom::BrowserChild> browserChild =
+ dom::BrowserChild::FindBrowserChild(aNestedFrameId);
+ if (!browserChild) {
+ MOZ_CRASH();
+ return IPC_FAIL_NO_REASON(this);
+ }
+ browserChild->SendAsyncAuthPrompt(aUri, aRealm, aCallbackId);
+ return IPC_OK();
+}
+
+/* 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();
+}
+
+PClassifierDummyChannelChild* NeckoChild::AllocPClassifierDummyChannelChild(
+ nsIURI* aURI, nsIURI* aTopWindowURI, const nsresult& aTopWindowURIResult,
+ const Maybe<LoadInfoArgs>& aLoadInfo) {
+ return new ClassifierDummyChannelChild();
+}
+
+bool NeckoChild::DeallocPClassifierDummyChannelChild(
+ PClassifierDummyChannelChild* aActor) {
+ delete static_cast<ClassifierDummyChannelChild*>(aActor);
+ return true;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/NeckoChild.h b/netwerk/ipc/NeckoChild.h
new file mode 100644
index 0000000000..efc98e95b9
--- /dev/null
+++ b/netwerk/ipc/NeckoChild.h
@@ -0,0 +1,96 @@
+/* -*- 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:
+ NeckoChild() = default;
+ virtual ~NeckoChild();
+
+ static void InitNeckoChild();
+
+ protected:
+ PStunAddrsRequestChild* AllocPStunAddrsRequestChild();
+ bool DeallocPStunAddrsRequestChild(PStunAddrsRequestChild* aActor);
+
+ PWebrtcTCPSocketChild* AllocPWebrtcTCPSocketChild(const Maybe<TabId>& tabId);
+ bool DeallocPWebrtcTCPSocketChild(PWebrtcTCPSocketChild* aActor);
+
+ PAltDataOutputStreamChild* AllocPAltDataOutputStreamChild(
+ const nsCString& type, const int64_t& predictedSize,
+ PHttpChannelChild* channel);
+ bool DeallocPAltDataOutputStreamChild(PAltDataOutputStreamChild* aActor);
+
+ PCookieServiceChild* AllocPCookieServiceChild();
+ bool DeallocPCookieServiceChild(PCookieServiceChild*);
+ PFTPChannelChild* AllocPFTPChannelChild(
+ PBrowserChild* aBrowser, const SerializedLoadContext& aSerialized,
+ const FTPChannelCreationArgs& aOpenArgs);
+ bool DeallocPFTPChannelChild(PFTPChannelChild*);
+ PWebSocketChild* AllocPWebSocketChild(PBrowserChild*,
+ const SerializedLoadContext&,
+ const uint32_t&);
+ bool DeallocPWebSocketChild(PWebSocketChild*);
+ PTCPSocketChild* AllocPTCPSocketChild(const nsString& 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 nsCString& aFilter);
+ bool DeallocPUDPSocketChild(PUDPSocketChild*);
+ PSimpleChannelChild* AllocPSimpleChannelChild(const uint32_t& channelId);
+ bool DeallocPSimpleChannelChild(PSimpleChannelChild* child);
+ PTransportProviderChild* AllocPTransportProviderChild();
+ bool DeallocPTransportProviderChild(PTransportProviderChild* aActor);
+ mozilla::ipc::IPCResult RecvAsyncAuthPromptForNestedFrame(
+ const TabId& aNestedFrameId, const nsCString& aUri,
+ const nsString& aRealm, const uint64_t& aCallbackId);
+ 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);
+
+ PClassifierDummyChannelChild* AllocPClassifierDummyChannelChild(
+ nsIURI* aURI, nsIURI* aTopWindowURI, const nsresult& aTopWindowURIResult,
+ const Maybe<LoadInfoArgs>& aLoadInfo);
+
+ bool DeallocPClassifierDummyChannelChild(
+ PClassifierDummyChannelChild* aChannel);
+};
+
+/**
+ * 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.h b/netwerk/ipc/NeckoCommon.h
new file mode 100644
index 0000000000..4de3c05fa8
--- /dev/null
+++ b/netwerk/ipc/NeckoCommon.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_NeckoCommon_h
+#define mozilla_net_NeckoCommon_h
+
+#include "nsXULAppAPI.h"
+#include "prenv.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Preferences.h"
+
+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) = 0;
+ [[nodiscard]] virtual nsresult LogMimeTypeMismatch(
+ const nsACString& aMessageName, bool aWarning, const nsAString& aURL,
+ const nsAString& aContentType) = 0;
+};
+
+} // 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..81887f8b61
--- /dev/null
+++ b/netwerk/ipc/NeckoMessageUtils.h
@@ -0,0 +1,149 @@
+/* -*- 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"
+
+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(Message* aMsg, const Permission& aParam) {
+ WriteParam(aMsg, aParam.origin);
+ WriteParam(aMsg, aParam.type);
+ WriteParam(aMsg, aParam.capability);
+ WriteParam(aMsg, aParam.expireType);
+ WriteParam(aMsg, aParam.expireTime);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ Permission* aResult) {
+ return ReadParam(aMsg, aIter, &aResult->origin) &&
+ ReadParam(aMsg, aIter, &aResult->type) &&
+ ReadParam(aMsg, aIter, &aResult->capability) &&
+ ReadParam(aMsg, aIter, &aResult->expireType) &&
+ ReadParam(aMsg, aIter, &aResult->expireTime);
+ }
+
+ static void Log(const Permission& p, std::wstring* l) {
+ l->append(L"(");
+ LogParam(p.origin, l);
+ l->append(L", ");
+ LogParam(p.capability, l);
+ l->append(L", ");
+ LogParam(p.expireTime, l);
+ l->append(L", ");
+ LogParam(p.expireType, l);
+ l->append(L")");
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::NetAddr> {
+ static void Write(Message* aMsg, const mozilla::net::NetAddr& aParam) {
+ WriteParam(aMsg, aParam.raw.family);
+ if (aParam.raw.family == AF_UNSPEC) {
+ aMsg->WriteBytes(aParam.raw.data, sizeof(aParam.raw.data));
+ } else if (aParam.raw.family == AF_INET) {
+ WriteParam(aMsg, aParam.inet.port);
+ WriteParam(aMsg, aParam.inet.ip);
+ } else if (aParam.raw.family == AF_INET6) {
+ WriteParam(aMsg, aParam.inet6.port);
+ WriteParam(aMsg, aParam.inet6.flowinfo);
+ WriteParam(aMsg, aParam.inet6.ip.u64[0]);
+ WriteParam(aMsg, aParam.inet6.ip.u64[1]);
+ WriteParam(aMsg, 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");
+ aMsg->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(const Message* aMsg, PickleIterator* aIter,
+ mozilla::net::NetAddr* aResult) {
+ if (!ReadParam(aMsg, aIter, &aResult->raw.family)) return false;
+
+ if (aResult->raw.family == AF_UNSPEC) {
+ return aMsg->ReadBytesInto(aIter, &aResult->raw.data,
+ sizeof(aResult->raw.data));
+ } else if (aResult->raw.family == AF_INET) {
+ return ReadParam(aMsg, aIter, &aResult->inet.port) &&
+ ReadParam(aMsg, aIter, &aResult->inet.ip);
+ } else if (aResult->raw.family == AF_INET6) {
+ return ReadParam(aMsg, aIter, &aResult->inet6.port) &&
+ ReadParam(aMsg, aIter, &aResult->inet6.flowinfo) &&
+ ReadParam(aMsg, aIter, &aResult->inet6.ip.u64[0]) &&
+ ReadParam(aMsg, aIter, &aResult->inet6.ip.u64[1]) &&
+ ReadParam(aMsg, aIter, &aResult->inet6.scope_id);
+#if defined(XP_UNIX)
+ } else if (aResult->raw.family == AF_LOCAL) {
+ return aMsg->ReadBytesInto(aIter, &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<nsIHttpChannel::FlashPluginState>
+ : public ContiguousEnumSerializerInclusive<
+ nsIHttpChannel::FlashPluginState, nsIHttpChannel::FlashPluginUnknown,
+ nsIHttpChannel::FlashPluginLastValue> {};
+
+} // 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..f0ee16d855
--- /dev/null
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -0,0 +1,922 @@
+/* -*- 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/FTPChannelParent.h"
+#include "mozilla/net/WebSocketChannelParent.h"
+#include "mozilla/net/WebSocketEventListenerParent.h"
+#include "mozilla/net/DataChannelParent.h"
+#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/ClassifierDummyChannelParent.h"
+#include "mozilla/net/IPCTransportProvider.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/ChromeUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/TabContext.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/network/TCPSocketParent.h"
+#include "mozilla/dom/network/TCPServerSocketParent.h"
+#include "mozilla/dom/network/UDPSocketParent.h"
+#include "mozilla/dom/ServiceWorkerManager.h"
+#include "mozilla/LoadContext.h"
+#include "mozilla/MozPromise.h"
+#include "nsPrintfCString.h"
+#include "nsHTMLDNSPrefetch.h"
+#include "nsEscape.h"
+#include "SerializedLoadContext.h"
+#include "nsAuthInformationHolder.h"
+#include "nsIAuthPromptCallback.h"
+#include "nsINetworkPredictor.h"
+#include "nsINetworkPredictorVerifier.h"
+#include "nsISpeculativeConnect.h"
+#include "nsHttpHandler.h"
+#include "nsNetUtil.h"
+
+using IPC::SerializedLoadContext;
+using mozilla::OriginAttributes;
+using mozilla::dom::BrowserParent;
+using mozilla::dom::ChromeUtils;
+using mozilla::dom::ContentParent;
+using mozilla::dom::ServiceWorkerManager;
+using mozilla::dom::TabContext;
+using mozilla::dom::TCPServerSocketParent;
+using mozilla::dom::TCPSocketParent;
+using mozilla::dom::UDPSocketParent;
+using mozilla::ipc::LoadInfoArgsToLoadInfo;
+using mozilla::ipc::PrincipalInfo;
+using mozilla::net::PTCPServerSocketParent;
+using mozilla::net::PTCPSocketParent;
+using mozilla::net::PUDPSocketParent;
+
+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 Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs) {
+ if (aOptionalLoadInfoArgs.isNothing()) {
+ return nullptr;
+ }
+
+ const LoadInfoArgs& loadInfoArgs = aOptionalLoadInfoArgs.ref();
+ const Maybe<PrincipalInfo>& optionalPrincipalInfo =
+ loadInfoArgs.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());
+}
+
+static already_AddRefed<nsIPrincipal> GetRequestingPrincipal(
+ const FTPChannelCreationArgs& aArgs) {
+ if (aArgs.type() != FTPChannelCreationArgs::TFTPChannelOpenArgs) {
+ return nullptr;
+ }
+
+ const FTPChannelOpenArgs& args = aArgs.get_FTPChannelOpenArgs();
+ 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 nsCString& 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;
+}
+
+PFTPChannelParent* NeckoParent::AllocPFTPChannelParent(
+ PBrowserParent* aBrowser, const SerializedLoadContext& aSerialized,
+ const FTPChannelCreationArgs& aOpenArgs) {
+ nsCOMPtr<nsIPrincipal> requestingPrincipal =
+ GetRequestingPrincipal(aOpenArgs);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ const char* error = CreateChannelLoadContext(
+ aBrowser, Manager(), aSerialized, requestingPrincipal, loadContext);
+ if (error) {
+ printf_stderr(
+ "NeckoParent::AllocPFTPChannelParent: "
+ "FATAL error: %s: KILLING CHILD PROCESS\n",
+ error);
+ return nullptr;
+ }
+ PBOverrideStatus overrideStatus =
+ PBOverrideStatusFromLoadContext(aSerialized);
+ FTPChannelParent* p = new FTPChannelParent(BrowserParent::GetFrom(aBrowser),
+ loadContext, overrideStatus);
+ p->AddRef();
+ return p;
+}
+
+bool NeckoParent::DeallocPFTPChannelParent(PFTPChannelParent* channel) {
+ FTPChannelParent* p = static_cast<FTPChannelParent*>(channel);
+ p->Release();
+ return true;
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPFTPChannelConstructor(
+ PFTPChannelParent* aActor, PBrowserParent* aBrowser,
+ const SerializedLoadContext& aSerialized,
+ const FTPChannelCreationArgs& aOpenArgs) {
+ FTPChannelParent* p = static_cast<FTPChannelParent*>(aActor);
+ if (!p->Init(aOpenArgs)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
+already_AddRefed<PDocumentChannelParent>
+NeckoParent::AllocPDocumentChannelParent(
+ const MaybeDiscarded<BrowsingContext>& aContext,
+ const DocumentChannelCreationArgs& args) {
+ RefPtr<DocumentChannelParent> p = new DocumentChannelParent();
+ return p.forget();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPDocumentChannelConstructor(
+ PDocumentChannelParent* aActor,
+ const MaybeDiscarded<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();
+}
+
+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 nsString& /* 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 nsCString& /* unused */) {
+ RefPtr<UDPSocketParent> p = new UDPSocketParent(this);
+
+ return p.forget().take();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPUDPSocketConstructor(
+ PUDPSocketParent* aActor, nsIPrincipal* aPrincipal,
+ const nsCString& 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 nsCString& aHost, const nsCString& aTrrServer, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes, const uint32_t& aFlags) {
+ RefPtr<DNSRequestHandler> handler = new DNSRequestHandler();
+ RefPtr<DNSRequestParent> actor = new DNSRequestParent(handler);
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPDNSRequestConstructor(
+ PDNSRequestParent* aActor, const nsCString& aHost,
+ const nsCString& aTrrServer, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes, const uint32_t& aFlags) {
+ RefPtr<DNSRequestParent> actor = static_cast<DNSRequestParent*>(aActor);
+ RefPtr<DNSRequestHandler> handler =
+ actor->GetDNSRequest()->AsDNSRequestHandler();
+ handler->DoAsyncResolve(aHost, aTrrServer, aType, aOriginAttributes, aFlags);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvSpeculativeConnect(
+ nsIURI* aURI, nsIPrincipal* aPrincipal, 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 (aAnonymous) {
+ speculator->SpeculativeAnonymousConnect(aURI, principal, nullptr);
+ } else {
+ speculator->SpeculativeConnect(aURI, principal, nullptr);
+ }
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvHTMLDNSPrefetch(
+ const nsString& hostname, const bool& isHttps,
+ const OriginAttributes& aOriginAttributes, const uint32_t& flags) {
+ nsHTMLDNSPrefetch::Prefetch(hostname, isHttps, aOriginAttributes, flags);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvCancelHTMLDNSPrefetch(
+ const nsString& hostname, const bool& isHttps,
+ const OriginAttributes& aOriginAttributes, const uint32_t& flags,
+ const nsresult& reason) {
+ nsHTMLDNSPrefetch::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;
+}
+
+namespace {
+std::map<uint64_t, nsCOMPtr<nsIAuthPromptCallback> >& CallbackMap() {
+ MOZ_ASSERT(NS_IsMainThread());
+ static std::map<uint64_t, nsCOMPtr<nsIAuthPromptCallback> > sCallbackMap;
+ return sCallbackMap;
+}
+} // namespace
+
+NS_IMPL_ISUPPORTS(NeckoParent::NestedFrameAuthPrompt, nsIAuthPrompt2)
+
+NeckoParent::NestedFrameAuthPrompt::NestedFrameAuthPrompt(PNeckoParent* aParent,
+ TabId aNestedFrameId)
+ : mNeckoParent(aParent), mNestedFrameId(aNestedFrameId) {}
+
+NS_IMETHODIMP
+NeckoParent::NestedFrameAuthPrompt::AsyncPromptAuth(
+ nsIChannel* aChannel, nsIAuthPromptCallback* callback, nsISupports*,
+ uint32_t, nsIAuthInformation* aInfo, nsICancelable**) {
+ static uint64_t callbackId = 0;
+ MOZ_ASSERT(XRE_IsParentProcess());
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString spec;
+ if (uri) {
+ rv = uri->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsString realm;
+ rv = aInfo->GetRealm(realm);
+ NS_ENSURE_SUCCESS(rv, rv);
+ callbackId++;
+ if (mNeckoParent->SendAsyncAuthPromptForNestedFrame(mNestedFrameId, spec,
+ realm, callbackId)) {
+ CallbackMap()[callbackId] = callback;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvOnAuthAvailable(
+ const uint64_t& aCallbackId, const nsString& aUser,
+ const nsString& aPassword, const nsString& aDomain) {
+ nsCOMPtr<nsIAuthPromptCallback> callback = CallbackMap()[aCallbackId];
+ if (!callback) {
+ return IPC_OK();
+ }
+ CallbackMap().erase(aCallbackId);
+
+ RefPtr<nsAuthInformationHolder> holder =
+ new nsAuthInformationHolder(0, u""_ns, ""_ns);
+ holder->SetUsername(aUser);
+ holder->SetPassword(aPassword);
+ holder->SetDomain(aDomain);
+
+ callback->OnAuthAvailable(nullptr, holder);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvOnAuthCancelled(
+ const uint64_t& aCallbackId, const bool& aUserCancel) {
+ nsCOMPtr<nsIAuthPromptCallback> callback = CallbackMap()[aCallbackId];
+ if (!callback) {
+ return IPC_OK();
+ }
+ CallbackMap().erase(aCallbackId);
+ callback->OnAuthCancelled(nullptr, aUserCancel);
+ return IPC_OK();
+}
+
+/* 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();
+}
+
+PClassifierDummyChannelParent* NeckoParent::AllocPClassifierDummyChannelParent(
+ nsIURI* aURI, nsIURI* aTopWindowURI, const nsresult& aTopWindowURIResult,
+ const Maybe<LoadInfoArgs>& aLoadInfo) {
+ RefPtr<ClassifierDummyChannelParent> c = new ClassifierDummyChannelParent();
+ return c.forget().take();
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvPClassifierDummyChannelConstructor(
+ PClassifierDummyChannelParent* aActor, nsIURI* aURI, nsIURI* aTopWindowURI,
+ const nsresult& aTopWindowURIResult, const Maybe<LoadInfoArgs>& aLoadInfo) {
+ ClassifierDummyChannelParent* p =
+ static_cast<ClassifierDummyChannelParent*>(aActor);
+
+ if (NS_WARN_IF(!aURI)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsresult rv = LoadInfoArgsToLoadInfo(aLoadInfo, getter_AddRefs(loadInfo));
+ if (NS_WARN_IF(NS_FAILED(rv)) || !loadInfo) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ p->Init(aURI, aTopWindowURI, aTopWindowURIResult, loadInfo);
+ return IPC_OK();
+}
+
+bool NeckoParent::DeallocPClassifierDummyChannelParent(
+ PClassifierDummyChannelParent* aActor) {
+ RefPtr<ClassifierDummyChannelParent> c =
+ dont_AddRef(static_cast<ClassifierDummyChannelParent*>(aActor));
+ MOZ_ASSERT(c);
+ return true;
+}
+
+mozilla::ipc::IPCResult NeckoParent::RecvInitSocketProcessBridge(
+ InitSocketProcessBridgeResolver&& aResolver) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Endpoint<PSocketProcessBridgeChild> invalidEndpoint;
+ if (NS_WARN_IF(mSocketProcessBridgeInited)) {
+ aResolver(std::move(invalidEndpoint));
+ return IPC_OK();
+ }
+
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (NS_WARN_IF(!parent)) {
+ aResolver(std::move(invalidEndpoint));
+ return IPC_OK();
+ }
+
+ Endpoint<PSocketProcessBridgeParent> parentEndpoint;
+ Endpoint<PSocketProcessBridgeChild> childEndpoint;
+ if (NS_WARN_IF(NS_FAILED(PSocketProcessBridge::CreateEndpoints(
+ parent->OtherPid(), Manager()->OtherPid(), &parentEndpoint,
+ &childEndpoint)))) {
+ aResolver(std::move(invalidEndpoint));
+ return IPC_OK();
+ }
+
+ if (NS_WARN_IF(!parent->SendInitSocketProcessBridgeParent(
+ Manager()->OtherPid(), std::move(parentEndpoint)))) {
+ aResolver(std::move(invalidEndpoint));
+ return IPC_OK();
+ }
+
+ aResolver(std::move(childEndpoint));
+ mSocketProcessBridgeInited = true;
+ 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, 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 nsCOMPtr<nsIInputStream>& aStream) {
+ aResolver(aStream);
+ },
+ [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(nullptr);
+ });
+
+ return IPC_OK();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/NeckoParent.h b/netwerk/ipc/NeckoParent.h
new file mode 100644
index 0000000000..1137c94b01
--- /dev/null
+++ b/netwerk/ipc/NeckoParent.h
@@ -0,0 +1,252 @@
+/* -*- 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:
+ NeckoParent();
+ virtual ~NeckoParent() = default;
+
+ [[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);
+ }
+
+ /*
+ * This implementation of nsIAuthPrompt2 is used for nested remote iframes
+ * that want an auth prompt. This class lives in the parent process and
+ * informs the NeckoChild that we want an auth prompt, which forwards the
+ * request to the BrowserParent in the remote iframe that contains the nested
+ * iframe
+ */
+ class NestedFrameAuthPrompt final : public nsIAuthPrompt2 {
+ ~NestedFrameAuthPrompt() {}
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NestedFrameAuthPrompt(PNeckoParent* aParent, TabId aNestedFrameId);
+
+ NS_IMETHOD PromptAuth(nsIChannel*, uint32_t, nsIAuthInformation*,
+ bool*) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD AsyncPromptAuth(nsIChannel* aChannel,
+ nsIAuthPromptCallback* callback, nsISupports*,
+ uint32_t, nsIAuthInformation* aInfo,
+ nsICancelable**) override;
+
+ protected:
+ PNeckoParent* mNeckoParent;
+ TabId mNestedFrameId;
+ };
+
+ protected:
+ 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 nsCString& type, const int64_t& predictedSize,
+ PHttpChannelParent* channel);
+ bool DeallocPAltDataOutputStreamParent(PAltDataOutputStreamParent* aActor);
+
+ bool DeallocPCookieServiceParent(PCookieServiceParent*);
+ PFTPChannelParent* AllocPFTPChannelParent(
+ PBrowserParent* aBrowser, const SerializedLoadContext& aSerialized,
+ const FTPChannelCreationArgs& aOpenArgs);
+ virtual mozilla::ipc::IPCResult RecvPFTPChannelConstructor(
+ PFTPChannelParent* aActor, PBrowserParent* aBrowser,
+ const SerializedLoadContext& aSerialized,
+ const FTPChannelCreationArgs& aOpenArgs) override;
+ bool DeallocPFTPChannelParent(PFTPChannelParent*);
+ PWebSocketParent* AllocPWebSocketParent(
+ PBrowserParent* browser, const SerializedLoadContext& aSerialized,
+ const uint32_t& aSerial);
+ bool DeallocPWebSocketParent(PWebSocketParent*);
+ PTCPSocketParent* AllocPTCPSocketParent(const nsString& 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 nsCString& aFilter);
+ virtual mozilla::ipc::IPCResult RecvPUDPSocketConstructor(
+ PUDPSocketParent*, nsIPrincipal* aPrincipal,
+ const nsCString& aFilter) override;
+ bool DeallocPUDPSocketParent(PUDPSocketParent*);
+ already_AddRefed<PDNSRequestParent> AllocPDNSRequestParent(
+ const nsCString& aHost, const nsCString& aTrrServer,
+ const uint16_t& aType, const OriginAttributes& aOriginAttributes,
+ const uint32_t& aFlags);
+ virtual mozilla::ipc::IPCResult RecvPDNSRequestConstructor(
+ PDNSRequestParent* actor, const nsCString& hostName,
+ const nsCString& trrServer, const uint16_t& type,
+ const OriginAttributes& aOriginAttributes,
+ const uint32_t& flags) override;
+ mozilla::ipc::IPCResult RecvSpeculativeConnect(nsIURI* aURI,
+ nsIPrincipal* aPrincipal,
+ const bool& aAnonymous);
+ mozilla::ipc::IPCResult RecvHTMLDNSPrefetch(
+ const nsString& hostname, const bool& isHttps,
+ const OriginAttributes& aOriginAttributes, const uint32_t& flags);
+ mozilla::ipc::IPCResult RecvCancelHTMLDNSPrefetch(
+ const nsString& hostname, const bool& isHttps,
+ const OriginAttributes& aOriginAttributes, const uint32_t& 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;
+
+ PSimpleChannelParent* AllocPSimpleChannelParent(const uint32_t& channelId);
+ bool DeallocPSimpleChannelParent(PSimpleChannelParent* parent);
+
+ 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);
+
+ mozilla::ipc::IPCResult RecvOnAuthAvailable(const uint64_t& aCallbackId,
+ const nsString& aUser,
+ const nsString& aPassword,
+ const nsString& aDomain);
+ mozilla::ipc::IPCResult RecvOnAuthCancelled(const uint64_t& aCallbackId,
+ const bool& aUserCancel);
+
+ /* 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, GetPageThumbStreamResolver&& aResolve);
+
+ PClassifierDummyChannelParent* AllocPClassifierDummyChannelParent(
+ nsIURI* aURI, nsIURI* aTopWindowURI, const nsresult& aTopWindowURIResult,
+ const Maybe<LoadInfoArgs>& aLoadInfo);
+
+ bool DeallocPClassifierDummyChannelParent(
+ PClassifierDummyChannelParent* aParent);
+
+ virtual mozilla::ipc::IPCResult RecvPClassifierDummyChannelConstructor(
+ PClassifierDummyChannelParent* aActor, nsIURI* aURI,
+ nsIURI* aTopWindowURI, const nsresult& aTopWindowURIResult,
+ const Maybe<LoadInfoArgs>& aLoadInfo) override;
+
+ mozilla::ipc::IPCResult RecvInitSocketProcessBridge(
+ InitSocketProcessBridgeResolver&& aResolver);
+
+ 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..2dba003fe4
--- /dev/null
+++ b/netwerk/ipc/PDataChannel.ipdl
@@ -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 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 {
+
+async refcounted 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..b6fdbb7b20
--- /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 nsHttpHeaderArray from "nsHttpHeaderArray.h";
+using mozilla::net::NetAddr from "mozilla/net/DNS.h";
+
+namespace mozilla {
+namespace net {
+
+refcounted protocol PDocumentChannel
+{
+ manager PNecko;
+
+parent:
+
+ async Cancel(nsresult status);
+
+ 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 aSwitchedProcess);
+
+ // 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..cc5f456166
--- /dev/null
+++ b/netwerk/ipc/PFileChannel.ipdl
@@ -0,0 +1,27 @@
+/* -*- 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
+ */
+async refcounted 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..5668fed06a
--- /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 {
+
+refcounted 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..624645e2a1
--- /dev/null
+++ b/netwerk/ipc/PNecko.ipdl
@@ -0,0 +1,190 @@
+/* -*- 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;
+include protocol PFTPChannel;
+include protocol PWebSocket;
+include protocol PWebSocketEventListener;
+include protocol PTCPSocket;
+include protocol PTCPServerSocket;
+include protocol PUDPSocket;
+include protocol PDNSRequest;
+include protocol PFileDescriptorSet;
+include protocol PDataChannel;
+include protocol PSimpleChannel;
+include protocol PTransportProvider;
+include protocol PChildToParentStream; //FIXME: bug #792908
+include protocol PParentToChildStream; //FIXME: bug #792908
+include protocol PStunAddrsRequest;
+include protocol PFileChannel;
+include protocol PClassifierDummyChannel;
+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 refcounted class nsIInputStream from "mozilla/ipc/IPCStreamUtils.h";
+using refcounted class nsIURI from "mozilla/ipc/URIUtils.h";
+using refcounted class nsIPrincipal from "nsIPrincipal.h";
+
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+nested(upto inside_cpow) sync protocol PNecko
+{
+ manager PContent;
+ manages PHttpChannel;
+ manages PCookieService;
+ manages PFTPChannel;
+ manages PWebSocket;
+ manages PWebSocketEventListener;
+ manages PTCPSocket;
+ manages PTCPServerSocket;
+ manages PUDPSocket;
+ manages PDNSRequest;
+ manages PDataChannel;
+ manages PSimpleChannel;
+ manages PFileChannel;
+ manages PTransportProvider;
+ manages PAltDataOutputStream;
+ manages PStunAddrsRequest;
+ manages PClassifierDummyChannel;
+ manages PWebrtcTCPSocket;
+ manages PDocumentChannel;
+
+parent:
+ async __delete__();
+
+ nested(inside_cpow) async PCookieService();
+ async PHttpChannel(nullable PBrowser browser,
+ SerializedLoadContext loadContext,
+ HttpChannelCreationArgs args);
+ async PFTPChannel(nullable PBrowser browser, SerializedLoadContext loadContext,
+ FTPChannelCreationArgs args);
+
+ async PWebSocket(nullable PBrowser browser, SerializedLoadContext loadContext,
+ uint32_t aSerialID);
+ async PTCPServerSocket(uint16_t localPort, uint16_t backlog, bool useArrayBuffers);
+ async PUDPSocket(nsIPrincipal principal, nsCString filter);
+
+ async PDNSRequest(nsCString hostName, nsCString trrServer, uint16_t type,
+ OriginAttributes originAttributes, uint32_t flags);
+
+ async PDocumentChannel(MaybeDiscardedBrowsingContext browsingContext,
+ DocumentChannelCreationArgs args);
+
+ async PWebSocketEventListener(uint64_t aInnerWindowID);
+
+ /* Predictor Methods */
+ async PredPredict(nsIURI targetURI, nsIURI sourceURI,
+ uint32_t reason, OriginAttributes originAttributes,
+ bool hasVerifier);
+ async PredLearn(nsIURI targetURI, nsIURI sourceURI,
+ uint32_t reason, OriginAttributes originAttributes);
+ async PredReset();
+
+ async SpeculativeConnect(nsIURI uri, nsIPrincipal principal, bool anonymous);
+ async HTMLDNSPrefetch(nsString hostname, bool isHttps,
+ OriginAttributes originAttributes, uint32_t flags);
+ async CancelHTMLDNSPrefetch(nsString hostname, bool isHttps,
+ OriginAttributes originAttributes,
+ uint32_t 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);
+ async PSimpleChannel(uint32_t channelId);
+ async PFileChannel(uint32_t channelId);
+
+ async PClassifierDummyChannel(nsIURI uri, nsIURI aTopWindowURI,
+ nsresult aTopWindowURIResult,
+ LoadInfoArgs? loadInfo);
+
+ /**
+ * These are called from the child with the results of the auth prompt.
+ * callbackId is the id that was passed in PBrowser::AsyncAuthPrompt,
+ * corresponding to an nsIAuthPromptCallback
+ */
+ async OnAuthAvailable(uint64_t callbackId, nsString user,
+ nsString password, nsString domain);
+ async OnAuthCancelled(uint64_t callbackId, bool userCancel);
+
+ 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(nsIURI uri) returns (nsIInputStream stream);
+ async GetExtensionFD(nsIURI uri) returns (FileDescriptor fd);
+
+ async InitSocketProcessBridge()
+ returns (Endpoint<PSocketProcessBridgeChild> endpoint);
+
+ async EnsureHSTSData()
+ returns (bool result);
+
+ /**
+ * Page thumbnails remote resource loading
+ */
+ async GetPageThumbStream(nsIURI uri) returns (nsIInputStream stream);
+
+child:
+ /*
+ * Bring up the http auth prompt for a nested remote mozbrowser.
+ * NestedFrameId is the id corresponding to the PBrowser. It is the same id
+ * that was passed to the PBrowserOrId param in to the PHttpChannel constructor
+ */
+ async AsyncAuthPromptForNestedFrame(TabId nestedFrameId, nsCString uri,
+ nsString realm, uint64_t callbackId);
+
+ /* Predictor Methods */
+ async PredOnPredictPrefetch(nsIURI uri, uint32_t httpStatus);
+ async PredOnPredictPreconnect(nsIURI uri);
+ async PredOnPredictDNS(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.
+ prio(mediumhigh) async NetworkChangeNotification(nsCString type);
+
+ async PTransportProvider();
+
+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/PProxyConfigLookup.ipdl b/netwerk/ipc/PProxyConfigLookup.ipdl
new file mode 100644
index 0000000000..49cb69071a
--- /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 refcounted 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..74a113e301
--- /dev/null
+++ b/netwerk/ipc/PSimpleChannel.ipdl
@@ -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 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 {
+
+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..b885d6a6a7
--- /dev/null
+++ b/netwerk/ipc/PSocketProcess.ipdl
@@ -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/. */
+
+include protocol PDNSRequest;
+include protocol PSocketProcessBridge;
+include protocol PProfiler;
+include protocol PWebrtcTCPSocket;
+include protocol PHttpTransaction;
+include protocol PHttpConnectionMgr;
+include protocol PFileDescriptorSet;
+include protocol PChildToParentStream;
+include protocol PParentToChildStream;
+include protocol PInputChannelThrottleQueue;
+include protocol PBackground;
+include protocol PAltService;
+include protocol PAltSvcTransaction;
+include protocol PTRRService;
+include protocol PProxyConfigLookup;
+include protocol PNativeDNSResolverOverride;
+include protocol PRemoteLazyInputStream;
+
+include MemoryReportTypes;
+include NeckoChannelParams;
+include PrefsTypes;
+include PSMIPCTypes;
+
+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";
+using refcounted 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";
+
+namespace mozilla {
+namespace net {
+
+struct HttpHandlerInitArgs {
+ bool mFastOpenSupported;
+ 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;
+ FileDescriptor? mSandboxBroker;
+};
+
+sync protocol PSocketProcess
+{
+ manages PDNSRequest;
+ manages PWebrtcTCPSocket;
+ manages PFileDescriptorSet;
+ manages PHttpTransaction;
+ manages PHttpConnectionMgr;
+ manages PChildToParentStream;
+ manages PParentToChildStream;
+ manages PInputChannelThrottleQueue;
+ manages PAltService;
+ manages PAltSvcTransaction;
+ manages PTRRService;
+ manages PProxyConfigLookup;
+ manages PNativeDNSResolverOverride;
+ manages PRemoteLazyInputStream;
+
+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 PChildToParentStream();
+ async ObserveHttpActivity(HttpActivityArgs aActivityArgs,
+ uint32_t aActivityType,
+ uint32_t aActivitySubtype,
+ PRTime aTimestamp,
+ uint64_t aExtraSizeData,
+ nsCString aExtraStringData);
+ async InitBackground(Endpoint<PBackgroundParent> aEndpoint);
+ async PAltService();
+ sync GetTLSClientCert(nsCString aHostName,
+ OriginAttributes aOriginAttributes,
+ int32_t aPort,
+ uint32_t aProviderFlags,
+ uint32_t aProviderTlsFlags,
+ ByteArray aServerCert,
+ ByteArray? aClientCert,
+ ByteArray[] aCollectedCANames)
+ returns (bool aSucceeded, ByteArray aOutCert, ByteArray aOutKey, ByteArray[] aBuiltChain);
+ async PProxyConfigLookup(nsIURI aUri, uint32_t aFlags);
+ async CachePushCheck(nsIURI aPushedURL,
+ OriginAttributes aOriginAttributes,
+ nsCString aRequestString)
+ returns (bool aAccepted);
+
+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);
+ // test-only
+ async SocketProcessTelemetryPing();
+
+ async PHttpTransaction();
+ async PParentToChildStream();
+ async PHttpConnectionMgr(HttpHandlerInitArgs aArgs);
+ async UpdateDeviceModelId(nsCString aModelId);
+
+ async OnHttpActivityDistributorActivated(bool aIsActivated);
+ async PInputChannelThrottleQueue(uint32_t meanBytesPerSecond,
+ uint32_t maxBytesPerSecond);
+ async PAltSvcTransaction(HttpConnectionInfoCloneArgs aConnInfo,
+ uint32_t aCaps);
+ async ClearSessionCache();
+ async PTRRService(bool aCaptiveIsPassed,
+ bool aParentalControlEnabled,
+ nsCString[] aDNSSuffixList);
+ async PNativeDNSResolverOverride();
+ async NotifyObserver(nsCString aTopic, nsString aData);
+
+ async PRemoteLazyInputStream(nsID aID, uint64_t aSize);
+
+ async GetSocketData()
+ returns (SocketDataArgs data);
+ async GetDNSCacheEntries()
+ returns (DNSCacheEntries[] entries);
+ async GetHttpConnectionData()
+ returns (HttpRetParams[] params);
+
+both:
+ async PFileDescriptorSet(FileDescriptor fd);
+ async PDNSRequest(nsCString hostName, nsCString trrServer, uint16_t type,
+ OriginAttributes originAttributes, uint32_t flags);
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/PSocketProcessBridge.ipdl b/netwerk/ipc/PSocketProcessBridge.ipdl
new file mode 100644
index 0000000000..b57eca181b
--- /dev/null
+++ b/netwerk/ipc/PSocketProcessBridge.ipdl
@@ -0,0 +1,30 @@
+/* -*- 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 PBackground;
+
+namespace mozilla {
+namespace net {
+
+/**
+ * PSocketProcessBridge is the IPC protocol between content process and
+ * socket process. This protocol allows socket process to send data to
+ * content process bypassing parent process.
+ * Once created, PSocketProcessBridgeChild is the actor that lives in
+ * content process and PSocketProcessBridgeParent lives in
+ * socket process.
+ */
+nested(upto inside_cpow) sync protocol PSocketProcessBridge
+{
+
+parent:
+ async InitBackground(Endpoint<PBackgroundParent> aEndpoint);
+both:
+ async Test();
+};
+
+}
+}
diff --git a/netwerk/ipc/ParentChannelWrapper.cpp b/netwerk/ipc/ParentChannelWrapper.cpp
new file mode 100644
index 0000000000..57b9fe37c1
--- /dev/null
+++ b/netwerk/ipc/ParentChannelWrapper.cpp
@@ -0,0 +1,107 @@
+/* -*- 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/UrlClassifierCommon.h"
+#include "nsIRedirectChannelRegistrar.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::NotifyFlashPluginStateChanged(
+ nsIHttpChannel::FlashPluginState aState) {
+ // For now, only HttpChannel use this attribute.
+ RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(mChannel);
+ if (httpChannel) {
+ httpChannel->SetFlashPluginState(aState);
+ }
+ 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..b9856a2913
--- /dev/null
+++ b/netwerk/ipc/ParentProcessDocumentChannel.cpp
@@ -0,0 +1,300 @@
+/* -*- 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/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"
+
+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 aIsXFOError)
+ : DocumentChannel(aLoadState, aLoadInfo, aLoadFlags, aCacheKey,
+ aUriModified, aIsXFOError) {
+ 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) {
+ 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()) {
+ 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), mAsyncOpenTime, mTiming,
+ std::move(initialClientInfo), Some(mUriModified), Some(mIsXFOError),
+ 0 /* ProcessId */, &rv);
+ } else {
+ promise = mDocumentLoadListener->OpenObject(
+ mLoadState, mCacheKey, Some(mChannelId), mAsyncOpenTime, mTiming,
+ std::move(initialClientInfo), InnerWindowIDForExtantDoc(docShell),
+ mLoadFlags, mLoadInfo->InternalContentPolicyType(),
+ UserActivation::IsHandlingUserInput(), 0 /* ProcessId */,
+ 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) {
+ // 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)
+ ->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.mSwitchedProcess) {
+ self->DisconnectChildListeners(aRejectValue.mStatus,
+ aRejectValue.mLoadGroupStatus);
+ }
+ self->RemoveObserver();
+ });
+ return NS_OK;
+}
+
+NS_IMETHODIMP ParentProcessDocumentChannel::Cancel(nsresult aStatus) {
+ LOG(("ParentProcessDocumentChannel Cancel [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(aStatus);
+
+ 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..5331be8d8b
--- /dev/null
+++ b/netwerk/ipc/ParentProcessDocumentChannel.h
@@ -0,0 +1,55 @@
+/* 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 "ProtocolUtils.h"
+#include "mozilla/net/DocumentChannel.h"
+#include "mozilla/net/DocumentLoadListener.h"
+#include "nsIObserver.h"
+
+namespace mozilla {
+namespace net {
+
+class ParentProcessDocumentChannel : public DocumentChannel,
+ public nsIAsyncVerifyRedirectCallback,
+ public nsIObserver {
+ public:
+ ParentProcessDocumentChannel(nsDocShellLoadState* aLoadState,
+ class LoadInfo* aLoadInfo,
+ nsLoadFlags aLoadFlags, uint32_t aCacheKey,
+ bool aUriModified, bool aIsXFOError);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSIOBSERVER
+
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+ NS_IMETHOD Cancel(nsresult aStatusCode) override;
+
+ RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
+ RedirectToRealChannel(
+ nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&&
+ aStreamFilterEndpoints,
+ uint32_t aRedirectFlags, uint32_t aLoadFlags);
+
+ 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/ProxyConfigLookup.cpp b/netwerk/ipc/ProxyConfigLookup.cpp
new file mode 100644
index 0000000000..58f6088d31
--- /dev/null
+++ b/netwerk/ipc/ProxyConfigLookup.cpp
@@ -0,0 +1,98 @@
+/* -*- 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"
+
+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..da935c9028
--- /dev/null
+++ b/netwerk/ipc/ProxyConfigLookup.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ProxyConfigLookup_h
+#define mozilla_net_ProxyConfigLookup_h
+
+#include <functional>
+#include "nsIProtocolProxyCallback.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/SocketProcessBridgeChild.cpp b/netwerk/ipc/SocketProcessBridgeChild.cpp
new file mode 100644
index 0000000000..01a5a7ad35
--- /dev/null
+++ b/netwerk/ipc/SocketProcessBridgeChild.cpp
@@ -0,0 +1,180 @@
+/* -*- 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/dom/ContentChild.h"
+#include "mozilla/net/NeckoChild.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Preferences.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(std::move(aEndpoint));
+ if (sSocketProcessBridgeChild->Inited()) {
+ return true;
+ }
+
+ sSocketProcessBridgeChild = nullptr;
+ return false;
+}
+
+// static
+already_AddRefed<SocketProcessBridgeChild>
+SocketProcessBridgeChild::GetSingleton() {
+ RefPtr<SocketProcessBridgeChild> child = sSocketProcessBridgeChild;
+ return child.forget();
+}
+
+static bool SocketProcessEnabled() {
+ static bool sInited = false;
+ static bool sSocketProcessEnabled = false;
+ if (!sInited) {
+ sSocketProcessEnabled = Preferences::GetBool("network.process.enabled") &&
+ XRE_IsContentProcess();
+ sInited = true;
+ }
+
+ return sSocketProcessEnabled;
+}
+
+// static
+RefPtr<SocketProcessBridgeChild::GetPromise>
+SocketProcessBridgeChild::GetSocketProcessBridge() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!SocketProcessEnabled()) {
+ 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(
+ Endpoint<PSocketProcessBridgeChild>&& aEndpoint)
+ : mShuttingDown(false) {
+ LOG(("CONSTRUCT SocketProcessBridgeChild::SocketProcessBridgeChild\n"));
+
+ mInited = aEndpoint.Bind(this);
+ if (!mInited) {
+ MOZ_ASSERT(false, "Bind failed!");
+ return;
+ }
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->AddObserver(this, "content-child-shutdown", false);
+ }
+
+ mSocketProcessPid = aEndpoint.OtherPid();
+}
+
+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"));
+ 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..385d8e8ac6
--- /dev/null
+++ b/netwerk/ipc/SocketProcessBridgeChild.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_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();
+ typedef MozPromise<RefPtr<SocketProcessBridgeChild>, nsCString, false>
+ GetPromise;
+ static RefPtr<GetPromise> GetSocketProcessBridge();
+
+ mozilla::ipc::IPCResult RecvTest();
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ void DeferredDestroy();
+ bool IsShuttingDown() const { return mShuttingDown; };
+ bool Inited() const { return mInited; };
+ ProcessId SocketProcessPid() const { return mSocketProcessPid; };
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SocketProcessBridgeChild);
+ static bool Create(Endpoint<PSocketProcessBridgeChild>&& aEndpoint);
+ explicit SocketProcessBridgeChild(
+ Endpoint<PSocketProcessBridgeChild>&& aEndpoint);
+ virtual ~SocketProcessBridgeChild();
+
+ static StaticRefPtr<SocketProcessBridgeChild> sSocketProcessBridgeChild;
+ bool mShuttingDown;
+ bool mInited = false;
+ 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..f28fa98925
--- /dev/null
+++ b/netwerk/ipc/SocketProcessBridgeParent.cpp
@@ -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/. */
+
+#include "SocketProcessBridgeParent.h"
+#include "SocketProcessLogging.h"
+
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "SocketProcessChild.h"
+
+namespace mozilla {
+namespace net {
+
+SocketProcessBridgeParent::SocketProcessBridgeParent(
+ ProcessId aId, Endpoint<PSocketProcessBridgeParent>&& aEndpoint)
+ : mId(aId), mClosed(false) {
+ LOG((
+ "CONSTRUCT SocketProcessBridgeParent::SocketProcessBridgeParent mId=%d\n",
+ mId));
+ MOZ_COUNT_CTOR(SocketProcessBridgeParent);
+ DebugOnly<bool> ok = aEndpoint.Bind(this);
+ MOZ_ASSERT(ok);
+}
+
+SocketProcessBridgeParent::~SocketProcessBridgeParent() {
+ LOG(("DESTRUCT SocketProcessBridgeParent::SocketProcessBridgeParent mId=%d\n",
+ mId));
+ MOZ_COUNT_DTOR(SocketProcessBridgeParent);
+}
+
+mozilla::ipc::IPCResult SocketProcessBridgeParent::RecvTest() {
+ LOG(("SocketProcessBridgeParent::RecvTest\n"));
+ Unused << SendTest();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessBridgeParent::RecvInitBackground(
+ Endpoint<PBackgroundParent>&& aEndpoint) {
+ LOG(("SocketProcessBridgeParent::RecvInitBackground mId=%d\n", mId));
+ if (!ipc::BackgroundParent::Alloc(nullptr, std::move(aEndpoint))) {
+ return IPC_FAIL(this, "BackgroundParent::Alloc failed");
+ }
+
+ return IPC_OK();
+}
+
+void SocketProcessBridgeParent::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("SocketProcessBridgeParent::ActorDestroy mId=%d\n", mId));
+
+ mClosed = true;
+ GetCurrentSerialEventTarget()->Dispatch(
+ NewRunnableMethod("net::SocketProcessBridgeParent::DeferredDestroy", this,
+ &SocketProcessBridgeParent::DeferredDestroy));
+}
+
+void SocketProcessBridgeParent::DeferredDestroy() {
+ if (SocketProcessChild* child = SocketProcessChild::GetSingleton()) {
+ child->DestroySocketProcessBridgeParent(mId);
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/SocketProcessBridgeParent.h b/netwerk/ipc/SocketProcessBridgeParent.h
new file mode 100644
index 0000000000..f35559d464
--- /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)
+
+ explicit SocketProcessBridgeParent(
+ ProcessId aId, Endpoint<PSocketProcessBridgeParent>&& aEndpoint);
+
+ mozilla::ipc::IPCResult RecvTest();
+ mozilla::ipc::IPCResult RecvInitBackground(
+ Endpoint<PBackgroundParent>&& aEndpoint);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ void DeferredDestroy();
+
+ bool Closed() const { return mClosed; }
+
+ private:
+ ~SocketProcessBridgeParent();
+
+ ProcessId mId;
+ bool mClosed;
+};
+
+} // 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..3d422a0f7c
--- /dev/null
+++ b/netwerk/ipc/SocketProcessChild.cpp
@@ -0,0 +1,618 @@
+/* -*- 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/dom/MemoryReportRequest.h"
+#include "mozilla/ipc/CrashReporterClient.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/FileDescriptorSetChild.h"
+#include "mozilla/ipc/IPCStreamAlloc.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/TRRServiceChild.h"
+#include "mozilla/ipc/PChildToParentStreamChild.h"
+#include "mozilla/ipc/PParentToChildStreamChild.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RemoteLazyInputStreamChild.h"
+#include "mozilla/Telemetry.h"
+#include "nsDebugImpl.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpHandler.h"
+#include "nsIDNSService.h"
+#include "nsIHttpActivityObserver.h"
+#include "nsNetUtil.h"
+#include "nsNSSComponent.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadManager.h"
+#include "ProcessUtils.h"
+#include "SocketProcessBridgeParent.h"
+
+#if defined(XP_WIN)
+# include <process.h>
+#else
+# include <unistd.h>
+#endif
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# include "mozilla/Sandbox.h"
+#endif
+
+#ifdef MOZ_GECKO_PROFILER
+# include "ChildProfilerController.h"
+#endif
+
+#ifdef MOZ_WEBRTC
+# include "mozilla/net/WebrtcTCPSocketChild.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+using namespace ipc;
+
+SocketProcessChild* sSocketProcessChild;
+
+SocketProcessChild::SocketProcessChild()
+ : mShuttingDown(false), mMutex("SocketProcessChild::mMutex") {
+ 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
+
+bool SocketProcessChild::Init(base::ProcessId aParentPid,
+ const char* aParentBuildID, MessageLoop* aIOLoop,
+ UniquePtr<IPC::Channel> aChannel) {
+ if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) {
+ return false;
+ }
+ if (NS_WARN_IF(!Open(std::move(aChannel), aParentPid, aIOLoop))) {
+ 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;
+ }
+
+ BackgroundChild::Startup();
+ 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;
+ }
+
+ return true;
+}
+
+void SocketProcessChild::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("SocketProcessChild::ActorDestroy\n"));
+
+ mShuttingDown = true;
+
+ if (AbnormalShutdown == aWhy) {
+ NS_WARNING("Shutting down Socket process early due to a crash!");
+ ProcessChild::QuickExit();
+ }
+
+#ifdef MOZ_GECKO_PROFILER
+ if (mProfilerController) {
+ mProfilerController->Shutdown();
+ mProfilerController = nullptr;
+ }
+#endif
+
+ CrashReporterClient::DestroySingleton();
+ XRE_ShutdownChildProcess();
+}
+
+void SocketProcessChild::CleanUp() {
+ LOG(("SocketProcessChild::CleanUp\n"));
+
+ for (auto iter = mSocketProcessBridgeParentMap.Iter(); !iter.Done();
+ iter.Next()) {
+ if (!iter.Data()->Closed()) {
+ iter.Data()->Close();
+ }
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ mBackgroundDataBridgeMap.Clear();
+ }
+ NS_ShutdownXPCOM(nullptr);
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvInit(
+ const SocketPorcessInitAttributes& aAttributes) {
+ Unused << RecvSetOffline(aAttributes.mOffline());
+ Unused << RecvSetConnectivity(aAttributes.mConnectivity());
+ if (aAttributes.mInitSandbox()) {
+ Unused << RecvInitLinuxSandbox(aAttributes.mSandboxBroker());
+ }
+ 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.Get(aContentProcessId, nullptr));
+
+ mSocketProcessBridgeParentMap.Put(
+ aContentProcessId, MakeRefPtr<SocketProcessBridgeParent>(
+ aContentProcessId, std::move(aEndpoint)));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvInitProfiler(
+ Endpoint<PProfilerChild>&& aEndpoint) {
+#ifdef MOZ_GECKO_PROFILER
+ mProfilerController =
+ mozilla::ChildProfilerController::Create(std::move(aEndpoint));
+#endif
+ return IPC_OK();
+}
+
+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();
+}
+
+PFileDescriptorSetChild* SocketProcessChild::AllocPFileDescriptorSetChild(
+ const FileDescriptor& aFD) {
+ return new FileDescriptorSetChild(aFD);
+}
+
+bool SocketProcessChild::DeallocPFileDescriptorSetChild(
+ PFileDescriptorSetChild* aActor) {
+ delete aActor;
+ return true;
+}
+
+PChildToParentStreamChild*
+SocketProcessChild::AllocPChildToParentStreamChild() {
+ MOZ_CRASH("PChildToParentStreamChild actors should be manually constructed!");
+}
+
+bool SocketProcessChild::DeallocPChildToParentStreamChild(
+ PChildToParentStreamChild* aActor) {
+ delete aActor;
+ return true;
+}
+
+PParentToChildStreamChild*
+SocketProcessChild::AllocPParentToChildStreamChild() {
+ return mozilla::ipc::AllocPParentToChildStreamChild();
+}
+
+bool SocketProcessChild::DeallocPParentToChildStreamChild(
+ PParentToChildStreamChild* aActor) {
+ delete aActor;
+ return true;
+}
+
+PChildToParentStreamChild*
+SocketProcessChild::SendPChildToParentStreamConstructor(
+ PChildToParentStreamChild* aActor) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return PSocketProcessChild::SendPChildToParentStreamConstructor(aActor);
+}
+
+PFileDescriptorSetChild* SocketProcessChild::SendPFileDescriptorSetConstructor(
+ const FileDescriptor& aFD) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return PSocketProcessChild::SendPFileDescriptorSetConstructor(aFD);
+}
+
+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 nsCString& aModelId) {
+ MOZ_ASSERT(gHttpHandler);
+ gHttpHandler->SetDeviceModelId(aModelId);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+SocketProcessChild::RecvOnHttpActivityDistributorActivated(
+ const bool& aIsActivated) {
+ if (nsCOMPtr<nsIHttpActivityObserver> distributor =
+ services::GetHttpActivityDistributor()) {
+ distributor->SetIsActive(aIsActivated);
+ }
+ 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 nsCString& aHost, const nsCString& aTrrServer, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes, const uint32_t& aFlags) {
+ RefPtr<DNSRequestHandler> handler = new DNSRequestHandler();
+ RefPtr<DNSRequestChild> actor = new DNSRequestChild(handler);
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvPDNSRequestConstructor(
+ PDNSRequestChild* aActor, const nsCString& aHost,
+ const nsCString& aTrrServer, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes, const uint32_t& aFlags) {
+ RefPtr<DNSRequestChild> actor = static_cast<DNSRequestChild*>(aActor);
+ RefPtr<DNSRequestHandler> handler =
+ actor->GetDNSRequest()->AsDNSRequestHandler();
+ handler->DoAsyncResolve(aHost, aTrrServer, aType, aOriginAttributes, aFlags);
+ return IPC_OK();
+}
+
+void SocketProcessChild::AddDataBridgeToMap(
+ uint64_t aChannelId, BackgroundDataBridgeParent* aActor) {
+ ipc::AssertIsOnBackgroundThread();
+ MutexAutoLock lock(mMutex);
+ mBackgroundDataBridgeMap.Put(aChannelId, aActor);
+}
+
+void SocketProcessChild::RemoveDataBridgeFromMap(uint64_t aChannelId) {
+ ipc::AssertIsOnBackgroundThread();
+ MutexAutoLock lock(mMutex);
+ mBackgroundDataBridgeMap.Remove(aChannelId);
+}
+
+Maybe<RefPtr<BackgroundDataBridgeParent>>
+SocketProcessChild::GetAndRemoveDataBridge(uint64_t aChannelId) {
+ MutexAutoLock lock(mMutex);
+ return mBackgroundDataBridgeMap.GetAndRemove(aChannelId);
+}
+
+mozilla::ipc::IPCResult SocketProcessChild::RecvClearSessionCache() {
+ if (EnsureNSSInitializedChromeOrContent()) {
+ nsNSSComponent::DoClearSSLExternalAndInternalSessionCache();
+ }
+ 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 nsCString& aTopic, const nsString& aData) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, aTopic.get(), aData.get());
+ }
+ return IPC_OK();
+}
+
+already_AddRefed<PRemoteLazyInputStreamChild>
+SocketProcessChild::AllocPRemoteLazyInputStreamChild(const nsID& aID,
+ const uint64_t& aSize) {
+ RefPtr<RemoteLazyInputStreamChild> actor =
+ new RemoteLazyInputStreamChild(aID, aSize);
+ return actor.forget();
+}
+
+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();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/SocketProcessChild.h b/netwerk/ipc/SocketProcessChild.h
new file mode 100644
index 0000000000..2cee1d18a5
--- /dev/null
+++ b/netwerk/ipc/SocketProcessChild.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_SocketProcessChild_h
+#define mozilla_net_SocketProcessChild_h
+
+#include "mozilla/net/PSocketProcessChild.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/Mutex.h"
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla {
+class ChildProfilerController;
+}
+
+namespace mozilla {
+namespace net {
+
+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 mozilla::ipc::ChildToParentStreamActorManager {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SocketProcessChild)
+
+ SocketProcessChild();
+
+ static SocketProcessChild* GetSingleton();
+
+ bool Init(base::ProcessId aParentPid, const char* aParentBuildID,
+ MessageLoop* aIOLoop, UniquePtr<IPC::Channel> aChannel);
+
+ 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);
+ mozilla::ipc::IPCResult RecvSocketProcessTelemetryPing();
+
+ PWebrtcTCPSocketChild* AllocPWebrtcTCPSocketChild(const Maybe<TabId>& tabId);
+ bool DeallocPWebrtcTCPSocketChild(PWebrtcTCPSocketChild* aActor);
+
+ already_AddRefed<PHttpTransactionChild> AllocPHttpTransactionChild();
+
+ PFileDescriptorSetChild* AllocPFileDescriptorSetChild(
+ const FileDescriptor& fd);
+ bool DeallocPFileDescriptorSetChild(PFileDescriptorSetChild* aActor);
+
+ PChildToParentStreamChild* AllocPChildToParentStreamChild();
+ bool DeallocPChildToParentStreamChild(PChildToParentStreamChild* aActor);
+ PParentToChildStreamChild* AllocPParentToChildStreamChild();
+ bool DeallocPParentToChildStreamChild(PParentToChildStreamChild* aActor);
+
+ void CleanUp();
+ void DestroySocketProcessBridgeParent(ProcessId aId);
+
+ PChildToParentStreamChild* SendPChildToParentStreamConstructor(
+ PChildToParentStreamChild* aActor) override;
+ PFileDescriptorSetChild* SendPFileDescriptorSetConstructor(
+ const FileDescriptor& aFD) override;
+ already_AddRefed<PHttpConnectionMgrChild> AllocPHttpConnectionMgrChild(
+ const HttpHandlerInitArgs& aArgs);
+ mozilla::ipc::IPCResult RecvUpdateDeviceModelId(const nsCString& aModelId);
+ mozilla::ipc::IPCResult RecvOnHttpActivityDistributorActivated(
+ const bool& aIsActivated);
+
+ 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() { return mShuttingDown; }
+
+ already_AddRefed<PDNSRequestChild> AllocPDNSRequestChild(
+ const nsCString& aHost, const nsCString& aTrrServer,
+ const uint16_t& aType, const OriginAttributes& aOriginAttributes,
+ const uint32_t& aFlags);
+ mozilla::ipc::IPCResult RecvPDNSRequestConstructor(
+ PDNSRequestChild* aActor, const nsCString& aHost,
+ const nsCString& aTrrServer, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes,
+ const uint32_t& 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();
+
+ 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 nsCString& aTopic,
+ const nsString& aData);
+
+ virtual already_AddRefed<PRemoteLazyInputStreamChild>
+ AllocPRemoteLazyInputStreamChild(const nsID& aID, const uint64_t& aSize);
+
+ mozilla::ipc::IPCResult RecvGetSocketData(GetSocketDataResolver&& aResolve);
+ mozilla::ipc::IPCResult RecvGetDNSCacheEntries(
+ GetDNSCacheEntriesResolver&& aResolve);
+ mozilla::ipc::IPCResult RecvGetHttpConnectionData(
+ GetHttpConnectionDataResolver&& aResolve);
+
+ protected:
+ friend class SocketProcessImpl;
+ ~SocketProcessChild();
+
+ private:
+ // Mapping of content process id and the SocketProcessBridgeParent.
+ // This table keeps SocketProcessBridgeParent alive in socket process.
+ nsRefPtrHashtable<nsUint32HashKey, SocketProcessBridgeParent>
+ mSocketProcessBridgeParentMap;
+
+#ifdef MOZ_GECKO_PROFILER
+ RefPtr<ChildProfilerController> mProfilerController;
+#endif
+
+ bool mShuttingDown;
+ // Protect the table below.
+ Mutex mMutex;
+ nsDataHashtable<nsUint64HashKey, RefPtr<BackgroundDataBridgeParent>>
+ mBackgroundDataBridgeMap;
+};
+
+} // 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..bf8ca10085
--- /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 "ProcessUtils.h"
+#include "SocketProcessParent.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "nsAppRunner.h"
+#include "nsIOService.h"
+#include "nsIObserverService.h"
+
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+# include "mozilla/SandboxBroker.h"
+# include "mozilla/SandboxBrokerPolicyFactory.h"
+# include "mozilla/SandboxSettings.h"
+#endif
+
+#ifdef MOZ_GECKO_PROFILER
+# include "ProfilerParent.h"
+#endif
+
+#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+# include "mozilla/Sandbox.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(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;
+
+ nsAutoCString parentBuildID(mozilla::PlatformBuildID());
+ extraArgs.push_back("-parentBuildID");
+ extraArgs.push_back(parentBuildID.get());
+
+ SharedPreferenceSerializer prefSerializer;
+ if (!prefSerializer.SerializeToSharedMemory()) {
+ return false;
+ }
+ prefSerializer.AddSharedPrefCmdLineArgs(*this, extraArgs);
+
+ mLaunchPhase = LaunchPhase::Waiting;
+ if (!GeckoChildProcessHost::LaunchAndWaitForProcessHandle(extraArgs)) {
+ mLaunchPhase = LaunchPhase::Complete;
+ return false;
+ }
+
+ return true;
+}
+
+void SocketProcessHost::OnChannelConnected(int32_t 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);
+ runnable = mTaskFactory.NewRunnableMethod(
+ &SocketProcessHost::OnChannelConnectedTask);
+ }
+ NS_DispatchToMainThread(runnable);
+}
+
+void SocketProcessHost::OnChannelError() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ GeckoChildProcessHost::OnChannelError();
+
+ // Post a task to the main thread. Take the lock because mTaskFactory is not
+ // thread-safe.
+ RefPtr<Runnable> runnable;
+ {
+ MonitorAutoLock lock(mMonitor);
+ runnable =
+ mTaskFactory.NewRunnableMethod(&SocketProcessHost::OnChannelErrorTask);
+ }
+ NS_DispatchToMainThread(runnable);
+}
+
+void SocketProcessHost::OnChannelConnectedTask() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mLaunchPhase == LaunchPhase::Waiting) {
+ InitAfterConnect(true);
+ }
+}
+
+void SocketProcessHost::OnChannelErrorTask() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mLaunchPhase == LaunchPhase::Waiting) {
+ InitAfterConnect(false);
+ }
+}
+
+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 = MakeUnique<SocketProcessParent>(this);
+ DebugOnly<bool> rv = mSocketProcessParent->Open(
+ TakeChannel(), base::GetProcId(GetChildProcessHandle()));
+ 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_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);
+
+#ifdef MOZ_GECKO_PROFILER
+ Unused << GetActor()->SendInitProfiler(
+ ProfilerParent::CreateForProcess(GetActor()->OtherPid()));
+#endif
+
+ 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.RevokeAll();
+ }
+
+ 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..beb1e13c89
--- /dev/null
+++ b/netwerk/ipc/SocketProcessHost.h
@@ -0,0 +1,151 @@
+/* -*- 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/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(int32_t peer_pid) override;
+ void OnChannelError() 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();
+ void OnChannelErrorTask();
+
+ // 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::ipc::TaskFactory<SocketProcessHost> mTaskFactory;
+
+ enum class LaunchPhase { Unlaunched, Waiting, Complete };
+ LaunchPhase mLaunchPhase;
+
+ UniquePtr<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..a67dd9c9bd
--- /dev/null
+++ b/netwerk/ipc/SocketProcessImpl.cpp
@@ -0,0 +1,108 @@
+/* -*- 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 "ProcessUtils.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 "ProcessUtils.h"
+#include "mozilla/ipc/IOThreadChild.h"
+
+#if defined(OS_WIN) && defined(MOZ_SANDBOX)
+# include "mozilla/sandboxTarget.h"
+#endif
+
+#ifdef OS_POSIX
+# include <unistd.h> // For sleep().
+#endif
+
+using mozilla::ipc::IOThreadChild;
+
+namespace mozilla {
+namespace net {
+
+LazyLogModule gSocketProcessLog("socketprocess");
+
+SocketProcessImpl::SocketProcessImpl(ProcessId aParentPid)
+ : ProcessChild(aParentPid) {}
+
+SocketProcessImpl::~SocketProcessImpl() = default;
+
+bool SocketProcessImpl::Init(int aArgc, char* aArgv[]) {
+#ifdef OS_POSIX
+ 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(OS_WIN)
+ LoadLibraryW(L"nss3.dll");
+ LoadLibraryW(L"softokn3.dll");
+ LoadLibraryW(L"freebl3.dll");
+ mozilla::SandboxTarget::Instance()->StartSandbox();
+#endif
+ char* parentBuildID = nullptr;
+ char* prefsHandle = nullptr;
+ char* prefMapHandle = nullptr;
+ char* prefsLen = nullptr;
+ char* prefMapSize = nullptr;
+
+ for (int i = 1; i < aArgc; i++) {
+ if (!aArgv[i]) {
+ continue;
+ }
+
+ if (strcmp(aArgv[i], "-parentBuildID") == 0) {
+ if (++i == aArgc) {
+ return false;
+ }
+
+ parentBuildID = aArgv[i];
+
+#ifdef XP_WIN
+ } else if (strcmp(aArgv[i], "-prefsHandle") == 0) {
+ if (++i == aArgc) {
+ return false;
+ }
+ prefsHandle = aArgv[i];
+ } else if (strcmp(aArgv[i], "-prefMapHandle") == 0) {
+ if (++i == aArgc) {
+ return false;
+ }
+ prefMapHandle = aArgv[i];
+#endif
+ } else if (strcmp(aArgv[i], "-prefsLen") == 0) {
+ if (++i == aArgc) {
+ return false;
+ }
+ prefsLen = aArgv[i];
+ } else if (strcmp(aArgv[i], "-prefMapSize") == 0) {
+ if (++i == aArgc) {
+ return false;
+ }
+ prefMapSize = aArgv[i];
+ }
+ }
+
+ ipc::SharedPreferenceDeserializer deserializer;
+ if (!deserializer.DeserializeFromSharedMemory(prefsHandle, prefMapHandle,
+ prefsLen, prefMapSize)) {
+ return false;
+ }
+
+ return mSocketProcessChild.Init(ParentPid(), parentBuildID,
+ IOThreadChild::message_loop(),
+ IOThreadChild::TakeChannel());
+}
+
+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..51d609b4a9
--- /dev/null
+++ b/netwerk/ipc/SocketProcessImpl.h
@@ -0,0 +1,36 @@
+/* -*- 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:
+ typedef mozilla::ipc::ProcessChild ProcessChild;
+
+ public:
+ explicit SocketProcessImpl(ProcessId aParentPid);
+ ~SocketProcessImpl();
+
+ bool Init(int aArgc, char* aArgv[]) override;
+ void CleanUp() override;
+
+ private:
+ SocketProcessChild mSocketProcessChild;
+ DISALLOW_COPY_AND_ASSIGN(SocketProcessImpl);
+};
+
+} // 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..7e3e0ac63c
--- /dev/null
+++ b/netwerk/ipc/SocketProcessParent.cpp
@@ -0,0 +1,429 @@
+/* -*- 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 "mozilla/dom/MemoryReportRequest.h"
+#include "mozilla/ipc/FileDescriptorSetParent.h"
+#include "mozilla/ipc/IPCStreamAlloc.h"
+#include "mozilla/ipc/PChildToParentStreamParent.h"
+#include "mozilla/ipc/PParentToChildStreamParent.h"
+#include "mozilla/net/DNSRequestParent.h"
+#include "mozilla/net/ProxyConfigLookupParent.h"
+#include "mozilla/RemoteLazyInputStreamParent.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryIPC.h"
+#include "nsIAppStartup.h"
+#include "nsIHttpActivityObserver.h"
+#include "nsNSSIOLayer.h"
+#include "PSMIPCCommon.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)
+
+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());
+
+ if (PR_GetEnv("MOZ_CRASHREPORTER_SHUTDOWN")) {
+ printf_stderr("Shutting down due to socket process crash.\n");
+ nsCOMPtr<nsIAppStartup> appService =
+ do_GetService("@mozilla.org/toolkit/app-startup;1");
+ if (appService) {
+ bool userAllowedQuit = true;
+ appService->Quit(nsIAppStartup::eForceQuit, 1, &userAllowedQuit);
+ }
+ }
+ }
+
+ 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 nsCString& aHost, const nsCString& aTrrServer, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes, const uint32_t& aFlags) {
+ RefPtr<DNSRequestHandler> handler = new DNSRequestHandler();
+ RefPtr<DNSRequestParent> actor = new DNSRequestParent(handler);
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvPDNSRequestConstructor(
+ PDNSRequestParent* aActor, const nsCString& aHost,
+ const nsCString& aTrrServer, const uint16_t& aType,
+ const OriginAttributes& aOriginAttributes, const uint32_t& aFlags) {
+ RefPtr<DNSRequestParent> actor = static_cast<DNSRequestParent*>(aActor);
+ RefPtr<DNSRequestHandler> handler =
+ actor->GetDNSRequest()->AsDNSRequestHandler();
+ handler->DoAsyncResolve(aHost, aTrrServer, aType, aOriginAttributes, aFlags);
+ return IPC_OK();
+}
+
+mozilla::ipc::PFileDescriptorSetParent*
+SocketProcessParent::AllocPFileDescriptorSetParent(const FileDescriptor& aFD) {
+ return new mozilla::ipc::FileDescriptorSetParent(aFD);
+}
+
+bool SocketProcessParent::DeallocPFileDescriptorSetParent(
+ PFileDescriptorSetParent* aActor) {
+ delete static_cast<mozilla::ipc::FileDescriptorSetParent*>(aActor);
+ return true;
+}
+
+mozilla::ipc::PChildToParentStreamParent*
+SocketProcessParent::AllocPChildToParentStreamParent() {
+ return mozilla::ipc::AllocPChildToParentStreamParent();
+}
+
+bool SocketProcessParent::DeallocPChildToParentStreamParent(
+ PChildToParentStreamParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+mozilla::ipc::PParentToChildStreamParent*
+SocketProcessParent::AllocPParentToChildStreamParent() {
+ MOZ_CRASH("PParentToChildStreamChild actors should be manually constructed!");
+}
+
+bool SocketProcessParent::DeallocPParentToChildStreamParent(
+ PParentToChildStreamParent* aActor) {
+ delete aActor;
+ return true;
+}
+
+mozilla::ipc::PParentToChildStreamParent*
+SocketProcessParent::SendPParentToChildStreamConstructor(
+ PParentToChildStreamParent* aActor) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return PSocketProcessParent::SendPParentToChildStreamConstructor(aActor);
+}
+
+mozilla::ipc::PFileDescriptorSetParent*
+SocketProcessParent::SendPFileDescriptorSetConstructor(
+ const FileDescriptor& aFD) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return PSocketProcessParent::SendPFileDescriptorSetConstructor(aFD);
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvObserveHttpActivity(
+ const HttpActivityArgs& aArgs, const uint32_t& aActivityType,
+ const uint32_t& aActivitySubtype, const PRTime& aTimestamp,
+ const uint64_t& aExtraSizeData, const nsCString& aExtraStringData) {
+ nsCOMPtr<nsIHttpActivityDistributor> activityDistributor =
+ services::GetHttpActivityDistributor();
+ MOZ_ASSERT(activityDistributor);
+
+ Unused << activityDistributor->ObserveActivityWithArgs(
+ aArgs, aActivityType, aActivitySubtype, aTimestamp, aExtraSizeData,
+ aExtraStringData);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvInitBackground(
+ Endpoint<PBackgroundParent>&& aEndpoint) {
+ LOG(("SocketProcessParent::RecvInitBackground\n"));
+ if (!ipc::BackgroundParent::Alloc(nullptr, std::move(aEndpoint))) {
+ return IPC_FAIL(this, "BackgroundParent::Alloc failed");
+ }
+
+ return IPC_OK();
+}
+
+already_AddRefed<PAltServiceParent>
+SocketProcessParent::AllocPAltServiceParent() {
+ RefPtr<AltServiceParent> actor = new AltServiceParent();
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult SocketProcessParent::RecvGetTLSClientCert(
+ const nsCString& aHostName, const OriginAttributes& aOriginAttributes,
+ const int32_t& aPort, const uint32_t& aProviderFlags,
+ const uint32_t& aProviderTlsFlags, const ByteArray& aServerCert,
+ Maybe<ByteArray>&& aClientCert, nsTArray<ByteArray>&& aCollectedCANames,
+ bool* aSucceeded, ByteArray* aOutCert, ByteArray* aOutKey,
+ nsTArray<ByteArray>* aBuiltChain) {
+ *aSucceeded = false;
+
+ SECItem serverCertItem = {
+ siBuffer, const_cast<uint8_t*>(aServerCert.data().Elements()),
+ static_cast<unsigned int>(aServerCert.data().Length())};
+ UniqueCERTCertificate serverCert(CERT_NewTempCertificate(
+ CERT_GetDefaultCertDB(), &serverCertItem, nullptr, false, true));
+ if (!serverCert) {
+ return IPC_OK();
+ }
+
+ RefPtr<nsIX509Cert> clientCert;
+ if (aClientCert) {
+ clientCert = nsNSSCertificate::ConstructFromDER(
+ BitwiseCast<char*, uint8_t*>(aClientCert->data().Elements()),
+ aClientCert->data().Length());
+ if (!clientCert) {
+ return IPC_OK();
+ }
+ }
+
+ ClientAuthInfo info(aHostName, aOriginAttributes, aPort, aProviderFlags,
+ aProviderTlsFlags, clientCert);
+ nsTArray<nsTArray<uint8_t>> collectedCANames;
+ for (auto& name : aCollectedCANames) {
+ collectedCANames.AppendElement(std::move(name.data()));
+ }
+
+ UniqueCERTCertificate cert;
+ UniqueSECKEYPrivateKey key;
+ UniqueCERTCertList builtChain;
+ SECStatus status =
+ DoGetClientAuthData(std::move(info), serverCert,
+ std::move(collectedCANames), cert, key, builtChain);
+ if (status != SECSuccess) {
+ return IPC_OK();
+ }
+
+ SerializeClientCertAndKey(cert, key, *aOutCert, *aOutKey);
+
+ if (builtChain) {
+ for (CERTCertListNode* n = CERT_LIST_HEAD(builtChain);
+ !CERT_LIST_END(n, builtChain); n = CERT_LIST_NEXT(n)) {
+ ByteArray array;
+ array.data().AppendElements(n->cert->derCert.data, n->cert->derCert.len);
+ aBuiltChain->AppendElement(std::move(array));
+ }
+ }
+
+ *aSucceeded = true;
+ return IPC_OK();
+}
+
+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(
+ UniquePtr<SocketProcessParent>&& aParent)
+ : Runnable("net::DeferredDeleteSocketProcessParent"),
+ mParent(std::move(aParent)) {}
+
+ NS_IMETHODIMP Run() override { return NS_OK; }
+
+ private:
+ UniquePtr<SocketProcessParent> mParent;
+};
+
+/* static */
+void SocketProcessParent::Destroy(UniquePtr<SocketProcessParent>&& aParent) {
+ NS_DispatchToMainThread(
+ new DeferredDeleteSocketProcessParent(std::move(aParent)));
+}
+
+already_AddRefed<PRemoteLazyInputStreamParent>
+SocketProcessParent::AllocPRemoteLazyInputStreamParent(const nsID& aID,
+ const uint64_t& aSize) {
+ RefPtr<RemoteLazyInputStreamParent> actor =
+ RemoteLazyInputStreamParent::Create(aID, aSize, this);
+ return actor.forget();
+}
+
+mozilla::ipc::IPCResult
+SocketProcessParent::RecvPRemoteLazyInputStreamConstructor(
+ PRemoteLazyInputStreamParent* aActor, const nsID& aID,
+ const uint64_t& aSize) {
+ if (!static_cast<RemoteLazyInputStreamParent*>(aActor)->HasValidStream()) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ return IPC_OK();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/ipc/SocketProcessParent.h b/netwerk/ipc/SocketProcessParent.h
new file mode 100644
index 0000000000..679ccefac5
--- /dev/null
+++ b/netwerk/ipc/SocketProcessParent.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_SocketProcessParent_h
+#define mozilla_net_SocketProcessParent_h
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ipc/BackgroundParent.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 ipc::ParentToChildStreamActorManager {
+ public:
+ friend class SocketProcessHost;
+
+ explicit SocketProcessParent(SocketProcessHost* aHost);
+ ~SocketProcessParent();
+
+ 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 nsCString& aHost, const nsCString& aTrrServer,
+ const uint16_t& aType, const OriginAttributes& aOriginAttributes,
+ const uint32_t& aFlags);
+ virtual mozilla::ipc::IPCResult RecvPDNSRequestConstructor(
+ PDNSRequestParent* actor, const nsCString& hostName,
+ const nsCString& trrServer, const uint16_t& type,
+ const OriginAttributes& aOriginAttributes,
+ const uint32_t& flags) override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+ bool SendRequestMemoryReport(const uint32_t& aGeneration,
+ const bool& aAnonymize,
+ const bool& aMinimizeMemoryUsage,
+ const Maybe<ipc::FileDescriptor>& aDMDFile);
+
+ PFileDescriptorSetParent* AllocPFileDescriptorSetParent(
+ const FileDescriptor& fd);
+ bool DeallocPFileDescriptorSetParent(PFileDescriptorSetParent* aActor);
+
+ PChildToParentStreamParent* AllocPChildToParentStreamParent();
+ bool DeallocPChildToParentStreamParent(PChildToParentStreamParent* aActor);
+ PParentToChildStreamParent* AllocPParentToChildStreamParent();
+ bool DeallocPParentToChildStreamParent(PParentToChildStreamParent* aActor);
+
+ PParentToChildStreamParent* SendPParentToChildStreamConstructor(
+ PParentToChildStreamParent* aActor) override;
+ PFileDescriptorSetParent* SendPFileDescriptorSetConstructor(
+ const FileDescriptor& aFD) override;
+
+ mozilla::ipc::IPCResult RecvObserveHttpActivity(
+ const HttpActivityArgs& aArgs, const uint32_t& aActivityType,
+ const uint32_t& aActivitySubtype, const PRTime& aTimestamp,
+ const uint64_t& aExtraSizeData, const nsCString& aExtraStringData);
+
+ mozilla::ipc::IPCResult RecvInitBackground(
+ Endpoint<PBackgroundParent>&& aEndpoint);
+
+ already_AddRefed<PAltServiceParent> AllocPAltServiceParent();
+
+ mozilla::ipc::IPCResult RecvGetTLSClientCert(
+ const nsCString& aHostName, const OriginAttributes& aOriginAttributes,
+ const int32_t& aPort, const uint32_t& aProviderFlags,
+ const uint32_t& aProviderTlsFlags, const ByteArray& aServerCert,
+ Maybe<ByteArray>&& aClientCert, nsTArray<ByteArray>&& aCollectedCANames,
+ bool* aSucceeded, ByteArray* aOutCert, ByteArray* aOutKey,
+ nsTArray<ByteArray>* aBuiltChain);
+
+ 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);
+
+ already_AddRefed<PRemoteLazyInputStreamParent>
+ AllocPRemoteLazyInputStreamParent(const nsID& aID, const uint64_t& aSize);
+
+ mozilla::ipc::IPCResult RecvPRemoteLazyInputStreamConstructor(
+ PRemoteLazyInputStreamParent* aActor, const nsID& aID,
+ const uint64_t& aSize);
+
+ private:
+ SocketProcessHost* mHost;
+ UniquePtr<dom::MemoryReportRequestHost> mMemoryReportRequest;
+
+ static void Destroy(UniquePtr<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..6124d4ff2a
--- /dev/null
+++ b/netwerk/ipc/moz.build
@@ -0,0 +1,99 @@
+# -*- 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",
+ "ProxyConfigLookup.h",
+ "ProxyConfigLookupChild.h",
+ "ProxyConfigLookupParent.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",
+ "NeckoParent.cpp",
+ "NeckoTargetHolder.cpp",
+ "ParentChannelWrapper.cpp",
+ "ParentProcessDocumentChannel.cpp",
+ "ProxyConfigLookup.cpp",
+ "ProxyConfigLookupChild.cpp",
+ "ProxyConfigLookupParent.cpp",
+ "SocketProcessBridgeChild.cpp",
+ "SocketProcessBridgeParent.cpp",
+ "SocketProcessChild.cpp",
+ "SocketProcessHost.cpp",
+ "SocketProcessImpl.cpp",
+ "SocketProcessParent.cpp",
+]
+
+IPDL_SOURCES = [
+ "NeckoChannelParams.ipdlh",
+ "PDataChannel.ipdl",
+ "PDocumentChannel.ipdl",
+ "PFileChannel.ipdl",
+ "PInputChannelThrottleQueue.ipdl",
+ "PNecko.ipdl",
+ "PProxyConfigLookup.ipdl",
+ "PSimpleChannel.ipdl",
+ "PSocketProcess.ipdl",
+ "PSocketProcessBridge.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..b9c7acb1d4
--- /dev/null
+++ b/netwerk/locales/en-US/necko.properties
@@ -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/.
+
+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…
+
+27=Beginning FTP transaction…
+28=Finished FTP transaction
+
+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.
+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 first-party isolation, 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.
+CookiePartitionedForeign=Partitioned cookie or storage access was provided to “%1$S” because it is loaded in the third-party context and storage 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(CookieRejectedNonRequiresSecureForBeta2): %1$S is the cookie name. %2$S is a URL. Do not localize "SameSite", "SameSite=None" and "secure".
+CookieRejectedNonRequiresSecureForBeta2=Cookie “%1$S” will be soon rejected because it has the “SameSite” attribute set to “None” or an invalid value, 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 (CookieSchemefulRejectForBeta): %1$S is the cookie name. %2$S is the hostname.
+CookieSchemefulRejectForBeta=Cookie “%1$S” will be soon treated as cross-site cookie against “%2$S” because the scheme does not match.
+# LOCALIZATION NOTE (CookieSchemefulReject): %1$S is the cookie name. %2$S is the hostname.
+CookieSchemefulReject=Cookie “%1$S” has been treated as cross-site against “%2$S” because the scheme does not match.
+# 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 (CookieRejectedExpired): %1$S is the cookie name.
+CookieRejectedExpired=Cookie “%1$S” has been rejected because it is already expired.
+# 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”.
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/mime/moz.build b/netwerk/mime/moz.build
new file mode 100644
index 0000000000..9e52c22131
--- /dev/null
+++ b/netwerk/mime/moz.build
@@ -0,0 +1,26 @@
+# -*- 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"
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/netwerk/mime/nsIMIMEHeaderParam.idl b/netwerk/mime/nsIMIMEHeaderParam.idl
new file mode 100644
index 0000000000..947fc618c7
--- /dev/null
+++ b/netwerk/mime/nsIMIMEHeaderParam.idl
@@ -0,0 +1,207 @@
+/* -*- 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 string 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..9563a253c8
--- /dev/null
+++ b/netwerk/mime/nsIMIMEInfo.idl
@@ -0,0 +1,370 @@
+/* -*- 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..5a9a379b0f
--- /dev/null
+++ b/netwerk/mime/nsIMIMEService.idl
@@ -0,0 +1,100 @@
+/* -*- 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;
+
+%{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);
+
+ //
+ 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);
+};
diff --git a/netwerk/mime/nsMIMEHeaderParamImpl.cpp b/netwerk/mime/nsMIMEHeaderParamImpl.cpp
new file mode 100644
index 0000000000..fb4f45aac0
--- /dev/null
+++ b/netwerk/mime/nsMIMEHeaderParamImpl.cpp
@@ -0,0 +1,1320 @@
+/* -*- 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 "plstr.h"
+#include "prmem.h"
+#include "plbase64.h"
+#include "nsCRT.h"
+#include "nsMemory.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsEscape.h"
+#include "nsMIMEHeaderParamImpl.h"
+#include "nsReadableUtils.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsError.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;
+ }
+
+ 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);
+}
+
+// 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(PromiseFlatCString(aHeaderVal).get(), 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 char* 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 char* aHeaderValue, const char* aParamName, ParamDecoding aDecoding,
+ char** aCharset, char** aLang, char** aResult) {
+ if (!aHeaderValue || !*aHeaderValue || !aResult) 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;
+
+ const char* str = aHeaderValue;
+
+ // 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 = PL_strchr(valueStart, 0x27);
+ const char* sQuote2 = sQuote1 ? PL_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 (PL_strstr(aHeaderVal, "=?") ||
+ (!aDefaultCharset.IsEmpty() &&
+ (!IsUtf8(nsDependentCString(aHeaderVal)) ||
+ Is7bitNonAsciiString(aHeaderVal, strlen(aHeaderVal))))) {
+ DecodeRFC2047Str(aHeaderVal, aDefaultCharset, aOverrideCharset, aResult);
+ } else if (aEatContinuations &&
+ (PL_strchr(aHeaderVal, '\n') || PL_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 = PL_strstr(begin, "=?")) != nullptr) {
+ if (isLastEncodedWord) {
+ // See if it's all whitespace.
+ for (q = begin; q < p; ++q) {
+ if (!PL_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 <= ' ' || PL_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 && !PL_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..9fc14931e1
--- /dev/null
+++ b/netwerk/mime/nsMIMEHeaderParamImpl.h
@@ -0,0 +1,42 @@
+/* -*- 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 char* aHeaderValue,
+ const char* aParamName,
+ ParamDecoding aDecoding, char** aCharset,
+ char** aLang, char** aResult);
+};
+
+#endif
diff --git a/netwerk/mime/nsMimeTypes.h b/netwerk/mime/nsMimeTypes.h
new file mode 100644
index 0000000000..d87d770ee2
--- /dev/null
+++ b/netwerk/mime/nsMimeTypes.h
@@ -0,0 +1,236 @@
+/* -*- 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_JAVASCRIPT_BINAST "application/javascript-binast"
+#define APPLICATION_WASM "application/wasm"
+
+#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 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 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 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"
+
+/* 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..c3414cba53
--- /dev/null
+++ b/netwerk/moz.build
@@ -0,0 +1,37 @@
+# -*- 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",
+ "cache",
+ "cache2",
+ "protocol",
+ "system",
+ "ipc",
+ "url-classifier",
+]
+
+if CONFIG["MOZ_SRTP"]:
+ DIRS += ["srtp/src"]
+
+if CONFIG["MOZ_SCTP"]:
+ DIRS += ["sctp/src", "sctp/datachannel"]
+
+if CONFIG["NECKO_WIFI"]:
+ DIRS += ["wifi"]
+
+DIRS += ["locales"]
+
+DIRS += ["build"]
+TEST_DIRS += ["test"]
diff --git a/netwerk/protocol/about/moz.build b/netwerk/protocol/about/moz.build
new file mode 100644
index 0000000000..66bd1e4559
--- /dev/null
+++ b/netwerk/protocol/about/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/.
+
+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",
+]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/netwerk/protocol/about/nsAboutBlank.cpp b/netwerk/protocol/about/nsAboutBlank.cpp
new file mode 100644
index 0000000000..4a5615b803
--- /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"
+
+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(nsISupports* aOuter, 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..4fcd90816c
--- /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(nsISupports* aOuter, 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..d9c5c7733f
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCache.cpp
@@ -0,0 +1,532 @@
+/* -*- 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;
+ rv = NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(mStream), 16384,
+ (uint32_t)-1,
+ true, // non-blocking input
+ false // blocking output
+ );
+ if (NS_FAILED(rv)) return rv;
+
+ 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);
+ mStorageList.AppendElement("appcache"_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 http-equiv=\"Content-Security-Policy\" content=\"default-src "
+ "chrome:; object-src 'none'\"/>\n"
+ " <link rel=\"stylesheet\" href=\"chrome://global/skin/about.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 = NS_MaybeOpenChannelUsingAsyncOpen(mChannel, 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, false,
+ getter_AddRefs(cacheStorage));
+ } else if (storageName == "memory") {
+ rv = cacheService->MemoryCacheStorage(loadInfo,
+ getter_AddRefs(cacheStorage));
+ } else if (storageName == "appcache") {
+ rv = cacheService->AppCacheStorage(loadInfo, nullptr,
+ 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"
+ " <th><a href=\"about:cache?storage=");
+ nsAppendEscapedHTML(mStorageName, mBuffer);
+ mBuffer.AppendLiteral(
+ "\">List Cache Entries</a></th>\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, int32_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-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>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");
+
+ // 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;
+ return NS_OK;
+}
+
+// static
+nsresult nsAboutCache::Create(nsISupports* aOuter, 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..2c7064d89e
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCache.h
@@ -0,0 +1,193 @@
+/* -*- 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(nsISupports** 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(nsISupports* aOuter, 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;
+
+ // Flag initially false, that indicates the entries header has
+ // been added to the output HTML.
+ bool mEntriesHeaderAdded;
+
+ // Cancelation flag
+ bool mCancel;
+
+ // 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..f416852a0e
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCacheEntry.cpp
@@ -0,0 +1,557 @@
+/* -*- 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 "nsAboutCacheEntry.h"
+
+#include "mozilla/Sprintf.h"
+
+#include "nsAboutCache.h"
+#include "nsICacheStorage.h"
+#include "CacheObserver.h"
+#include "nsNetUtil.h"
+#include "nsEscape.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsContentUtils.h"
+#include "nsInputStreamPump.h"
+#include "CacheFileUtils.h"
+#include <algorithm>
+
+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;
+ rv = NS_NewPipe2(getter_AddRefs(inputStream), getter_AddRefs(mOutputStream),
+ true, false, 256, UINT32_MAX);
+ if (NS_FAILED(rv)) return rv;
+
+ constexpr auto buffer =
+ "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ " <meta http-equiv=\"Content-Security-Policy\" content=\"default-src "
+ "chrome:; object-src 'none'\" />\n"
+ " <title>Cache entry information</title>\n"
+ " <link rel=\"stylesheet\" "
+ "href=\"chrome://global/skin/about.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, nsIApplicationCache* aApplicationCache,
+ uint32_t* result) {
+ *result = nsICacheEntryOpenCallback::ENTRY_WANTED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutCacheEntry::Channel::OnCacheEntryAvailable(
+ nsICacheEntry* entry, bool isNew, nsIApplicationCache* aApplicationCache,
+ 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;
+ int32_t i = 0;
+ nsAutoCString s;
+
+ // Fetch Count
+ s.Truncate();
+ entry->GetFetchCount(&i);
+ s.AppendInt(i);
+ 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<nsISupports> 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..4c4e6c2ee8
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutCacheEntry.h
@@ -0,0 +1,85 @@
+/* -*- 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"
+
+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() : mBuffer(nullptr), mWaitingForData(false), mHexDumpState(0) {}
+
+ 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;
+ nsCOMPtr<nsIAsyncOutputStream> mOutputStream;
+ bool mWaitingForData;
+ uint32_t mHexDumpState;
+
+ 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..de8a83a5df
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolHandler.cpp
@@ -0,0 +1,445 @@
+/* -*- 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 "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(kSimpleURICID, NS_SIMPLEURI_CID);
+static NS_DEFINE_CID(kNestedAboutURICID, NS_NESTEDABOUTURI_CID);
+
+static bool IsSafeForUntrustedContent(nsIAboutModule* aModule, nsIURI* aURI) {
+ uint32_t flags;
+ nsresult rv = aModule->GetURIFlags(aURI, &flags);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return (flags & nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT) != 0;
+}
+
+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.
+ if (path.EqualsLiteral("blank") || path.EqualsLiteral("logo") ||
+ path.EqualsLiteral("srcdoc")) {
+ return true;
+ }
+
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsAboutProtocolHandler, nsIProtocolHandler,
+ nsIProtocolHandlerWithDynamicFlags, nsISupportsWeakReference)
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::GetScheme(nsACString& result) {
+ result.AssignLiteral("about");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::GetDefaultPort(int32_t* result) {
+ *result = -1; // no port for about: URLs
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::GetProtocolFlags(uint32_t* result) {
+ *result = URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD |
+ URI_SCHEME_NOT_SELF_LINKABLE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAboutProtocolHandler::GetFlagsForURI(nsIURI* aURI, uint32_t* aFlags) {
+ // First use the default (which is "unsafe for content"):
+ GetProtocolFlags(aFlags);
+
+ // 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);
+
+ nsCOMPtr<nsIURI> base(aBaseURI);
+ rv = NS_MutateURI(new nsNestedAboutURI::Mutator())
+ .Apply(NS_MutatorMethod(&nsINestedAboutURIMutator::InitWithBase,
+ inner, base))
+ .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;
+ nsresult rv2 = NS_GetAboutModuleName(uri, path);
+ if (NS_SUCCEEDED(rv2) && 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.
+ rv = NS_ERROR_FACTORY_NOT_REGISTERED;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ // The standard return case:
+ rv = aboutMod->NewChannel(uri, aLoadInfo, result);
+ if (NS_SUCCEEDED(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 (IsSafeForUntrustedContent(aboutMod, uri)) {
+ (*result)->SetOwner(nullptr);
+ }
+
+ RefPtr<nsNestedAboutURI> aboutURI;
+ nsresult rv2 =
+ uri->QueryInterface(kNestedAboutURICID, getter_AddRefs(aboutURI));
+ if (NS_SUCCEEDED(rv2) && aboutURI->GetBaseURI()) {
+ nsCOMPtr<nsIWritablePropertyBag2> writableBag =
+ do_QueryInterface(*result);
+ if (writableBag) {
+ writableBag->SetPropertyAsInterface(u"baseURI"_ns,
+ aboutURI->GetBaseURI());
+ }
+ }
+ }
+ return rv;
+ }
+
+ // mumble...
+
+ if (rv == NS_ERROR_FACTORY_NOT_REGISTERED) {
+ // This looks like an about: we don't know about. Convert
+ // this to an invalid URI error.
+ rv = NS_ERROR_MALFORMED_URI;
+ }
+
+ return rv;
+}
+
+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::GetDefaultPort(int32_t* result) {
+ *result = -1; // no port for moz-safe-about: URLs
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSafeAboutProtocolHandler::GetProtocolFlags(uint32_t* result) {
+ *result = URI_NORELATIVE | URI_NOAUTH | URI_LOADABLE_BY_ANYONE |
+ URI_IS_POTENTIALLY_TRUSTWORTHY;
+ 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..173742e458
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolHandler.h
@@ -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/. */
+
+#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() : nsSimpleNestedURI() {}
+ 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->mMutable = false;
+ 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;
+ }
+
+ void ResetMutable() {
+ if (mURI) {
+ mURI->mMutable = true;
+ }
+ }
+
+ 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..ef508dce2e
--- /dev/null
+++ b/netwerk/protocol/about/nsAboutProtocolUtils.h
@@ -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/. */
+
+#ifndef nsAboutProtocolUtils_h
+#define nsAboutProtocolUtils_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");
+
+ nsresult rv = aAboutURI->GetPathQueryRef(aModule);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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;
+}
+
+inline nsresult NS_GetAboutModule(nsIURI* aAboutURI, nsIAboutModule** aModule) {
+ MOZ_ASSERT(aAboutURI, "Must have URI");
+
+ nsAutoCString contractID;
+ nsresult rv = NS_GetAboutModuleName(aAboutURI, contractID);
+ if (NS_FAILED(rv)) return rv;
+
+ // 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..b7ebec8f67
--- /dev/null
+++ b/netwerk/protocol/about/nsIAboutModule.idl
@@ -0,0 +1,114 @@
+/* -*- 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 needs to allow unsanitized content.
+ * Only to be used by about:home and about:newtab.
+ */
+ const unsigned long ALLOW_UNSANITIZED_CONTENT = (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..025cc2060e
--- /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"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(DataChannelParent, nsIParentChannel, nsIStreamListener)
+
+bool DataChannelParent::Init(const uint64_t& aChannelId) {
+ nsCOMPtr<nsIChannel> channel;
+ MOZ_ALWAYS_SUCCEEDS(
+ 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::NotifyFlashPluginStateChanged(
+ nsIHttpChannel::FlashPluginState aState) {
+ // 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..2b96cb0853
--- /dev/null
+++ b/netwerk/protocol/data/nsDataChannel.cpp
@@ -0,0 +1,109 @@
+/* -*- 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"
+
+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);
+ 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;
+
+ bufInStream.forget(result);
+
+ return NS_OK;
+}
diff --git a/netwerk/protocol/data/nsDataChannel.h b/netwerk/protocol/data/nsDataChannel.h
new file mode 100644
index 0000000000..8ee759f1db
--- /dev/null
+++ b/netwerk/protocol/data/nsDataChannel.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/. */
+
+// 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); }
+
+ protected:
+ [[nodiscard]] virtual nsresult OpenContentStream(
+ bool async, nsIInputStream** result, nsIChannel** channel) override;
+};
+
+#endif /* nsDataChannel_h___ */
diff --git a/netwerk/protocol/data/nsDataHandler.cpp b/netwerk/protocol/data/nsDataHandler.cpp
new file mode 100644
index 0000000000..01efa9b0f1
--- /dev/null
+++ b/netwerk/protocol/data/nsDataHandler.cpp
@@ -0,0 +1,270 @@
+/* -*- 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 "plstr.h"
+#include "nsSimpleURI.h"
+#include "mozilla/dom/MimeType.h"
+
+#ifdef ANDROID
+# include "mozilla/StaticPrefs_network.h"
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsDataHandler, nsIProtocolHandler, nsISupportsWeakReference)
+
+nsresult nsDataHandler::Create(nsISupports* aOuter, 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;
+}
+
+NS_IMETHODIMP
+nsDataHandler::GetDefaultPort(int32_t* result) {
+ // no ports for data protocol
+ *result = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDataHandler::GetProtocolFlags(uint32_t* result) {
+ *result = URI_NORELATIVE | URI_NOAUTH | URI_INHERITS_SECURITY_CONTEXT |
+ URI_LOADABLE_BY_ANYONE | URI_NON_PERSISTABLE |
+ URI_IS_LOCAL_RESOURCE | URI_SYNC_LOAD_IS_OK;
+ return NS_OK;
+}
+
+/* static */ nsresult nsDataHandler::CreateNewURI(const nsACString& aSpec,
+ const char* aCharset,
+ nsIURI* aBaseURI,
+ nsIURI** result) {
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+
+ nsCString spec(aSpec);
+
+#ifdef ANDROID
+ // Due to heap limitations on mobile, limits the size of data URL
+ if (spec.Length() > StaticPrefs::network_data_max_uri_length_mobile())
+ return NS_ERROR_OUT_OF_MEMORY;
+#endif
+
+ if (aBaseURI && !spec.IsEmpty() && spec[0] == '#') {
+ // Looks like a reference instead of a fully-specified URI.
+ // --> initialize |uri| as a clone of |aBaseURI|, with ref appended.
+ rv = NS_MutateURI(aBaseURI).SetRef(spec).Finalize(uri);
+ } else {
+ // Otherwise, we'll assume |spec| is a fully-specified data URI
+ nsAutoCString contentType;
+ bool base64;
+ rv = ParseURI(spec, contentType, /* contentCharset = */ nullptr, base64,
+ /* dataBuffer = */ nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ // Strip whitespace unless this is text, where whitespace is important
+ // Don't strip escaped whitespace though (bug 391951)
+ if (base64 || (strncmp(contentType.get(), "text/", 5) != 0 &&
+ contentType.Find("xml") == kNotFound)) {
+ // it's ascii encoded binary, don't let any spaces in
+ if (!spec.StripWhitespace(mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ rv = NS_MutateURI(new mozilla::net::nsSimpleURI::Mutator())
+ .SetSpec(spec)
+ .Finalize(uri);
+ }
+
+ if (NS_FAILED(rv)) return 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;
+}
+
+/**
+ * Helper that performs a case insensitive match to find the offset of a given
+ * pattern in a nsACString.
+ * The search is performed starting from the end of the string; if the string
+ * contains more than one match, the rightmost (last) match will be returned.
+ */
+static bool FindOffsetOf(const nsACString& aPattern, const nsACString& aSrc,
+ nsACString::size_type& aOffset) {
+ nsACString::const_iterator begin, end;
+ aSrc.BeginReading(begin);
+ aSrc.EndReading(end);
+ if (!RFindInReadable(aPattern, begin, end,
+ nsCaseInsensitiveCStringComparator)) {
+ return false;
+ }
+
+ // FindInReadable updates |begin| and |end| to the match coordinates.
+ aOffset = nsACString::size_type(begin.get() - aSrc.Data());
+ return true;
+}
+
+nsresult nsDataHandler::ParsePathWithoutRef(
+ const nsACString& aPath, nsCString& aContentType,
+ nsCString* aContentCharset, bool& aIsBase64,
+ nsDependentCSubstring* aDataBuffer) {
+ static constexpr auto kBase64 = "base64"_ns;
+ static constexpr auto kCharset = "charset"_ns;
+
+ aIsBase64 = false;
+
+ // First, find the start of the data
+ 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;
+ }
+ if (commaIdx == 0 || commaIdx == kNotFound) {
+ // Nothing but data.
+ aContentType.AssignLiteral("text/plain");
+ if (aContentCharset) {
+ aContentCharset->AssignLiteral("US-ASCII");
+ }
+ } else {
+ auto mediaType = Substring(aPath, 0, commaIdx);
+
+ // Determine if the data is base64 encoded.
+ nsACString::size_type base64;
+ if (FindOffsetOf(kBase64, mediaType, base64) && base64 > 0) {
+ nsACString::size_type offset = base64 + kBase64.Length();
+ // Per the RFC 2397 grammar, "base64" MUST be at the end of the
+ // non-data part.
+ //
+ // But we also allow it in between parameters so a subsequent ";"
+ // is ok as well (this deals with *broken* data URIs, see bug
+ // 781693 for an example). Anything after "base64" in the non-data
+ // part will be discarded in this case, however.
+ if (offset == mediaType.Length() || mediaType[offset] == ';' ||
+ mediaType[offset] == ' ') {
+ MOZ_DIAGNOSTIC_ASSERT(base64 > 0, "Did someone remove the check?");
+ // Index is on the first character of matched "base64" so we
+ // move to the preceding character
+ base64--;
+ // Skip any preceding spaces, searching for a semicolon
+ while (base64 > 0 && mediaType[base64] == ' ') {
+ base64--;
+ }
+ if (mediaType[base64] == ';') {
+ aIsBase64 = true;
+ // Trim the base64 part off.
+ mediaType.Rebind(aPath, 0, base64);
+ }
+ }
+ }
+
+ // Skip any leading spaces
+ nsACString::size_type startIndex = 0;
+ while (startIndex < mediaType.Length() && mediaType[startIndex] == ' ') {
+ startIndex++;
+ }
+
+ nsAutoCString mediaTypeBuf;
+ // If the mimetype starts with ';' we assume text/plain
+ if (startIndex < mediaType.Length() && mediaType[startIndex] == ';') {
+ mediaTypeBuf.AssignLiteral("text/plain");
+ mediaTypeBuf.Append(mediaType);
+ mediaType.Rebind(mediaTypeBuf, 0, mediaTypeBuf.Length());
+ }
+
+ // Everything else is content type.
+ if (mozilla::UniquePtr<CMimeType> parsed = CMimeType::Parse(mediaType)) {
+ parsed->GetFullType(aContentType);
+ if (aContentCharset) {
+ parsed->GetParameterValue(kCharset, *aContentCharset);
+ }
+ } else {
+ // Mime Type parsing failed
+ aContentType.AssignLiteral("text/plain");
+ if (aContentCharset) {
+ aContentCharset->AssignLiteral("US-ASCII");
+ }
+ }
+ }
+
+ if (aDataBuffer) {
+ aDataBuffer->Rebind(aPath, commaIdx + 1);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDataHandler::ParseURI(nsCString& spec, nsCString& contentType,
+ nsCString* contentCharset, bool& isBase64,
+ nsCString* dataBuffer) {
+ static constexpr auto kDataScheme = "data:"_ns;
+
+ // move past "data:"
+ int32_t scheme = spec.Find(kDataScheme, /* aIgnoreCase = */ true);
+ if (scheme == kNotFound) {
+ // malformed uri
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ 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..37d170f3e5
--- /dev/null
+++ b/netwerk/protocol/data/nsDataHandler.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 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(nsISupports* aOuter, 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(nsCString& 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);
+};
+
+#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..904b9fa88f
--- /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"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(FileChannelParent, nsIParentChannel, nsIStreamListener)
+
+bool FileChannelParent::Init(const uint64_t& aChannelId) {
+ nsCOMPtr<nsIChannel> channel;
+ MOZ_ALWAYS_SUCCEEDS(
+ 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::NotifyFlashPluginStateChanged(
+ nsIHttpChannel::FlashPluginState aState) {
+ // 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..06a883f826
--- /dev/null
+++ b/netwerk/protocol/file/moz.build
@@ -0,0 +1,43 @@
+# -*- 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",
+]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/netwerk/protocol/file/nsFileChannel.cpp b/netwerk/protocol/file/nsFileChannel.cpp
new file mode 100644
index 0000000000..a94aa50599
--- /dev/null
+++ b/netwerk/protocol/file/nsFileChannel.cpp
@@ -0,0 +1,511 @@
+/* -*- 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 "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) {
+ rv = NS_ERROR_FILE_DISK_FULL; // stopped prematurely (out of disk 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);
+
+ // 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)) {
+ // canonicalize error message
+ if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) rv = NS_ERROR_FILE_NOT_FOUND;
+
+ 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);
+ }
+ }
+
+ *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 = new TaskQueue(sts.forget());
+ 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 ||
+ NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv)) {
+ size = 0;
+ } else {
+ return rv;
+ }
+ }
+ mContentLength = size;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsFileChannel::nsISupports
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFileChannel, nsBaseChannel, nsIUploadChannel,
+ nsIFileChannel)
+
+//-----------------------------------------------------------------------------
+// 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);
+}
+
+//-----------------------------------------------------------------------------
+// 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) {
+ NS_IF_ADDREF(*result = mUploadStream);
+ return NS_OK;
+}
diff --git a/netwerk/protocol/file/nsFileChannel.h b/netwerk/protocol/file/nsFileChannel.h
new file mode 100644
index 0000000000..dcf0646f03
--- /dev/null
+++ b/netwerk/protocol/file/nsFileChannel.h
@@ -0,0 +1,53 @@
+/* -*- 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:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFILECHANNEL
+ NS_DECL_NSIUPLOADCHANNEL
+
+ 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);
+
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ int64_t mUploadLength;
+ nsCOMPtr<nsIURI> mFileURI;
+};
+
+#endif // !nsFileChannel_h__
diff --git a/netwerk/protocol/file/nsFileProtocolHandler.cpp b/netwerk/protocol/file/nsFileProtocolHandler.cpp
new file mode 100644
index 0000000000..d1ab886a0f
--- /dev/null
+++ b/netwerk/protocol/file/nsFileProtocolHandler.cpp
@@ -0,0 +1,277 @@
+/* -*- 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"
+
+// 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::GetDefaultPort(int32_t* result) {
+ *result = -1; // no port for file: URLs
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::GetProtocolFlags(uint32_t* result) {
+ *result = URI_NOAUTH | URI_IS_LOCAL_FILE | URI_IS_LOCAL_RESOURCE |
+ URI_IS_POTENTIALLY_TRUSTWORTHY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ nsresult rv;
+
+ RefPtr<nsFileChannel> chan;
+ if (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;
+ }
+
+ chan.forget(result);
+ 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 nsStandardURL::Mutator())
+ .Apply(NS_MutatorMethod(&nsIFileURLMutator::SetFile, file))
+ .Finalize(aResult);
+}
+
+NS_IMETHODIMP
+nsFileProtocolHandler::NewFileURIMutator(nsIFile* aFile,
+ nsIURIMutator** aResult) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsresult rv;
+
+ nsCOMPtr<nsIURIMutator> mutator = new 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..7ca9f6cf29
--- /dev/null
+++ b/netwerk/protocol/file/nsIFileProtocolHandler.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 "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);
+
+ /**
+ * Converts the 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.
+ * NOTE: Callers should use getURLSpecFromActualFile or
+ * getURLSpecFromDirFile if possible, for performance reasons.
+ */
+ AUTF8String getURLSpecFromFile(in nsIFile file);
+
+ /**
+ * Converts the nsIFile to the corresponding URL string. Should
+ * only be called on files which are not directories. Otherwise
+ * identical to getURLSpecFromFile, but is usually more efficient.
+ * WARNING: This restriction may not be enforced at runtime!
+ */
+ AUTF8String getURLSpecFromActualFile(in nsIFile file);
+
+ /**
+ * Converts the nsIFile to the corresponding URL string. Should
+ * only be called on files which are directories. Otherwise
+ * identical to getURLSpecFromFile, but is usually more efficient.
+ * WARNING: This restriction may not be enforced at runtime!
+ */
+ 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/ftp/FTPChannelChild.cpp b/netwerk/protocol/ftp/FTPChannelChild.cpp
new file mode 100644
index 0000000000..63edaff4cd
--- /dev/null
+++ b/netwerk/protocol/ftp/FTPChannelChild.cpp
@@ -0,0 +1,541 @@
+/* -*- 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/NeckoChild.h"
+#include "mozilla/net/FTPChannelChild.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "nsContentUtils.h"
+#include "nsFtpProtocolHandler.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 "mozilla/ScopeExit.h"
+
+using mozilla::dom::ContentChild;
+using namespace mozilla::ipc;
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
+
+namespace mozilla {
+namespace net {
+
+FTPChannelChild::FTPChannelChild(nsIURI* aUri)
+ : mIPCOpen(false),
+ mEventQ(new ChannelEventQueue(static_cast<nsIFTPChannel*>(this))),
+ mCanceled(false),
+ mSuspendCount(0),
+ mIsPending(false),
+ mLastModifiedTime(0),
+ mStartPos(0),
+ mSuspendSent(false) {
+ LOG(("Creating FTPChannelChild @%p\n", this));
+ // grab a reference to the handler to ensure that it doesn't go away.
+ NS_ADDREF(gFtpHandler);
+ 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();
+}
+
+FTPChannelChild::~FTPChannelChild() {
+ LOG(("Destroying FTPChannelChild @%p\n", this));
+ gFtpHandler->Release();
+}
+
+void FTPChannelChild::AddIPDLReference() {
+ MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference");
+ mIPCOpen = true;
+ AddRef();
+}
+
+void FTPChannelChild::ReleaseIPDLReference() {
+ MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference");
+ mIPCOpen = false;
+ Release();
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelChild::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS_INHERITED(FTPChannelChild, nsBaseChannel, nsIFTPChannel,
+ nsIUploadChannel, nsIResumableChannel,
+ nsIProxiedChannel, nsIChildChannel)
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelChild::GetLastModifiedTime(PRTime* aLastModifiedTime) {
+ *aLastModifiedTime = mLastModifiedTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::SetLastModifiedTime(PRTime aLastModifiedTime) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::ResumeAt(uint64_t aStartPos, const nsACString& aEntityID) {
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ mStartPos = aStartPos;
+ mEntityID = aEntityID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::GetEntityID(nsACString& aEntityID) {
+ aEntityID = mEntityID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::GetProxyInfo(nsIProxyInfo** aProxyInfo) { DROP_DEAD(); }
+
+NS_IMETHODIMP FTPChannelChild::GetHttpProxyConnectResponseCode(
+ int32_t* aResponseCode) {
+ DROP_DEAD();
+}
+
+NS_IMETHODIMP
+FTPChannelChild::SetUploadStream(nsIInputStream* aStream,
+ const nsACString& aContentType,
+ int64_t aContentLength) {
+ NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
+ mUploadStream = aStream;
+ // NOTE: contentLength is intentionally ignored here.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::GetUploadStream(nsIInputStream** aStream) {
+ NS_ENSURE_ARG_POINTER(aStream);
+ *aStream = mUploadStream;
+ NS_IF_ADDREF(*aStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::AsyncOpen(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("FTPChannelChild::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);
+
+ mozilla::ipc::AutoIPCStream autoStream;
+ autoStream.Serialize(mUploadStream,
+ static_cast<ContentChild*>(gNeckoChild->Manager()));
+
+ uint32_t loadFlags = 0;
+ GetLoadFlags(&loadFlags);
+
+ FTPChannelOpenArgs openArgs;
+ SerializeURI(nsBaseChannel::URI(), openArgs.uri());
+ openArgs.startPos() = mStartPos;
+ openArgs.entityID() = mEntityID;
+ openArgs.uploadStream() = autoStream.TakeOptionalValue();
+ 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();
+
+ gNeckoChild->SendPFTPChannelConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), openArgs);
+
+ // The socket transport layer in the chrome process now has a logical ref to
+ // us until OnStopRequest is called.
+ AddIPDLReference();
+
+ mIsPending = true;
+ mWasOpened = true;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::IsPending(bool* aResult) {
+ *aResult = mIsPending;
+ return NS_OK;
+}
+
+nsresult FTPChannelChild::OpenContentStream(bool aAsync,
+ nsIInputStream** aStream,
+ nsIChannel** aChannel) {
+ MOZ_CRASH("FTPChannel*Child* should never have OpenContentStream called!");
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelChild::PFTPChannelChild
+//-----------------------------------------------------------------------------
+
+mozilla::ipc::IPCResult FTPChannelChild::RecvOnStartRequest(
+ const nsresult& aChannelStatus, const int64_t& aContentLength,
+ const nsCString& aContentType, const PRTime& aLastModified,
+ const nsCString& aEntityID, const URIParams& aURI) {
+ LOG(("FTPChannelChild::RecvOnStartRequest [this=%p]\n", this));
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<FTPChannelChild>(this), aChannelStatus,
+ aContentLength, aContentType, aLastModified, aEntityID, aURI]() {
+ self->DoOnStartRequest(aChannelStatus, aContentLength, aContentType,
+ aLastModified, aEntityID, aURI);
+ }));
+ return IPC_OK();
+}
+
+void FTPChannelChild::DoOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsCString& aContentType,
+ const PRTime& aLastModified,
+ const nsCString& aEntityID,
+ const URIParams& aURI) {
+ mDuringOnStart = true;
+ RefPtr<FTPChannelChild> self = this;
+ auto clearDuringFlag =
+ mozilla::MakeScopeExit([self] { self->mDuringOnStart = false; });
+
+ LOG(("FTPChannelChild::DoOnStartRequest [this=%p]\n", this));
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aChannelStatus;
+ }
+
+ mContentLength = aContentLength;
+ SetContentType(aContentType);
+ mLastModifiedTime = aLastModified;
+ 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);
+ }
+ } else {
+ Cancel(rv);
+ }
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+ rv = mListener->OnStartRequest(this);
+ if (NS_FAILED(rv)) Cancel(rv);
+}
+
+mozilla::ipc::IPCResult FTPChannelChild::RecvOnDataAvailable(
+ const nsresult& aChannelStatus, const nsCString& aData,
+ const uint64_t& aOffset, const uint32_t& aCount) {
+ LOG(("FTPChannelChild::RecvOnDataAvailable [this=%p]\n", this));
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<FTPChannelChild>(this), aChannelStatus, aData,
+ aOffset, aCount]() {
+ self->DoOnDataAvailable(aChannelStatus, aData, aOffset, aCount);
+ }));
+
+ return IPC_OK();
+}
+
+void FTPChannelChild::DoOnDataAvailable(const nsresult& aChannelStatus,
+ const nsCString& aData,
+ const uint64_t& aOffset,
+ const uint32_t& aCount) {
+ LOG(("FTPChannelChild::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 FTPChannelChild::RecvOnStopRequest(
+ const nsresult& aChannelStatus, const nsCString& aErrorMsg,
+ const bool& aUseUTF8) {
+ LOG(("FTPChannelChild::RecvOnStopRequest [this=%p status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aChannelStatus)));
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<FTPChannelChild>(this), aChannelStatus, aErrorMsg,
+ aUseUTF8]() {
+ self->DoOnStopRequest(aChannelStatus, aErrorMsg, aUseUTF8);
+ }));
+ return IPC_OK();
+}
+
+void FTPChannelChild::DoOnStopRequest(const nsresult& aChannelStatus,
+ const nsCString& aErrorMsg,
+ bool aUseUTF8) {
+ LOG(("FTPChannelChild::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::DeallocPFTPChannelChild(), which deletes |this| if
+ // IPDL holds the last reference. Don't rely on |this| existing after here!
+ Send__delete__(this);
+}
+
+mozilla::ipc::IPCResult FTPChannelChild::RecvFailedAsyncOpen(
+ const nsresult& aStatusCode) {
+ LOG(("FTPChannelChild::RecvFailedAsyncOpen [this=%p status=%" PRIx32 "]\n",
+ this, static_cast<uint32_t>(aStatusCode)));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<FTPChannelChild>(this), aStatusCode]() {
+ self->DoFailedAsyncOpen(aStatusCode);
+ }));
+ return IPC_OK();
+}
+
+void FTPChannelChild::DoFailedAsyncOpen(const nsresult& aStatusCode) {
+ LOG(("FTPChannelChild::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 FTPChannelChild::RecvDeleteSelf() {
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<FTPChannelChild>(this)]() { self->DoDeleteSelf(); }));
+ return IPC_OK();
+}
+
+void FTPChannelChild::DoDeleteSelf() {
+ if (mIPCOpen) {
+ Send__delete__(this);
+ }
+}
+
+NS_IMETHODIMP
+FTPChannelChild::Cancel(nsresult aStatus) {
+ LOG(("FTPChannelChild::Cancel [this=%p]\n", this));
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ mCanceled = true;
+ mStatus = aStatus;
+ if (mIPCOpen) {
+ SendCancel(aStatus);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::Suspend() {
+ NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
+
+ LOG(("FTPChannelChild::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
+FTPChannelChild::Resume() {
+ NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
+
+ LOG(("FTPChannelChild::Resume [this=%p]\n", this));
+
+ // SendResume only once, when suspend count drops to 0.
+ if (!--mSuspendCount && mSuspendSent) {
+ SendResume();
+ }
+ mEventQ->Resume();
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelChild::nsIChildChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelChild::ConnectParent(uint32_t aId) {
+ NS_ENSURE_TRUE((gNeckoChild), NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(
+ !static_cast<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown(),
+ NS_ERROR_FAILURE);
+
+ LOG(("FTPChannelChild::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();
+
+ FTPChannelConnectArgs connectArgs(aId);
+
+ if (!gNeckoChild->SendPFTPChannelConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), connectArgs)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) {
+ LOG(("FTPChannelChild::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 FTPChannelChild::SetupNeckoTarget() {
+ if (mNeckoTarget) {
+ return;
+ }
+ nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo();
+ mNeckoTarget =
+ nsContentUtils::GetEventTargetByLoadInfo(loadInfo, TaskCategory::Network);
+ if (!mNeckoTarget) {
+ return;
+ }
+
+ gNeckoChild->SetEventTargetForActor(this, mNeckoTarget);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/ftp/FTPChannelChild.h b/netwerk/protocol/ftp/FTPChannelChild.h
new file mode 100644
index 0000000000..e9c7dc9f6f
--- /dev/null
+++ b/netwerk/protocol/ftp/FTPChannelChild.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_FTPChannelChild_h
+#define mozilla_net_FTPChannelChild_h
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/net/PFTPChannelChild.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "nsBaseChannel.h"
+#include "nsIFTPChannel.h"
+#include "nsIUploadChannel.h"
+#include "nsIProxiedChannel.h"
+#include "nsIResumableChannel.h"
+#include "nsIChildChannel.h"
+#include "nsIEventTarget.h"
+
+#include "nsIStreamListener.h"
+#include "mozilla/net/PrivateBrowsingChannel.h"
+
+class nsIEventTarget;
+
+namespace mozilla {
+
+namespace net {
+
+// This class inherits logic from nsBaseChannel that is not needed for an
+// e10s child channel, but it works. At some point we could slice up
+// nsBaseChannel and have a new class that has only the common logic for
+// nsFTPChannel/FTPChannelChild.
+
+class FTPChannelChild final : public PFTPChannelChild,
+ public nsBaseChannel,
+ public nsIFTPChannel,
+ public nsIUploadChannel,
+ public nsIResumableChannel,
+ public nsIProxiedChannel,
+ public nsIChildChannel {
+ public:
+ typedef ::nsIStreamListener nsIStreamListener;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIFTPCHANNEL
+ NS_DECL_NSIUPLOADCHANNEL
+ NS_DECL_NSIRESUMABLECHANNEL
+ NS_DECL_NSIPROXIEDCHANNEL
+ NS_DECL_NSICHILDCHANNEL
+
+ NS_IMETHOD Cancel(nsresult aStatus) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+
+ explicit FTPChannelChild(nsIURI* aUri);
+
+ 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 ~FTPChannelChild();
+
+ mozilla::ipc::IPCResult RecvOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsCString& aContentType,
+ const PRTime& aLastModified,
+ const nsCString& aEntityID,
+ const URIParams& aURI) override;
+ mozilla::ipc::IPCResult RecvOnDataAvailable(const nsresult& aChannelStatus,
+ const nsCString& aData,
+ const uint64_t& aOffset,
+ const uint32_t& aCount) override;
+ mozilla::ipc::IPCResult RecvOnStopRequest(const nsresult& aChannelStatus,
+ const nsCString& aErrorMsg,
+ const bool& aUseUTF8) override;
+ mozilla::ipc::IPCResult RecvFailedAsyncOpen(
+ const nsresult& aStatusCode) override;
+ mozilla::ipc::IPCResult RecvDeleteSelf() override;
+
+ void DoOnStartRequest(const nsresult& aChannelStatus,
+ const int64_t& aContentLength,
+ const nsCString& aContentType,
+ const PRTime& aLastModified, const nsCString& aEntityID,
+ const URIParams& aURI);
+ void DoOnDataAvailable(const nsresult& aChannelStatus, const nsCString& aData,
+ const uint64_t& aOffset, const uint32_t& aCount);
+ void DoOnStopRequest(const nsresult& StatusCode, const nsCString& aErrorMsg,
+ bool aUseUTF8);
+ void DoFailedAsyncOpen(const nsresult& aStatusCode);
+ void DoDeleteSelf();
+
+ void SetupNeckoTarget() override;
+
+ friend class NeckoTargetChannelFunctionEvent;
+
+ private:
+ nsCOMPtr<nsIInputStream> mUploadStream;
+
+ bool mIPCOpen;
+ const RefPtr<ChannelEventQueue> mEventQ;
+
+ bool mCanceled;
+ uint32_t mSuspendCount;
+ bool mIsPending;
+
+ // This will only be true while DoOnStartRequest is in progress.
+ // It is used to enforce that DivertToParent is only called during that time.
+ bool mDuringOnStart = false;
+
+ PRTime mLastModifiedTime;
+ uint64_t mStartPos;
+ nsCString mEntityID;
+
+ // Set if SendSuspend is called. Determines if SendResume is needed when
+ // diverting callbacks to parent.
+ bool mSuspendSent;
+};
+
+inline bool FTPChannelChild::IsSuspended() const { return mSuspendCount != 0; }
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_FTPChannelChild_h
diff --git a/netwerk/protocol/ftp/FTPChannelParent.cpp b/netwerk/protocol/ftp/FTPChannelParent.cpp
new file mode 100644
index 0000000000..3bf94fea70
--- /dev/null
+++ b/netwerk/protocol/ftp/FTPChannelParent.cpp
@@ -0,0 +1,418 @@
+/* -*- 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/FTPChannelParent.h"
+#include "nsStringStream.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "nsFTPChannel.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "nsFtpProtocolHandler.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsISecureBrowserUI.h"
+#include "nsIForcePendingChannel.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/Unused.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/dom/ContentParent.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
+
+namespace mozilla {
+namespace net {
+
+FTPChannelParent::FTPChannelParent(dom::BrowserParent* aIframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus)
+ : mIPCClosed(false),
+ mLoadContext(aLoadContext),
+ mPBOverride(aOverrideStatus),
+ mStatus(NS_OK),
+ mBrowserParent(aIframeEmbedding),
+ mUseUTF8(false) {
+ nsIProtocolHandler* handler;
+ CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &handler);
+ MOZ_ASSERT(handler, "no ftp handler");
+
+ mEventQ = new ChannelEventQueue(static_cast<nsIParentChannel*>(this));
+}
+
+FTPChannelParent::~FTPChannelParent() { gFtpHandler->Release(); }
+
+void FTPChannelParent::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;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(FTPChannelParent, nsIStreamListener, nsIParentChannel,
+ nsIInterfaceRequestor, nsIRequestObserver,
+ nsIChannelEventSink, nsIFTPChannelParentInternal)
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::PFTPChannelParent
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent methods
+//-----------------------------------------------------------------------------
+
+bool FTPChannelParent::Init(const FTPChannelCreationArgs& aArgs) {
+ switch (aArgs.type()) {
+ case FTPChannelCreationArgs::TFTPChannelOpenArgs: {
+ const FTPChannelOpenArgs& a = aArgs.get_FTPChannelOpenArgs();
+ return DoAsyncOpen(a.uri(), a.startPos(), a.entityID(), a.uploadStream(),
+ a.loadInfo(), a.loadFlags());
+ }
+ case FTPChannelCreationArgs::TFTPChannelConnectArgs: {
+ const FTPChannelConnectArgs& cArgs = aArgs.get_FTPChannelConnectArgs();
+ return ConnectChannel(cArgs.channelId());
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown open type");
+ return false;
+ }
+}
+
+bool FTPChannelParent::DoAsyncOpen(const URIParams& aURI,
+ const uint64_t& aStartPos,
+ const nsCString& aEntityID,
+ const Maybe<IPCStream>& aUploadStream,
+ const Maybe<LoadInfoArgs>& aLoadInfoArgs,
+ const uint32_t& aLoadFlags) {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+ if (!uri) return false;
+
+#ifdef DEBUG
+ LOG(("FTPChannelParent DoAsyncOpen [this=%p uri=%s]\n", this,
+ uri->GetSpecOrDefault().get()));
+#endif
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ rv = mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfoArgs,
+ 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;
+
+ // later on mChannel may become an HTTP channel (we'll be redirected to one
+ // if we're using a proxy), but for now this is safe
+ nsFtpChannel* ftpChan = static_cast<nsFtpChannel*>(mChannel.get());
+
+ if (mPBOverride != kPBOverride_Unset) {
+ ftpChan->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
+ }
+ rv = ftpChan->SetNotificationCallbacks(this);
+ if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv);
+
+ nsCOMPtr<nsIInputStream> upload = DeserializeIPCStream(aUploadStream);
+ if (upload) {
+ // contentType and contentLength are ignored
+ rv = ftpChan->SetUploadStream(upload, ""_ns, 0);
+ if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv);
+ }
+
+ rv = ftpChan->ResumeAt(aStartPos, aEntityID);
+ if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv);
+
+ rv = ftpChan->AsyncOpen(this);
+
+ if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv);
+
+ return true;
+}
+
+bool FTPChannelParent::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 FTPChannelParent::RecvCancel(const nsresult& status) {
+ if (mChannel) mChannel->Cancel(status);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FTPChannelParent::RecvSuspend() {
+ if (mChannel) {
+ mChannel->Suspend();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FTPChannelParent::RecvResume() {
+ if (mChannel) {
+ mChannel->Resume();
+ }
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsIRequestObserver
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelParent::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("FTPChannelParent::OnStartRequest [this=%p]\n", this));
+
+ nsCOMPtr<nsIChannel> chan = do_QueryInterface(aRequest);
+ MOZ_ASSERT(chan);
+ NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
+
+ // Send down any permissions which are relevant to this URL if we are
+ // performing a document load.
+ 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);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ int64_t contentLength;
+ chan->GetContentLength(&contentLength);
+ nsCString contentType;
+ chan->GetContentType(contentType);
+ nsresult channelStatus = NS_OK;
+ chan->GetStatus(&channelStatus);
+
+ nsCString entityID;
+ nsCOMPtr<nsIResumableChannel> resChan = do_QueryInterface(aRequest);
+ MOZ_ASSERT(
+ resChan); // both FTP and HTTP should implement nsIResumableChannel
+ if (resChan) {
+ resChan->GetEntityID(entityID);
+ }
+
+ PRTime lastModified = 0;
+ nsCOMPtr<nsIFTPChannel> ftpChan = do_QueryInterface(aRequest);
+ if (ftpChan) {
+ ftpChan->GetLastModifiedTime(&lastModified);
+ }
+ nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(aRequest);
+ if (httpChan) {
+ Unused << httpChan->GetLastModifiedTime(&lastModified);
+ }
+
+ URIParams uriparam;
+ nsCOMPtr<nsIURI> uri;
+ chan->GetURI(getter_AddRefs(uri));
+ SerializeURI(uri, uriparam);
+
+ if (mIPCClosed ||
+ !SendOnStartRequest(channelStatus, contentLength, contentType,
+ lastModified, entityID, uriparam)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ LOG(("FTPChannelParent::OnStopRequest: [this=%p status=%" PRIu32 "]\n", this,
+ static_cast<uint32_t>(aStatusCode)));
+
+ if (mIPCClosed || !SendOnStopRequest(aStatusCode, mErrorMsg, mUseUTF8)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsIStreamListener
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelParent::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ LOG(("FTPChannelParent::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;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsIParentChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelParent::SetParentListener(ParentChannelListener* aListener) {
+ // Do not need ptr to ParentChannelListener.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ // One day, this should probably be filled in.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelParent::NotifyFlashPluginStateChanged(
+ nsIHttpChannel::FlashPluginState aState) {
+ // One day, this should probably be filled in.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelParent::SetClassifierMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ // One day, this should probably be filled in.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelParent::SetClassifierMatchedTrackingInfo(
+ const nsACString& aLists, const nsACString& aFullHashes) {
+ // One day, this should probably be filled in.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelParent::Delete() {
+ if (mIPCClosed || !SendDeleteSelf()) return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelParent::GetRemoteType(nsACString& aRemoteType) {
+ if (!CanSend()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ dom::PContentParent* pcp = Manager()->Manager();
+ aRemoteType = static_cast<dom::ContentParent*>(pcp)->GetRemoteType();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelParent::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);
+ }
+ } else if (uuid.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ uuid.Equals(NS_GET_IID(nsIAuthPrompt2))) {
+ nsCOMPtr<nsIAuthPromptProvider> provider(do_QueryObject(mBrowserParent));
+ if (provider) {
+ nsresult rv = provider->GetAuthPrompt(
+ nsIAuthPromptProvider::PROMPT_NORMAL, uuid, result);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+ return NS_OK;
+ }
+ }
+
+ // 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);
+}
+
+//-----------------------------------------------------------------------------
+// FTPChannelParent::nsIChannelEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+FTPChannelParent::AsyncOnChannelRedirect(
+ nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t redirectFlags,
+ nsIAsyncVerifyRedirectCallback* callback) {
+ nsCOMPtr<nsIFTPChannel> ftpChan = do_QueryInterface(newChannel);
+ if (!ftpChan) {
+ // when FTP is set to use HTTP proxying, we wind up getting redirected to an
+ // HTTP channel.
+ nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(newChannel);
+ if (!httpChan) return NS_ERROR_UNEXPECTED;
+ }
+ mChannel = newChannel;
+ callback->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FTPChannelParent::SetErrorMsg(const char* aMsg, bool aUseUTF8) {
+ mErrorMsg = aMsg;
+ mUseUTF8 = aUseUTF8;
+ return NS_OK;
+}
+
+//---------------------
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/ftp/FTPChannelParent.h b/netwerk/protocol/ftp/FTPChannelParent.h
new file mode 100644
index 0000000000..21ef7590bb
--- /dev/null
+++ b/netwerk/protocol/ftp/FTPChannelParent.h
@@ -0,0 +1,92 @@
+/* -*- 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_FTPChannelParent_h
+#define mozilla_net_FTPChannelParent_h
+
+#include "mozilla/net/PFTPChannelParent.h"
+#include "mozilla/net/NeckoParent.h"
+#include "nsIParentChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIChannelEventSink.h"
+#include "nsIFTPChannelParentInternal.h"
+
+class nsILoadContext;
+
+namespace mozilla {
+
+namespace dom {
+class BrowserParent;
+} // namespace dom
+
+namespace net {
+class ChannelEventQueue;
+
+class FTPChannelParent final : public PFTPChannelParent,
+ public nsIParentChannel,
+ public nsIInterfaceRequestor,
+ public nsIChannelEventSink,
+ public nsIFTPChannelParentInternal {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIPARENTCHANNEL
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSICHANNELEVENTSINK
+
+ FTPChannelParent(dom::BrowserParent* aIframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus);
+
+ bool Init(const FTPChannelCreationArgs& aOpenArgs);
+
+ NS_IMETHOD SetErrorMsg(const char* aMsg, bool aUseUTF8) override;
+
+ protected:
+ virtual ~FTPChannelParent();
+
+ bool DoAsyncOpen(const URIParams& aURI, const uint64_t& aStartPos,
+ const nsCString& aEntityID,
+ const Maybe<IPCStream>& aUploadStream,
+ const Maybe<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;
+
+ // if configured to use HTTP proxy for FTP, this can an an HTTP channel.
+ nsCOMPtr<nsIChannel> mChannel;
+
+ bool mIPCClosed;
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+
+ PBOverrideStatus mPBOverride;
+
+ // Set to the canceled status value if the main channel was canceled.
+ nsresult mStatus;
+
+ RefPtr<mozilla::dom::BrowserParent> mBrowserParent;
+
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ nsCString mErrorMsg;
+ bool mUseUTF8;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_FTPChannelParent_h
diff --git a/netwerk/protocol/ftp/PFTPChannel.ipdl b/netwerk/protocol/ftp/PFTPChannel.ipdl
new file mode 100644
index 0000000000..be0d8abaac
--- /dev/null
+++ b/netwerk/protocol/ftp/PFTPChannel.ipdl
@@ -0,0 +1,54 @@
+/* -*- 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 {
+
+async protocol PFTPChannel
+{
+ 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,
+ PRTime aLastModified,
+ nsCString aEntityID,
+ URIParams aURI);
+ async OnDataAvailable(nsresult channelStatus,
+ nsCString data,
+ uint64_t offset,
+ uint32_t count);
+ async OnStopRequest(nsresult channelStatus,
+ nsCString aErrorMsg,
+ bool aUseUTF8);
+ async FailedAsyncOpen(nsresult statusCode);
+
+ async DeleteSelf();
+};
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/protocol/ftp/doc/testdoc b/netwerk/protocol/ftp/doc/testdoc
new file mode 100644
index 0000000000..61fda16fcc
--- /dev/null
+++ b/netwerk/protocol/ftp/doc/testdoc
@@ -0,0 +1,4 @@
+Test
+here
+there
+everywhere
diff --git a/netwerk/protocol/ftp/ftpCore.h b/netwerk/protocol/ftp/ftpCore.h
new file mode 100644
index 0000000000..5c7433495b
--- /dev/null
+++ b/netwerk/protocol/ftp/ftpCore.h
@@ -0,0 +1,15 @@
+/* -*- 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 __ftpCore_h___
+#define __ftpCore_h___
+
+#include "nsError.h"
+
+/**
+ * Status nsresult codes
+ */
+
+#endif // __ftpCore_h___
diff --git a/netwerk/protocol/ftp/moz.build b/netwerk/protocol/ftp/moz.build
new file mode 100644
index 0000000000..914916bf29
--- /dev/null
+++ b/netwerk/protocol/ftp/moz.build
@@ -0,0 +1,50 @@
+# -*- 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: FTP")
+
+XPIDL_SOURCES += [
+ "nsIFTPChannel.idl",
+ "nsIFTPChannelParentInternal.idl",
+]
+
+XPIDL_MODULE = "necko_ftp"
+
+EXPORTS += [
+ "ftpCore.h",
+]
+
+EXPORTS.mozilla.net += [
+ "FTPChannelChild.h",
+ "FTPChannelParent.h",
+]
+
+UNIFIED_SOURCES += [
+ "FTPChannelChild.cpp",
+ "FTPChannelParent.cpp",
+ "nsFTPChannel.cpp",
+ "nsFtpConnectionThread.cpp",
+ "nsFtpControlConnection.cpp",
+ "nsFtpProtocolHandler.cpp",
+]
+
+IPDL_SOURCES += [
+ "PFTPChannel.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
+
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
diff --git a/netwerk/protocol/ftp/nsFTPChannel.cpp b/netwerk/protocol/ftp/nsFTPChannel.cpp
new file mode 100644
index 0000000000..97a7be1e68
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFTPChannel.cpp
@@ -0,0 +1,213 @@
+/* -*- 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/. */
+
+#include "nsFTPChannel.h"
+#include "nsFtpConnectionThread.h" // defines nsFtpState
+
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+extern LazyLogModule gFTPLog;
+
+// There are two transport connections established for an
+// ftp connection. One is used for the command channel , and
+// the other for the data channel. The command channel is the first
+// connection made and is used to negotiate the second, data, channel.
+// The data channel is driven by the command channel and is either
+// initiated by the server (PORT command) or by the client (PASV command).
+// Client initiation is the most common case and is attempted first.
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFtpChannel, nsBaseChannel, nsIUploadChannel,
+ nsIResumableChannel, nsIFTPChannel,
+ nsIProxiedChannel, nsIForcePendingChannel,
+ nsISupportsWeakReference)
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsFtpChannel::SetUploadStream(nsIInputStream* stream,
+ const nsACString& contentType,
+ int64_t contentLength) {
+ NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
+
+ mUploadStream = stream;
+
+ // NOTE: contentLength is intentionally ignored here.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::GetUploadStream(nsIInputStream** aStream) {
+ NS_ENSURE_ARG_POINTER(aStream);
+ nsCOMPtr<nsIInputStream> stream = mUploadStream;
+ stream.forget(aStream);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsFtpChannel::ResumeAt(uint64_t aStartPos, const nsACString& aEntityID) {
+ NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS);
+ mEntityID = aEntityID;
+ mStartPos = aStartPos;
+ mResumeRequested = (mStartPos || !mEntityID.IsEmpty());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::GetEntityID(nsACString& entityID) {
+ if (mEntityID.IsEmpty()) return NS_ERROR_NOT_RESUMABLE;
+
+ entityID = mEntityID;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsFtpChannel::GetProxyInfo(nsIProxyInfo** aProxyInfo) {
+ nsCOMPtr<nsIProxyInfo> info = ProxyInfo();
+ info.forget(aProxyInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFtpChannel::GetHttpProxyConnectResponseCode(
+ int32_t* aResponseCode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult nsFtpChannel::OpenContentStream(bool async, nsIInputStream** result,
+ nsIChannel** channel) {
+ if (!async) return NS_ERROR_NOT_IMPLEMENTED;
+
+ RefPtr<nsFtpState> state = new nsFtpState();
+
+ nsresult rv = state->Init(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ state.forget(result);
+ return NS_OK;
+}
+
+bool nsFtpChannel::GetStatusArg(nsresult status, nsString& statusArg) {
+ nsAutoCString host;
+ URI()->GetHost(host);
+ CopyUTF8toUTF16(host, statusArg);
+ return true;
+}
+
+void nsFtpChannel::OnCallbacksChanged() { mFTPEventSink = nullptr; }
+
+//-----------------------------------------------------------------------------
+
+namespace {
+
+class FTPEventSinkProxy final : public nsIFTPEventSink {
+ ~FTPEventSinkProxy() = default;
+
+ public:
+ explicit FTPEventSinkProxy(nsIFTPEventSink* aTarget)
+ : mTarget(aTarget), mEventTarget(GetCurrentEventTarget()) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIFTPEVENTSINK
+
+ class OnFTPControlLogRunnable : public Runnable {
+ public:
+ OnFTPControlLogRunnable(nsIFTPEventSink* aTarget, bool aServer,
+ const char* aMessage)
+ : mozilla::Runnable("FTPEventSinkProxy::OnFTPControlLogRunnable"),
+ mTarget(aTarget),
+ mServer(aServer),
+ mMessage(aMessage) {}
+
+ NS_DECL_NSIRUNNABLE
+
+ private:
+ nsCOMPtr<nsIFTPEventSink> mTarget;
+ bool mServer;
+ nsCString mMessage;
+ };
+
+ private:
+ nsCOMPtr<nsIFTPEventSink> mTarget;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+NS_IMPL_ISUPPORTS(FTPEventSinkProxy, nsIFTPEventSink)
+
+NS_IMETHODIMP
+FTPEventSinkProxy::OnFTPControlLog(bool aServer, const char* aMsg) {
+ RefPtr<OnFTPControlLogRunnable> r =
+ new OnFTPControlLogRunnable(mTarget, aServer, aMsg);
+ return mEventTarget->Dispatch(r, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+FTPEventSinkProxy::OnFTPControlLogRunnable::Run() {
+ mTarget->OnFTPControlLog(mServer, mMessage.get());
+ return NS_OK;
+}
+
+} // namespace
+
+void nsFtpChannel::GetFTPEventSink(nsCOMPtr<nsIFTPEventSink>& aResult) {
+ if (!mFTPEventSink) {
+ nsCOMPtr<nsIFTPEventSink> ftpSink;
+ GetCallback(ftpSink);
+ if (ftpSink) {
+ mFTPEventSink = new FTPEventSinkProxy(ftpSink);
+ }
+ }
+ aResult = mFTPEventSink;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::ForcePending(bool aForcePending) {
+ // Set true here so IsPending will return true.
+ // Required for callback diversion from child back to parent. In such cases
+ // OnStopRequest can be called in the parent before callbacks are diverted
+ // back from the child to the listener in the parent.
+ mForcePending = aForcePending;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::IsPending(bool* result) {
+ *result = Pending();
+ return NS_OK;
+}
+
+bool nsFtpChannel::Pending() const {
+ return nsBaseChannel::Pending() || mForcePending;
+}
+
+NS_IMETHODIMP
+nsFtpChannel::Suspend() {
+ LOG(("nsFtpChannel::Suspend [this=%p]\n", this));
+ NS_ENSURE_TRUE(Pending(), NS_ERROR_NOT_AVAILABLE);
+
+ ++mSuspendCount;
+ return nsBaseChannel::Suspend();
+}
+
+NS_IMETHODIMP
+nsFtpChannel::Resume() {
+ LOG(("nsFtpChannel::Resume [this=%p]\n", this));
+ NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
+ --mSuspendCount;
+ return nsBaseChannel::Resume();
+}
diff --git a/netwerk/protocol/ftp/nsFTPChannel.h b/netwerk/protocol/ftp/nsFTPChannel.h
new file mode 100644
index 0000000000..52dc23506a
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFTPChannel.h
@@ -0,0 +1,115 @@
+/* -*- 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/. */
+
+#ifndef nsFTPChannel_h___
+#define nsFTPChannel_h___
+
+#include "nsBaseChannel.h"
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIFTPChannel.h"
+#include "nsIForcePendingChannel.h"
+#include "nsIUploadChannel.h"
+#include "nsIProxyInfo.h"
+#include "nsIProxiedChannel.h"
+#include "nsIResumableChannel.h"
+#include "nsWeakReference.h"
+
+class nsIURI;
+
+class nsFtpChannel final : public nsBaseChannel,
+ public nsIFTPChannel,
+ public nsIUploadChannel,
+ public nsIResumableChannel,
+ public nsIProxiedChannel,
+ public nsIForcePendingChannel,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIUPLOADCHANNEL
+ NS_DECL_NSIRESUMABLECHANNEL
+ NS_DECL_NSIPROXIEDCHANNEL
+
+ nsFtpChannel(nsIURI* uri, nsIProxyInfo* pi)
+ : mProxyInfo(pi),
+ mStartPos(0),
+ mResumeRequested(false),
+ mLastModifiedTime(0),
+ mForcePending(false),
+ mSuspendCount(0) {
+ SetURI(uri);
+ }
+
+ void UpdateURI(nsIURI* aURI) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(), "Not thread-safe.");
+ mURI = aURI;
+ }
+
+ nsIProxyInfo* ProxyInfo() { return mProxyInfo; }
+
+ void SetProxyInfo(nsIProxyInfo* pi) { mProxyInfo = pi; }
+
+ NS_IMETHOD IsPending(bool* result) override;
+
+ // This is a short-cut to calling nsIRequest::IsPending().
+ // Overrides Pending in nsBaseChannel.
+ bool Pending() const override;
+
+ // Were we asked to resume a download?
+ bool ResumeRequested() { return mResumeRequested; }
+
+ // Download from this byte offset
+ uint64_t StartPos() { return mStartPos; }
+
+ // ID of the entity to resume downloading
+ const nsCString& EntityID() { return mEntityID; }
+ void SetEntityID(const nsACString& entityID) { mEntityID = entityID; }
+
+ NS_IMETHOD GetLastModifiedTime(PRTime* lastModifiedTime) override {
+ *lastModifiedTime = mLastModifiedTime;
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetLastModifiedTime(PRTime lastModifiedTime) override {
+ mLastModifiedTime = lastModifiedTime;
+ return NS_OK;
+ }
+
+ // Data stream to upload
+ nsIInputStream* UploadStream() { return mUploadStream; }
+
+ // Helper function for getting the nsIFTPEventSink.
+ void GetFTPEventSink(nsCOMPtr<nsIFTPEventSink>& aResult);
+
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+
+ public:
+ NS_IMETHOD ForcePending(bool aForcePending) override;
+
+ protected:
+ virtual ~nsFtpChannel() = default;
+ virtual nsresult OpenContentStream(bool async, nsIInputStream** result,
+ nsIChannel** channel) override;
+ virtual bool GetStatusArg(nsresult status, nsString& statusArg) override;
+ virtual void OnCallbacksChanged() override;
+
+ private:
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+ nsCOMPtr<nsIFTPEventSink> mFTPEventSink;
+ nsCOMPtr<nsIInputStream> mUploadStream;
+ uint64_t mStartPos;
+ nsCString mEntityID;
+ bool mResumeRequested;
+ PRTime mLastModifiedTime;
+ bool mForcePending;
+
+ // Current suspension depth for this channel object
+ uint32_t mSuspendCount;
+};
+
+#endif /* nsFTPChannel_h___ */
diff --git a/netwerk/protocol/ftp/nsFtpConnectionThread.cpp b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp
new file mode 100644
index 0000000000..c93a2c604b
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp
@@ -0,0 +1,1960 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set tw=80 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 <ctype.h>
+
+#include "prprf.h"
+#include "mozilla/Logging.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/TextUtils.h"
+#include "prtime.h"
+
+#include "nsIOService.h"
+#include "nsFTPChannel.h"
+#include "nsFtpConnectionThread.h"
+#include "nsFtpControlConnection.h"
+#include "nsFtpProtocolHandler.h"
+#include "netCore.h"
+#include "nsCRT.h"
+#include "nsEscape.h"
+#include "nsMimeTypes.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIAsyncStreamCopier.h"
+#include "nsThreadUtils.h"
+#include "nsStreamUtils.h"
+#include "nsIURL.h"
+#include "nsISocketTransport.h"
+#include "nsIPrefBranch.h"
+#include "nsAuthInformationHolder.h"
+#include "nsIProtocolProxyService.h"
+#include "nsICancelable.h"
+#include "nsIOutputStream.h"
+#include "nsIProtocolHandler.h"
+#include "nsIProxyInfo.h"
+#include "nsISocketTransportService.h"
+#include "nsIURI.h"
+#include "nsILoadInfo.h"
+#include "nsIAuthPrompt2.h"
+#include "nsIFTPChannelParentInternal.h"
+#include "mozilla/Telemetry.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+extern LazyLogModule gFTPLog;
+#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
+#define LOG_INFO(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Info, args)
+
+// remove FTP parameters (starting with ";") from the path
+static void removeParamsFromPath(nsCString& path) {
+ int32_t index = path.FindChar(';');
+ if (index >= 0) {
+ path.SetLength(index);
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsFtpState, nsBaseContentStream,
+ nsIInputStreamCallback, nsITransportEventSink,
+ nsIRequestObserver, nsIProtocolProxyCallback)
+
+nsFtpState::nsFtpState()
+ : nsBaseContentStream(true),
+ mState(FTP_INIT),
+ mNextState(FTP_S_USER),
+ mKeepRunning(true),
+ mResponseCode(0),
+ mReceivedControlData(false),
+ mTryingCachedControl(false),
+ mRETRFailed(false),
+ mFileSize(kJS_MAX_SAFE_UINTEGER),
+ mServerType(FTP_GENERIC_TYPE),
+ mAction(GET),
+ mAnonymous(true),
+ mRetryPass(false),
+ mStorReplyReceived(false),
+ mRlist1xxReceived(false),
+ mRretr1xxReceived(false),
+ mRstor1xxReceived(false),
+ mInternalError(NS_OK),
+ mReconnectAndLoginAgain(false),
+ mCacheConnection(true),
+ mPort(21),
+ mAddressChecked(false),
+ mServerIsIPv6(false),
+ mUseUTF8(false),
+ mControlStatus(NS_OK),
+ mDeferredCallbackPending(false) {
+ this->mServerAddress.raw.family = 0;
+ this->mServerAddress.inet = {};
+ LOG_INFO(("FTP:(%p) nsFtpState created", this));
+
+ // make sure handler stays around
+ mHandler = gFtpHandler;
+}
+
+nsFtpState::~nsFtpState() {
+ LOG_INFO(("FTP:(%p) nsFtpState destroyed", this));
+
+ if (mProxyRequest) mProxyRequest->Cancel(NS_ERROR_FAILURE);
+
+ // release reference to handler
+ mHandler = nullptr;
+}
+
+// nsIInputStreamCallback implementation
+NS_IMETHODIMP
+nsFtpState::OnInputStreamReady(nsIAsyncInputStream* aInStream) {
+ LOG(("FTP:(%p) data stream ready\n", this));
+
+ // We are receiving a notification from our data stream, so just forward it
+ // on to our stream callback.
+ if (HasPendingCallback()) DispatchCallbackSync();
+
+ return NS_OK;
+}
+
+void nsFtpState::OnControlDataAvailable(const char* aData, uint32_t aDataLen) {
+ LOG(("FTP:(%p) control data available [%u]\n", this, aDataLen));
+ mControlConnection->WaitData(this); // queue up another call
+
+ if (!mReceivedControlData) {
+ // parameter can be null cause the channel fills them in.
+ OnTransportStatus(nullptr, NS_NET_STATUS_BEGIN_FTP_TRANSACTION, 0, 0);
+ mReceivedControlData = true;
+ }
+
+ // Sometimes we can get two responses in the same packet, eg from LIST.
+ // So we need to parse the response line by line
+
+ nsCString buffer = mControlReadCarryOverBuf;
+
+ // Clear the carryover buf - if we still don't have a line, then it will
+ // be reappended below
+ mControlReadCarryOverBuf.Truncate();
+
+ buffer.Append(aData, aDataLen);
+
+ const char* currLine = buffer.get();
+ while (*currLine && mKeepRunning) {
+ int32_t eolLength = strcspn(currLine, CRLF);
+ int32_t currLineLength = strlen(currLine);
+
+ // if currLine is empty or only contains CR or LF, then bail. we can
+ // sometimes get an ODA event with the full response line + CR without
+ // the trailing LF. the trailing LF might come in the next ODA event.
+ // because we are happy enough to process a response line ending only
+ // in CR, we need to take care to discard the extra LF (bug 191220).
+ if (eolLength == 0 && currLineLength <= 1) break;
+
+ if (eolLength == currLineLength) {
+ mControlReadCarryOverBuf.Assign(currLine);
+ break;
+ }
+
+ // Append the current segment, including the LF
+ nsAutoCString line;
+ int32_t crlfLength = 0;
+
+ if ((currLineLength > eolLength) && (currLine[eolLength] == nsCRT::CR) &&
+ (currLine[eolLength + 1] == nsCRT::LF)) {
+ crlfLength = 2; // CR +LF
+ } else {
+ crlfLength = 1; // + LF or CR
+ }
+
+ line.Assign(currLine, eolLength + crlfLength);
+
+ // Does this start with a response code?
+ bool startNum = (line.Length() >= 3 && IsAsciiDigit(line[0]) &&
+ IsAsciiDigit(line[1]) && IsAsciiDigit(line[2]));
+
+ if (mResponseMsg.IsEmpty()) {
+ // If we get here, then we know that we have a complete line, and
+ // that it is the first one
+
+ NS_ASSERTION(line.Length() > 4 && startNum,
+ "Read buffer doesn't include response code");
+
+ mResponseCode = atoi(PromiseFlatCString(Substring(line, 0, 3)).get());
+ }
+
+ mResponseMsg.Append(line);
+
+ // This is the last line if its 3 numbers followed by a space
+ if (startNum && line[3] == ' ') {
+ // yup. last line, let's move on.
+ if (mState == mNextState) {
+ NS_ERROR("ftp read state mixup");
+ mInternalError = NS_ERROR_FAILURE;
+ mState = FTP_ERROR;
+ } else {
+ mState = mNextState;
+ }
+
+ nsCOMPtr<nsIFTPEventSink> ftpSink;
+ mChannel->GetFTPEventSink(ftpSink);
+ if (ftpSink) ftpSink->OnFTPControlLog(true, mResponseMsg.get());
+
+ nsresult rv = Process();
+ mResponseMsg.Truncate();
+ if (NS_FAILED(rv)) {
+ CloseWithStatus(rv);
+ return;
+ }
+ }
+
+ currLine = currLine + eolLength + crlfLength;
+ }
+}
+
+void nsFtpState::OnControlError(nsresult status) {
+ NS_ASSERTION(NS_FAILED(status), "expecting error condition");
+
+ LOG(("FTP:(%p) CC(%p) error [%" PRIx32 " was-cached=%u]\n", this,
+ mControlConnection.get(), static_cast<uint32_t>(status),
+ mTryingCachedControl));
+
+ mControlStatus = status;
+ if (mReconnectAndLoginAgain && NS_SUCCEEDED(mInternalError)) {
+ mReconnectAndLoginAgain = false;
+ mAnonymous = false;
+ mControlStatus = NS_OK;
+ Connect();
+ } else if (mTryingCachedControl && NS_SUCCEEDED(mInternalError)) {
+ mTryingCachedControl = false;
+ Connect();
+ } else {
+ CloseWithStatus(status);
+ }
+}
+
+nsresult nsFtpState::EstablishControlConnection() {
+ NS_ASSERTION(!mControlConnection, "we already have a control connection");
+
+ nsresult rv;
+
+ LOG(("FTP:(%p) trying cached control\n", this));
+
+ // Look to see if we can use a cached control connection:
+ RefPtr<nsFtpControlConnection> connection;
+ // Don't use cached control if anonymous (bug #473371)
+ if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
+ gFtpHandler->RemoveConnection(mChannel->URI(), getter_AddRefs(connection));
+
+ if (connection) {
+ mControlConnection.swap(connection);
+ if (mControlConnection->IsAlive()) {
+ // set stream listener of the control connection to be us.
+ mControlConnection->WaitData(this);
+
+ // read cached variables into us.
+ mServerType = mControlConnection->mServerType;
+ mPassword = mControlConnection->mPassword;
+ mPwd = mControlConnection->mPwd;
+ mUseUTF8 = mControlConnection->mUseUTF8;
+ mTryingCachedControl = true;
+
+ // we have to set charset to connection if server supports utf-8
+ if (mUseUTF8) mChannel->SetContentCharset("UTF-8"_ns);
+
+ // we're already connected to this server, skip login.
+ mState = FTP_S_PASV;
+ mResponseCode = 530; // assume the control connection was dropped.
+ mControlStatus = NS_OK;
+ mReceivedControlData = false; // For this request, we have not.
+
+ // if we succeed, return. Otherwise, we need to create a transport
+ rv = mControlConnection->Connect(mChannel->ProxyInfo(), this);
+ if (NS_SUCCEEDED(rv)) return rv;
+ }
+ LOG(("FTP:(%p) cached CC(%p) is unusable\n", this,
+ mControlConnection.get()));
+
+ mControlConnection->WaitData(nullptr);
+ mControlConnection = nullptr;
+ }
+
+ LOG(("FTP:(%p) creating CC\n", this));
+
+ mState = FTP_READ_BUF;
+ mNextState = FTP_S_USER;
+
+ nsAutoCString host;
+ rv = mChannel->URI()->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ mControlConnection = new nsFtpControlConnection(host, mPort);
+ if (!mControlConnection) return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = mControlConnection->Connect(mChannel->ProxyInfo(), this);
+ if (NS_FAILED(rv)) {
+ LOG(("FTP:(%p) CC(%p) failed to connect [rv=%" PRIx32 "]\n", this,
+ mControlConnection.get(), static_cast<uint32_t>(rv)));
+ mControlConnection = nullptr;
+ return rv;
+ }
+
+ return mControlConnection->WaitData(this);
+}
+
+void nsFtpState::MoveToNextState(FTP_STATE nextState) {
+ if (NS_FAILED(mInternalError)) {
+ mState = FTP_ERROR;
+ LOG(("FTP:(%p) FAILED (%" PRIx32 ")\n", this,
+ static_cast<uint32_t>(mInternalError)));
+ } else {
+ mState = FTP_READ_BUF;
+ mNextState = nextState;
+ }
+}
+
+nsresult nsFtpState::Process() {
+ nsresult rv = NS_OK;
+ bool processingRead = true;
+
+ while (mKeepRunning && processingRead) {
+ switch (mState) {
+ case FTP_COMMAND_CONNECT:
+ KillControlConnection();
+ LOG(("FTP:(%p) establishing CC", this));
+ mInternalError = EstablishControlConnection(); // sets mState
+ if (NS_FAILED(mInternalError)) {
+ mState = FTP_ERROR;
+ LOG(("FTP:(%p) FAILED\n", this));
+ } else {
+ LOG(("FTP:(%p) SUCCEEDED\n", this));
+ }
+ break;
+
+ case FTP_READ_BUF:
+ LOG(("FTP:(%p) Waiting for CC(%p)\n", this, mControlConnection.get()));
+ processingRead = false;
+ break;
+
+ case FTP_ERROR: // xx needs more work to handle dropped control
+ // connection cases
+ if ((mTryingCachedControl && mResponseCode == 530 &&
+ mInternalError == NS_ERROR_FTP_PASV) ||
+ (mResponseCode == 425 && mInternalError == NS_ERROR_FTP_PASV)) {
+ // The user was logged out during an pasv operation
+ // we want to restart this request with a new control
+ // channel.
+ mState = FTP_COMMAND_CONNECT;
+ } else if (mResponseCode == 421 &&
+ mInternalError != NS_ERROR_FTP_LOGIN) {
+ // The command channel dropped for some reason.
+ // Fire it back up, unless we were trying to login
+ // in which case the server might just be telling us
+ // that the max number of users has been reached...
+ mState = FTP_COMMAND_CONNECT;
+ } else if (mAnonymous && mInternalError == NS_ERROR_FTP_LOGIN) {
+ // If the login was anonymous, and it failed, try again with a
+ // username Don't reuse old control connection, see #386167
+ mAnonymous = false;
+ mState = FTP_COMMAND_CONNECT;
+ } else {
+ LOG(("FTP:(%p) FTP_ERROR - calling StopProcessing\n", this));
+ rv = StopProcessing();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
+ processingRead = false;
+ }
+ break;
+
+ case FTP_COMPLETE:
+ LOG(("FTP:(%p) COMPLETE\n", this));
+ rv = StopProcessing();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
+ processingRead = false;
+ break;
+
+ // USER
+ case FTP_S_USER:
+ rv = S_user();
+
+ if (NS_FAILED(rv)) mInternalError = NS_ERROR_FTP_LOGIN;
+
+ MoveToNextState(FTP_R_USER);
+ break;
+
+ case FTP_R_USER:
+ mState = R_user();
+
+ if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_LOGIN;
+
+ break;
+ // PASS
+ case FTP_S_PASS:
+ rv = S_pass();
+
+ if (NS_FAILED(rv)) mInternalError = NS_ERROR_FTP_LOGIN;
+
+ MoveToNextState(FTP_R_PASS);
+ break;
+
+ case FTP_R_PASS:
+ mState = R_pass();
+
+ if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_LOGIN;
+
+ break;
+ // ACCT
+ case FTP_S_ACCT:
+ rv = S_acct();
+
+ if (NS_FAILED(rv)) mInternalError = NS_ERROR_FTP_LOGIN;
+
+ MoveToNextState(FTP_R_ACCT);
+ break;
+
+ case FTP_R_ACCT:
+ mState = R_acct();
+
+ if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_LOGIN;
+
+ break;
+
+ // SYST
+ case FTP_S_SYST:
+ rv = S_syst();
+
+ if (NS_FAILED(rv)) mInternalError = NS_ERROR_FTP_LOGIN;
+
+ MoveToNextState(FTP_R_SYST);
+ break;
+
+ case FTP_R_SYST:
+ mState = R_syst();
+
+ if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_LOGIN;
+
+ break;
+
+ // TYPE
+ case FTP_S_TYPE:
+ rv = S_type();
+
+ if (NS_FAILED(rv)) mInternalError = rv;
+
+ MoveToNextState(FTP_R_TYPE);
+ break;
+
+ case FTP_R_TYPE:
+ mState = R_type();
+
+ if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE;
+
+ break;
+ // CWD
+ case FTP_S_CWD:
+ rv = S_cwd();
+
+ if (NS_FAILED(rv)) mInternalError = NS_ERROR_FTP_CWD;
+
+ MoveToNextState(FTP_R_CWD);
+ break;
+
+ case FTP_R_CWD:
+ mState = R_cwd();
+
+ if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_CWD;
+ break;
+
+ // LIST
+ case FTP_S_LIST:
+ rv = S_list();
+
+ if (rv == NS_ERROR_NOT_RESUMABLE) {
+ mInternalError = rv;
+ } else if (NS_FAILED(rv)) {
+ mInternalError = NS_ERROR_FTP_CWD;
+ }
+
+ MoveToNextState(FTP_R_LIST);
+ break;
+
+ case FTP_R_LIST:
+ mState = R_list();
+
+ if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE;
+
+ break;
+
+ // SIZE
+ case FTP_S_SIZE:
+ rv = S_size();
+
+ if (NS_FAILED(rv)) mInternalError = rv;
+
+ MoveToNextState(FTP_R_SIZE);
+ break;
+
+ case FTP_R_SIZE:
+ mState = R_size();
+
+ if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE;
+
+ break;
+
+ // REST
+ case FTP_S_REST:
+ rv = S_rest();
+
+ if (NS_FAILED(rv)) mInternalError = rv;
+
+ MoveToNextState(FTP_R_REST);
+ break;
+
+ case FTP_R_REST:
+ mState = R_rest();
+
+ if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE;
+
+ break;
+
+ // MDTM
+ case FTP_S_MDTM:
+ rv = S_mdtm();
+ if (NS_FAILED(rv)) mInternalError = rv;
+ MoveToNextState(FTP_R_MDTM);
+ break;
+
+ case FTP_R_MDTM:
+ mState = R_mdtm();
+
+ // Don't want to overwrite a more explicit status code
+ if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
+ mInternalError = NS_ERROR_FAILURE;
+
+ break;
+
+ // RETR
+ case FTP_S_RETR:
+ rv = S_retr();
+
+ if (NS_FAILED(rv)) mInternalError = rv;
+
+ MoveToNextState(FTP_R_RETR);
+ break;
+
+ case FTP_R_RETR:
+
+ mState = R_retr();
+
+ if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE;
+
+ break;
+
+ // STOR
+ case FTP_S_STOR:
+ rv = S_stor();
+
+ if (NS_FAILED(rv)) mInternalError = rv;
+
+ MoveToNextState(FTP_R_STOR);
+ break;
+
+ case FTP_R_STOR:
+ mState = R_stor();
+
+ if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE;
+
+ break;
+
+ // PASV
+ case FTP_S_PASV:
+ rv = S_pasv();
+
+ if (NS_FAILED(rv)) mInternalError = NS_ERROR_FTP_PASV;
+
+ MoveToNextState(FTP_R_PASV);
+ break;
+
+ case FTP_R_PASV:
+ mState = R_pasv();
+
+ if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_PASV;
+
+ break;
+
+ // PWD
+ case FTP_S_PWD:
+ rv = S_pwd();
+
+ if (NS_FAILED(rv)) mInternalError = NS_ERROR_FTP_PWD;
+
+ MoveToNextState(FTP_R_PWD);
+ break;
+
+ case FTP_R_PWD:
+ mState = R_pwd();
+
+ if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_PWD;
+
+ break;
+
+ // FEAT for RFC2640 support
+ case FTP_S_FEAT:
+ rv = S_feat();
+
+ if (NS_FAILED(rv)) mInternalError = rv;
+
+ MoveToNextState(FTP_R_FEAT);
+ break;
+
+ case FTP_R_FEAT:
+ mState = R_feat();
+
+ // Don't want to overwrite a more explicit status code
+ if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
+ mInternalError = NS_ERROR_FAILURE;
+ break;
+
+ // OPTS for some non-RFC2640-compliant servers support
+ case FTP_S_OPTS:
+ rv = S_opts();
+
+ if (NS_FAILED(rv)) mInternalError = rv;
+
+ MoveToNextState(FTP_R_OPTS);
+ break;
+
+ case FTP_R_OPTS:
+ mState = R_opts();
+
+ // Don't want to overwrite a more explicit status code
+ if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
+ mInternalError = NS_ERROR_FAILURE;
+ break;
+
+ default:;
+ }
+ }
+
+ return rv;
+}
+
+///////////////////////////////////
+// STATE METHODS
+///////////////////////////////////
+nsresult nsFtpState::S_user() {
+ // some servers on connect send us a 421 or 521. (84525) (141784)
+ if ((mResponseCode == 421) || (mResponseCode == 521)) return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsAutoCString usernameStr("USER ");
+
+ mResponseMsg = "";
+
+ if (mAnonymous) {
+ mReconnectAndLoginAgain = true;
+ usernameStr.AppendLiteral("anonymous");
+ } else {
+ mReconnectAndLoginAgain = false;
+ if (mUsername.IsEmpty()) {
+ // No prompt for anonymous requests (bug #473371)
+ if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAuthPrompt2> prompter;
+ NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel),
+ getter_AddRefs(prompter));
+ if (!prompter) return NS_ERROR_NOT_INITIALIZED;
+
+ RefPtr<nsAuthInformationHolder> info = new nsAuthInformationHolder(
+ nsIAuthInformation::AUTH_HOST, u""_ns, ""_ns);
+
+ bool retval;
+ rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE, info,
+ &retval);
+
+ // if the user canceled or didn't supply a username we want to fail
+ if (NS_FAILED(rv) || !retval || info->User().IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ mUsername = info->User();
+ mPassword = info->Password();
+ }
+ // XXX Is UTF-8 the best choice?
+ AppendUTF16toUTF8(mUsername, usernameStr);
+ }
+
+ usernameStr.AppendLiteral(CRLF);
+
+ return SendFTPCommand(usernameStr);
+}
+
+FTP_STATE
+nsFtpState::R_user() {
+ mReconnectAndLoginAgain = false;
+ if (mResponseCode / 100 == 3) {
+ // send off the password
+ return FTP_S_PASS;
+ }
+ if (mResponseCode / 100 == 2) {
+ // no password required, we're already logged in
+ return FTP_S_SYST;
+ }
+ if (mResponseCode / 100 == 5) {
+ // problem logging in. typically this means the server
+ // has reached it's user limit.
+ return FTP_ERROR;
+ }
+ // LOGIN FAILED
+ return FTP_ERROR;
+}
+
+nsresult nsFtpState::S_pass() {
+ nsresult rv;
+ nsAutoCString passwordStr("PASS ");
+
+ mResponseMsg = "";
+
+ if (mAnonymous) {
+ if (!mPassword.IsEmpty()) {
+ // XXX Is UTF-8 the best choice?
+ AppendUTF16toUTF8(mPassword, passwordStr);
+ } else {
+ nsAutoCString anonPassword;
+ bool useRealEmail = false;
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ rv = prefs->GetBoolPref("advanced.mailftp", &useRealEmail);
+ if (NS_SUCCEEDED(rv) && useRealEmail) {
+ prefs->GetCharPref("network.ftp.anonymous_password", anonPassword);
+ }
+ }
+ if (!anonPassword.IsEmpty()) {
+ passwordStr.AppendASCII(anonPassword.get());
+ } else {
+ // We need to default to a valid email address - bug 101027
+ // example.com is reserved (rfc2606), so use that
+ passwordStr.AppendLiteral("mozilla@example.com");
+ }
+ }
+ } else {
+ if (mPassword.IsEmpty() || mRetryPass) {
+ // No prompt for anonymous requests (bug #473371)
+ if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAuthPrompt2> prompter;
+ NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel),
+ getter_AddRefs(prompter));
+ if (!prompter) return NS_ERROR_NOT_INITIALIZED;
+
+ RefPtr<nsAuthInformationHolder> info = new nsAuthInformationHolder(
+ nsIAuthInformation::AUTH_HOST | nsIAuthInformation::ONLY_PASSWORD,
+ u""_ns, ""_ns);
+
+ info->SetUserInternal(mUsername);
+
+ bool retval;
+ rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE, info,
+ &retval);
+
+ // we want to fail if the user canceled. Note here that if they want
+ // a blank password, we will pass it along.
+ if (NS_FAILED(rv) || !retval) return NS_ERROR_FAILURE;
+
+ mPassword = info->Password();
+ }
+ // XXX Is UTF-8 the best choice?
+ AppendUTF16toUTF8(mPassword, passwordStr);
+ }
+
+ passwordStr.AppendLiteral(CRLF);
+
+ return SendFTPCommand(passwordStr);
+}
+
+FTP_STATE
+nsFtpState::R_pass() {
+ if (mResponseCode / 100 == 3) {
+ // send account info
+ return FTP_S_ACCT;
+ }
+ if (mResponseCode / 100 == 2) {
+ // logged in
+ return FTP_S_SYST;
+ }
+ if (mResponseCode == 503) {
+ // start over w/ the user command.
+ // note: the password was successful, and it's stored in mPassword
+ mRetryPass = false;
+ return FTP_S_USER;
+ }
+ if (mResponseCode / 100 == 5 || mResponseCode == 421) {
+ // There is no difference between a too-many-users error,
+ // a wrong-password error, or any other sort of error
+
+ if (!mAnonymous) mRetryPass = true;
+
+ return FTP_ERROR;
+ }
+ // unexpected response code
+ return FTP_ERROR;
+}
+
+nsresult nsFtpState::S_pwd() {
+ return SendFTPCommand(nsLiteralCString("PWD" CRLF));
+}
+
+FTP_STATE
+nsFtpState::R_pwd() {
+ // Error response to PWD command isn't fatal, but don't cache the connection
+ // if CWD command is sent since correct mPwd is needed for further requests.
+ if (mResponseCode / 100 != 2) return FTP_S_TYPE;
+
+ nsAutoCString respStr(mResponseMsg);
+ int32_t pos = respStr.FindChar('"');
+ if (pos > -1) {
+ respStr.Cut(0, pos + 1);
+ pos = respStr.FindChar('"');
+ if (pos > -1) {
+ respStr.Truncate(pos);
+ if (mServerType == FTP_VMS_TYPE) ConvertDirspecFromVMS(respStr);
+ if (respStr.IsEmpty() || respStr.Last() != '/') respStr.Append('/');
+ mPwd = respStr;
+ }
+ }
+ return FTP_S_TYPE;
+}
+
+nsresult nsFtpState::S_syst() {
+ return SendFTPCommand(nsLiteralCString("SYST" CRLF));
+}
+
+FTP_STATE
+nsFtpState::R_syst() {
+ if (mResponseCode / 100 == 2) {
+ if ((mResponseMsg.Find("L8") > -1) || (mResponseMsg.Find("UNIX") > -1) ||
+ (mResponseMsg.Find("BSD") > -1) ||
+ (mResponseMsg.Find("MACOS Peter's Server") > -1) ||
+ (mResponseMsg.Find("MACOS WebSTAR FTP") > -1) ||
+ (mResponseMsg.Find("MVS") > -1) || (mResponseMsg.Find("OS/390") > -1) ||
+ (mResponseMsg.Find("OS/400") > -1)) {
+ mServerType = FTP_UNIX_TYPE;
+ } else if ((mResponseMsg.Find("WIN32", true) > -1) ||
+ (mResponseMsg.Find("windows", true) > -1)) {
+ mServerType = FTP_NT_TYPE;
+ } else if (mResponseMsg.Find("OS/2", true) > -1) {
+ mServerType = FTP_OS2_TYPE;
+ } else if (mResponseMsg.Find("VMS", true) > -1) {
+ mServerType = FTP_VMS_TYPE;
+ } else {
+ NS_ERROR("Server type list format unrecognized.");
+
+ // clear mResponseMsg, which is displayed to the user.
+ mResponseMsg = "";
+ return FTP_ERROR;
+ }
+
+ return FTP_S_FEAT;
+ }
+
+ if (mResponseCode / 100 == 5) {
+ // server didn't like the SYST command. Probably (500, 501, 502)
+ // No clue. We will just hope it is UNIX type server.
+ mServerType = FTP_UNIX_TYPE;
+
+ return FTP_S_FEAT;
+ }
+ return FTP_ERROR;
+}
+
+nsresult nsFtpState::S_acct() {
+ return SendFTPCommand(nsLiteralCString("ACCT noaccount" CRLF));
+}
+
+FTP_STATE
+nsFtpState::R_acct() {
+ if (mResponseCode / 100 == 2) return FTP_S_SYST;
+
+ return FTP_ERROR;
+}
+
+nsresult nsFtpState::S_type() {
+ return SendFTPCommand(nsLiteralCString("TYPE I" CRLF));
+}
+
+FTP_STATE
+nsFtpState::R_type() {
+ if (mResponseCode / 100 != 2) return FTP_ERROR;
+
+ return FTP_S_PASV;
+}
+
+nsresult nsFtpState::S_cwd() {
+ // Don't cache the connection if PWD command failed
+ if (mPwd.IsEmpty()) mCacheConnection = false;
+
+ nsAutoCString cwdStr;
+ if (mAction != PUT) cwdStr = mPath;
+ if (cwdStr.IsEmpty() || cwdStr.First() != '/') cwdStr.Insert(mPwd, 0);
+ if (mServerType == FTP_VMS_TYPE) ConvertDirspecToVMS(cwdStr);
+ cwdStr.InsertLiteral("CWD ", 0);
+ cwdStr.AppendLiteral(CRLF);
+
+ return SendFTPCommand(cwdStr);
+}
+
+FTP_STATE
+nsFtpState::R_cwd() {
+ if (mResponseCode / 100 == 2) {
+ if (mAction == PUT) return FTP_S_STOR;
+
+ return FTP_S_LIST;
+ }
+
+ return FTP_ERROR;
+}
+
+nsresult nsFtpState::S_size() {
+ nsAutoCString sizeBuf(mPath);
+ if (sizeBuf.IsEmpty() || sizeBuf.First() != '/') sizeBuf.Insert(mPwd, 0);
+ if (mServerType == FTP_VMS_TYPE) ConvertFilespecToVMS(sizeBuf);
+ sizeBuf.InsertLiteral("SIZE ", 0);
+ sizeBuf.AppendLiteral(CRLF);
+
+ return SendFTPCommand(sizeBuf);
+}
+
+FTP_STATE
+nsFtpState::R_size() {
+ if (mResponseCode / 100 == 2) {
+ PR_sscanf(mResponseMsg.get() + 4, "%llu", &mFileSize);
+ mChannel->SetContentLength(mFileSize);
+ }
+
+ // We may want to be able to resume this
+ return FTP_S_MDTM;
+}
+
+nsresult nsFtpState::S_mdtm() {
+ nsAutoCString mdtmBuf(mPath);
+ if (mdtmBuf.IsEmpty() || mdtmBuf.First() != '/') mdtmBuf.Insert(mPwd, 0);
+ if (mServerType == FTP_VMS_TYPE) ConvertFilespecToVMS(mdtmBuf);
+ mdtmBuf.InsertLiteral("MDTM ", 0);
+ mdtmBuf.AppendLiteral(CRLF);
+
+ return SendFTPCommand(mdtmBuf);
+}
+
+FTP_STATE
+nsFtpState::R_mdtm() {
+ if (mResponseCode == 213) {
+ mResponseMsg.Cut(0, 4);
+ mResponseMsg.Trim(" \t\r\n");
+ // yyyymmddhhmmss
+ if (mResponseMsg.Length() != 14) {
+ NS_ASSERTION(mResponseMsg.Length() == 14, "Unknown MDTM response");
+ } else {
+ mModTime = mResponseMsg;
+
+ // Save lastModified time for downloaded files.
+ nsAutoCString timeString;
+ nsresult error;
+ PRExplodedTime exTime;
+
+ mResponseMsg.Mid(timeString, 0, 4);
+ exTime.tm_year = timeString.ToInteger(&error);
+ mResponseMsg.Mid(timeString, 4, 2);
+ exTime.tm_month = timeString.ToInteger(&error) - 1; // january = 0
+ mResponseMsg.Mid(timeString, 6, 2);
+ exTime.tm_mday = timeString.ToInteger(&error);
+ mResponseMsg.Mid(timeString, 8, 2);
+ exTime.tm_hour = timeString.ToInteger(&error);
+ mResponseMsg.Mid(timeString, 10, 2);
+ exTime.tm_min = timeString.ToInteger(&error);
+ mResponseMsg.Mid(timeString, 12, 2);
+ exTime.tm_sec = timeString.ToInteger(&error);
+ exTime.tm_usec = 0;
+
+ exTime.tm_params.tp_gmt_offset = 0;
+ exTime.tm_params.tp_dst_offset = 0;
+
+ PR_NormalizeTime(&exTime, PR_GMTParameters);
+ exTime.tm_params = PR_LocalTimeParameters(&exTime);
+
+ PRTime time = PR_ImplodeTime(&exTime);
+ (void)mChannel->SetLastModifiedTime(time);
+ }
+ }
+
+ nsCString entityID;
+ entityID.Truncate();
+ entityID.AppendInt(int64_t(mFileSize));
+ entityID.Append('/');
+ entityID.Append(mModTime);
+ mChannel->SetEntityID(entityID);
+
+ // We weren't asked to resume
+ if (!mChannel->ResumeRequested()) return FTP_S_RETR;
+
+ // if (our entityID == supplied one (if any))
+ if (mSuppliedEntityID.IsEmpty() || entityID.Equals(mSuppliedEntityID))
+ return FTP_S_REST;
+
+ mInternalError = NS_ERROR_ENTITY_CHANGED;
+ mResponseMsg.Truncate();
+ return FTP_ERROR;
+}
+
+nsresult nsFtpState::SetContentType() {
+ // FTP directory URLs don't always end in a slash. Make sure they do.
+ // This check needs to be here rather than a more obvious place
+ // (e.g. LIST command processing) so that it ensures the terminating
+ // slash is appended for the new request case.
+
+ if (!mPath.IsEmpty() && mPath.Last() != '/') {
+ nsCOMPtr<nsIURL> url = (do_QueryInterface(mChannel->URI()));
+ nsAutoCString filePath;
+ if (NS_SUCCEEDED(url->GetFilePath(filePath))) {
+ filePath.Append('/');
+ nsresult rv = NS_MutateURI(url).SetFilePath(filePath).Finalize(url);
+ if (NS_SUCCEEDED(rv)) {
+ mChannel->UpdateURI(url);
+ }
+ }
+ }
+ return mChannel->SetContentType(
+ nsLiteralCString(APPLICATION_HTTP_INDEX_FORMAT));
+}
+
+nsresult nsFtpState::S_list() {
+ nsresult rv = SetContentType();
+ if (NS_FAILED(rv))
+ // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has
+ // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109)
+ return (nsresult)FTP_ERROR;
+
+ rv = mChannel->PushStreamConverter("text/ftp-dir",
+ APPLICATION_HTTP_INDEX_FORMAT);
+ if (NS_FAILED(rv)) {
+ // clear mResponseMsg which is displayed to the user.
+ // TODO: we should probably set this to something meaningful.
+ mResponseMsg = "";
+ return rv;
+ }
+
+ // dir listings aren't resumable
+ NS_ENSURE_TRUE(!mChannel->ResumeRequested(), NS_ERROR_NOT_RESUMABLE);
+
+ mChannel->SetEntityID(""_ns);
+
+ const char* listString;
+ if (mServerType == FTP_VMS_TYPE) {
+ listString = "LIST *.*;0" CRLF;
+ } else {
+ listString = "LIST" CRLF;
+ }
+
+ return SendFTPCommand(nsDependentCString(listString));
+}
+
+FTP_STATE
+nsFtpState::R_list() {
+ if (mResponseCode / 100 == 1) {
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_FTP_OPENED_CHANNELS_LISTINGS, 1);
+
+ mRlist1xxReceived = true;
+
+ // OK, time to start reading from the data connection.
+ if (mDataStream && HasPendingCallback())
+ mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
+ return FTP_READ_BUF;
+ }
+
+ if (mResponseCode / 100 == 2 && mRlist1xxReceived) {
+ //(DONE)
+ mNextState = FTP_COMPLETE;
+ mRlist1xxReceived = false;
+ return FTP_COMPLETE;
+ }
+ return FTP_ERROR;
+}
+
+nsresult nsFtpState::S_retr() {
+ nsAutoCString retrStr(mPath);
+ if (retrStr.IsEmpty() || retrStr.First() != '/') retrStr.Insert(mPwd, 0);
+ if (mServerType == FTP_VMS_TYPE) ConvertFilespecToVMS(retrStr);
+ retrStr.InsertLiteral("RETR ", 0);
+ retrStr.AppendLiteral(CRLF);
+
+ return SendFTPCommand(retrStr);
+}
+
+FTP_STATE
+nsFtpState::R_retr() {
+ if (mResponseCode / 100 == 2) {
+ if (!mRretr1xxReceived) return FTP_ERROR;
+
+ //(DONE)
+ mNextState = FTP_COMPLETE;
+ mRretr1xxReceived = false;
+ return FTP_COMPLETE;
+ }
+
+ if (mResponseCode / 100 == 1) {
+ mChannel->SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM));
+
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_FTP_OPENED_CHANNELS_FILES, 1);
+
+ mRretr1xxReceived = true;
+
+ if (mDataStream && HasPendingCallback())
+ mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
+ return FTP_READ_BUF;
+ }
+
+ // These error codes are related to problems with the connection.
+ // If we encounter any at this point, do not try CWD and abort.
+ if (mResponseCode == 421 || mResponseCode == 425 || mResponseCode == 426)
+ return FTP_ERROR;
+
+ if (mResponseCode / 100 == 5) {
+ mRETRFailed = true;
+ return FTP_S_PASV;
+ }
+
+ return FTP_S_CWD;
+}
+
+nsresult nsFtpState::S_rest() {
+ nsAutoCString restString("REST ");
+ // The int64_t cast is needed to avoid ambiguity
+ restString.AppendInt(int64_t(mChannel->StartPos()), 10);
+ restString.AppendLiteral(CRLF);
+
+ return SendFTPCommand(restString);
+}
+
+FTP_STATE
+nsFtpState::R_rest() {
+ if (mResponseCode / 100 == 4) {
+ // If REST fails, then we can't resume
+ mChannel->SetEntityID(""_ns);
+
+ mInternalError = NS_ERROR_NOT_RESUMABLE;
+ mResponseMsg.Truncate();
+
+ return FTP_ERROR;
+ }
+
+ return FTP_S_RETR;
+}
+
+nsresult nsFtpState::S_stor() {
+ NS_ENSURE_STATE(mChannel->UploadStream());
+
+ NS_ASSERTION(mAction == PUT, "Wrong state to be here");
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(mChannel->URI());
+ NS_ASSERTION(url, "I thought you were a nsStandardURL");
+
+ nsAutoCString storStr;
+ url->GetFilePath(storStr);
+ NS_ASSERTION(!storStr.IsEmpty(), "What does it mean to store a empty path");
+
+ // kill the first slash since we want to be relative to CWD.
+ if (storStr.First() == '/') storStr.Cut(0, 1);
+
+ if (mServerType == FTP_VMS_TYPE) ConvertFilespecToVMS(storStr);
+
+ NS_UnescapeURL(storStr);
+ storStr.InsertLiteral("STOR ", 0);
+ storStr.AppendLiteral(CRLF);
+
+ return SendFTPCommand(storStr);
+}
+
+FTP_STATE
+nsFtpState::R_stor() {
+ if (mResponseCode / 100 == 2 && mRstor1xxReceived) {
+ //(DONE)
+ mNextState = FTP_COMPLETE;
+ mStorReplyReceived = true;
+ mRstor1xxReceived = false;
+
+ // Call Close() if it was not called in nsFtpState::OnStoprequest()
+ if (!mUploadRequest && !IsClosed()) Close();
+
+ return FTP_COMPLETE;
+ }
+
+ if (mResponseCode / 100 == 1) {
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::NETWORKING_FTP_OPENED_CHANNELS_FILES, 1);
+
+ LOG(("FTP:(%p) writing on DT\n", this));
+ mRstor1xxReceived = true;
+ return FTP_READ_BUF;
+ }
+
+ mStorReplyReceived = true;
+ return FTP_ERROR;
+}
+
+nsresult nsFtpState::S_pasv() {
+ if (!mAddressChecked) {
+ // Find socket address
+ mAddressChecked = true;
+ mServerAddress.raw.family = AF_INET;
+ mServerAddress.inet.ip = htonl(INADDR_ANY);
+ mServerAddress.inet.port = htons(0);
+
+ nsITransport* controlSocket = mControlConnection->Transport();
+ if (!controlSocket)
+ // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has
+ // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109)
+ return (nsresult)FTP_ERROR;
+
+ nsCOMPtr<nsISocketTransport> sTrans = do_QueryInterface(controlSocket);
+ if (sTrans) {
+ nsresult rv = sTrans->GetPeerAddr(&mServerAddress);
+ if (NS_SUCCEEDED(rv)) {
+ if (!mServerAddress.IsIPAddrAny())
+ mServerIsIPv6 = (mServerAddress.raw.family == AF_INET6) &&
+ !mServerAddress.IsIPAddrV4Mapped();
+ else {
+ /*
+ * In case of SOCKS5 remote DNS resolution, we do
+ * not know the remote IP address. Still, if it is
+ * an IPV6 host, then the external address of the
+ * socks server should also be IPv6, and this is the
+ * self address of the transport.
+ */
+ NetAddr selfAddress;
+ rv = sTrans->GetSelfAddr(&selfAddress);
+ if (NS_SUCCEEDED(rv))
+ mServerIsIPv6 = (selfAddress.raw.family == AF_INET6) &&
+ !selfAddress.IsIPAddrV4Mapped();
+ }
+ }
+ }
+ }
+
+ const char* string;
+ if (mServerIsIPv6) {
+ string = "EPSV" CRLF;
+ } else {
+ string = "PASV" CRLF;
+ }
+
+ return SendFTPCommand(nsDependentCString(string));
+}
+
+FTP_STATE
+nsFtpState::R_pasv() {
+ if (mResponseCode / 100 != 2) return FTP_ERROR;
+
+ nsresult rv;
+ int32_t port;
+
+ nsAutoCString responseCopy(mResponseMsg);
+ char* response = responseCopy.BeginWriting();
+
+ char* ptr = response;
+
+ // Make sure to ignore the address in the PASV response (bug 370559)
+
+ if (mServerIsIPv6) {
+ // The returned string is of the form
+ // text (|||ppp|)
+ // Where '|' can be any single character
+ char delim;
+ while (*ptr && *ptr != '(') ptr++;
+ if (*ptr++ != '(') return FTP_ERROR;
+ delim = *ptr++;
+ if (!delim || *ptr++ != delim || *ptr++ != delim || *ptr < '0' ||
+ *ptr > '9')
+ return FTP_ERROR;
+ port = 0;
+ do {
+ port = port * 10 + *ptr++ - '0';
+ } while (*ptr >= '0' && *ptr <= '9');
+ if (*ptr++ != delim || *ptr != ')') return FTP_ERROR;
+ } else {
+ // The returned address string can be of the form
+ // (xxx,xxx,xxx,xxx,ppp,ppp) or
+ // xxx,xxx,xxx,xxx,ppp,ppp (without parens)
+ int32_t h0, h1, h2, h3, p0, p1;
+
+ int32_t fields = 0;
+ // First try with parens
+ while (*ptr && *ptr != '(') ++ptr;
+ if (*ptr) {
+ ++ptr;
+ fields = PR_sscanf(ptr, "%ld,%ld,%ld,%ld,%ld,%ld", &h0, &h1, &h2, &h3,
+ &p0, &p1);
+ }
+ if (!*ptr || fields < 6) {
+ // OK, lets try w/o parens
+ ptr = response;
+ while (*ptr && *ptr != ',') ++ptr;
+ if (*ptr) {
+ // backup to the start of the digits
+ do {
+ ptr--;
+ } while ((ptr >= response) && (*ptr >= '0') && (*ptr <= '9'));
+ ptr++; // get back onto the numbers
+ fields = PR_sscanf(ptr, "%ld,%ld,%ld,%ld,%ld,%ld", &h0, &h1, &h2, &h3,
+ &p0, &p1);
+ }
+ }
+
+ NS_ASSERTION(fields == 6, "Can't parse PASV response");
+ if (fields < 6) return FTP_ERROR;
+
+ port = ((int32_t)(p0 << 8)) + p1;
+ }
+
+ bool newDataConn = true;
+ if (mDataTransport) {
+ // Reuse this connection only if its still alive, and the port
+ // is the same
+ nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(mDataTransport);
+ if (strans) {
+ int32_t oldPort;
+ nsresult rv = strans->GetPort(&oldPort);
+ if (NS_SUCCEEDED(rv)) {
+ if (oldPort == port) {
+ bool isAlive;
+ if (NS_SUCCEEDED(strans->IsAlive(&isAlive)) && isAlive)
+ newDataConn = false;
+ }
+ }
+ }
+
+ if (newDataConn) {
+ mDataTransport->Close(NS_ERROR_ABORT);
+ mDataTransport = nullptr;
+ if (mDataStream) {
+ mDataStream->CloseWithStatus(NS_ERROR_ABORT);
+ mDataStream = nullptr;
+ }
+ }
+ }
+
+ if (newDataConn) {
+ // now we know where to connect our data channel
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ if (!sts) return FTP_ERROR;
+
+ nsCOMPtr<nsISocketTransport> strans;
+
+ nsAutoCString host;
+ if (!mServerAddress.IsIPAddrAny()) {
+ char buf[kIPv6CStrBufSize];
+ mServerAddress.ToStringBuffer(buf, sizeof(buf));
+ host.Assign(buf);
+ } else {
+ /*
+ * In case of SOCKS5 remote DNS resolving, the peer address
+ * fetched previously will be invalid (0.0.0.0): it is unknown
+ * to us. But we can pass on the original hostname to the
+ * connect for the data connection.
+ */
+ rv = mChannel->URI()->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return FTP_ERROR;
+ }
+
+ rv = sts->CreateTransport(nsTArray<nsCString>(), host, port,
+ mChannel->ProxyInfo(),
+ getter_AddRefs(strans)); // the data socket
+ if (NS_FAILED(rv)) return FTP_ERROR;
+ mDataTransport = strans;
+
+ strans->SetQoSBits(gFtpHandler->GetDataQoSBits());
+
+ LOG(("FTP:(%p) created DT (%s:%x)\n", this, host.get(), port));
+
+ // hook ourself up as a proxy for status notifications
+ rv = mDataTransport->SetEventSink(this, GetCurrentEventTarget());
+ NS_ENSURE_SUCCESS(rv, FTP_ERROR);
+
+ if (mAction == PUT) {
+ NS_ASSERTION(!mRETRFailed, "Failed before uploading");
+
+ // nsIUploadChannel requires the upload stream to support ReadSegments.
+ // therefore, we can open an unbuffered socket output stream.
+ nsCOMPtr<nsIOutputStream> output;
+ rv = mDataTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
+ getter_AddRefs(output));
+ if (NS_FAILED(rv)) return FTP_ERROR;
+
+ // perform the data copy on the socket transport thread. we do this
+ // because "output" is a socket output stream, so the result is that
+ // all work will be done on the socket transport thread.
+ nsCOMPtr<nsIEventTarget> stEventTarget =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ if (!stEventTarget) return FTP_ERROR;
+
+ nsCOMPtr<nsIAsyncStreamCopier> copier =
+ do_CreateInstance(NS_ASYNCSTREAMCOPIER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = copier->Init(mChannel->UploadStream(), output, stEventTarget, true,
+ false /* output is NOT buffered */, 0, true, true);
+ }
+ if (NS_FAILED(rv)) return FTP_ERROR;
+
+ rv = copier->AsyncCopy(this, nullptr);
+ if (NS_FAILED(rv)) return FTP_ERROR;
+
+ // hold a reference to the copier so we can cancel it if necessary.
+ mUploadRequest = copier;
+
+ // update the current working directory before sending the STOR
+ // command. this is needed since we might be reusing a control
+ // connection.
+ return FTP_S_CWD;
+ }
+
+ //
+ // else, we are reading from the data connection...
+ //
+
+ // open a buffered, asynchronous socket input stream
+ nsCOMPtr<nsIInputStream> input;
+ rv = mDataTransport->OpenInputStream(0, nsIOService::gDefaultSegmentSize,
+ nsIOService::gDefaultSegmentCount,
+ getter_AddRefs(input));
+ NS_ENSURE_SUCCESS(rv, FTP_ERROR);
+ mDataStream = do_QueryInterface(input);
+ }
+
+ if (mRETRFailed || mPath.IsEmpty() || mPath.Last() == '/') return FTP_S_CWD;
+ return FTP_S_SIZE;
+}
+
+nsresult nsFtpState::S_feat() {
+ return SendFTPCommand(nsLiteralCString("FEAT" CRLF));
+}
+
+FTP_STATE
+nsFtpState::R_feat() {
+ if (mResponseCode / 100 == 2) {
+ if (mResponseMsg.Find(nsLiteralCString(CRLF " UTF8" CRLF), true) > -1) {
+ // This FTP server supports UTF-8 encoding
+ mChannel->SetContentCharset("UTF-8"_ns);
+ mUseUTF8 = true;
+ return FTP_S_OPTS;
+ }
+ }
+
+ mUseUTF8 = false;
+ return FTP_S_PWD;
+}
+
+nsresult nsFtpState::S_opts() {
+ // This command is for compatibility of old FTP spec (IETF Draft)
+ return SendFTPCommand(nsLiteralCString("OPTS UTF8 ON" CRLF));
+}
+
+FTP_STATE
+nsFtpState::R_opts() {
+ // Ignore error code because "OPTS UTF8 ON" is for compatibility of
+ // FTP server using IETF draft
+ return FTP_S_PWD;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+
+nsresult nsFtpState::Init(nsFtpChannel* channel) {
+ // parameter validation
+ NS_ASSERTION(channel, "FTP: needs a channel");
+
+ mChannel = channel; // a straight ref ptr to the channel
+
+ mKeepRunning = true;
+ mSuppliedEntityID = channel->EntityID();
+
+ if (channel->UploadStream()) mAction = PUT;
+
+ nsresult rv;
+ nsCOMPtr<nsIURL> url = do_QueryInterface(mChannel->URI());
+
+ nsAutoCString host;
+ if (url) {
+ rv = url->GetAsciiHost(host);
+ } else {
+ rv = mChannel->URI()->GetAsciiHost(host);
+ }
+ if (NS_FAILED(rv) || host.IsEmpty()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsAutoCString path;
+ if (url) {
+ rv = url->GetFilePath(path);
+ } else {
+ rv = mChannel->URI()->GetPathQueryRef(path);
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ removeParamsFromPath(path);
+
+ nsCOMPtr<nsIURI> outURI;
+ // FTP parameters such as type=i are ignored
+ if (url) {
+ rv = NS_MutateURI(url).SetFilePath(path).Finalize(outURI);
+ } else {
+ rv = NS_MutateURI(mChannel->URI()).SetPathQueryRef(path).Finalize(outURI);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ mChannel->UpdateURI(outURI);
+ }
+
+ // Skip leading slash
+ char* fwdPtr = path.BeginWriting();
+ if (!fwdPtr) return NS_ERROR_OUT_OF_MEMORY;
+ if (*fwdPtr == '/') fwdPtr++;
+ if (*fwdPtr != '\0') {
+ // now unescape it... %xx reduced inline to resulting character
+ int32_t len = NS_UnescapeURL(fwdPtr);
+ mPath.Assign(fwdPtr, len);
+
+#ifdef DEBUG
+ if (mPath.FindCharInSet(CRLF) >= 0)
+ NS_ERROR("NewURI() should've prevented this!!!");
+#endif
+ }
+
+ // pull any username and/or password out of the uri
+ nsAutoCString uname;
+ rv = mChannel->URI()->GetUsername(uname);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!uname.IsEmpty() && !uname.EqualsLiteral("anonymous")) {
+ mAnonymous = false;
+ CopyUTF8toUTF16(NS_UnescapeURL(uname), mUsername);
+
+ // return an error if we find a CR or LF in the username
+ if (uname.FindCharInSet(CRLF) >= 0) return NS_ERROR_MALFORMED_URI;
+ }
+
+ nsAutoCString password;
+ rv = mChannel->URI()->GetPassword(password);
+ if (NS_FAILED(rv)) return rv;
+
+ CopyUTF8toUTF16(NS_UnescapeURL(password), mPassword);
+
+ // return an error if we find a CR or LF in the password
+ if (mPassword.FindCharInSet(CRLF) >= 0) return NS_ERROR_MALFORMED_URI;
+
+ int32_t port;
+ rv = mChannel->URI()->GetPort(&port);
+ if (NS_FAILED(rv)) return rv;
+
+ if (port > 0) mPort = port;
+
+ // Lookup Proxy information asynchronously if it isn't already set
+ // on the channel and if we aren't configured explicitly to go directly
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
+
+ if (pps && !mChannel->ProxyInfo()) {
+ pps->AsyncResolve(static_cast<nsIChannel*>(mChannel), 0, this, nullptr,
+ getter_AddRefs(mProxyRequest));
+ }
+
+ return NS_OK;
+}
+
+void nsFtpState::Connect() {
+ mState = FTP_COMMAND_CONNECT;
+ mNextState = FTP_S_USER;
+
+ nsresult rv = Process();
+
+ // check for errors.
+ if (NS_FAILED(rv)) {
+ LOG(("FTP:Process() failed: %" PRIx32 "\n", static_cast<uint32_t>(rv)));
+ mInternalError = NS_ERROR_FAILURE;
+ mState = FTP_ERROR;
+ CloseWithStatus(mInternalError);
+ }
+}
+
+void nsFtpState::KillControlConnection() {
+ mControlReadCarryOverBuf.Truncate(0);
+
+ mAddressChecked = false;
+ mServerIsIPv6 = false;
+
+ // if everything went okay, save the connection.
+ // FIX: need a better way to determine if we can cache the connections.
+ // there are some errors which do not mean that we need to kill the
+ // connection e.g. fnf.
+
+ if (!mControlConnection) return;
+
+ // kill the reference to ourselves in the control connection.
+ mControlConnection->WaitData(nullptr);
+
+ if (NS_SUCCEEDED(mInternalError) && NS_SUCCEEDED(mControlStatus) &&
+ mControlConnection->IsAlive() && mCacheConnection) {
+ LOG_INFO(("FTP:(%p) caching CC(%p)", this, mControlConnection.get()));
+
+ // Store connection persistent data
+ mControlConnection->mServerType = mServerType;
+ mControlConnection->mPassword = mPassword;
+ mControlConnection->mPwd = mPwd;
+ mControlConnection->mUseUTF8 = mUseUTF8;
+
+ nsresult rv = NS_OK;
+ // Don't cache controlconnection if anonymous (bug #473371)
+ if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
+ rv = gFtpHandler->InsertConnection(mChannel->URI(), mControlConnection);
+ // Can't cache it? Kill it then.
+ mControlConnection->Disconnect(rv);
+ } else {
+ mControlConnection->Disconnect(NS_BINDING_ABORTED);
+ }
+
+ mControlConnection = nullptr;
+}
+
+nsresult nsFtpState::StopProcessing() {
+ // Only do this function once.
+ if (!mKeepRunning) return NS_OK;
+ mKeepRunning = false;
+
+ LOG_INFO(("FTP:(%p) nsFtpState stopping", this));
+
+ if (NS_FAILED(mInternalError) && !mResponseMsg.IsEmpty()) {
+ // check to see if the control status is bad, forward the error message.
+ nsCOMPtr<nsIFTPChannelParentInternal> ftpChanP;
+ mChannel->GetCallback(ftpChanP);
+ if (ftpChanP) {
+ ftpChanP->SetErrorMsg(mResponseMsg.get(), mUseUTF8);
+ }
+ }
+
+ nsresult broadcastErrorCode = mControlStatus;
+ if (NS_SUCCEEDED(broadcastErrorCode)) broadcastErrorCode = mInternalError;
+
+ mInternalError = broadcastErrorCode;
+
+ KillControlConnection();
+
+ // XXX This can fire before we are done loading data. Is that a problem?
+ OnTransportStatus(nullptr, NS_NET_STATUS_END_FTP_TRANSACTION, 0, 0);
+
+ if (NS_FAILED(broadcastErrorCode)) CloseWithStatus(broadcastErrorCode);
+
+ return NS_OK;
+}
+
+nsresult nsFtpState::SendFTPCommand(const nsACString& command) {
+ NS_ASSERTION(mControlConnection, "null control connection");
+
+ // we don't want to log the password:
+ nsAutoCString logcmd(command);
+ if (StringBeginsWith(command, "PASS "_ns)) logcmd = "PASS xxxxx";
+
+ LOG(("FTP:(%p) writing \"%s\"\n", this, logcmd.get()));
+
+ nsCOMPtr<nsIFTPEventSink> ftpSink;
+ mChannel->GetFTPEventSink(ftpSink);
+ if (ftpSink) ftpSink->OnFTPControlLog(false, logcmd.get());
+
+ if (mControlConnection) return mControlConnection->Write(command);
+
+ return NS_ERROR_FAILURE;
+}
+
+// Convert a unix-style filespec to VMS format
+// /foo/fred/barney/file.txt -> foo:[fred.barney]file.txt
+// /foo/file.txt -> foo:[000000]file.txt
+void nsFtpState::ConvertFilespecToVMS(nsCString& fileString) {
+ int ntok = 1;
+ char *t, *nextToken;
+ nsAutoCString fileStringCopy;
+
+ // Get a writeable copy we can strtok with.
+ fileStringCopy = fileString;
+ t = nsCRT::strtok(fileStringCopy.BeginWriting(), "/", &nextToken);
+ if (t)
+ while (nsCRT::strtok(nextToken, "/", &nextToken))
+ ntok++; // count number of terms (tokens)
+ LOG(("FTP:(%p) ConvertFilespecToVMS ntok: %d\n", this, ntok));
+ LOG(("FTP:(%p) ConvertFilespecToVMS from: \"%s\"\n", this, fileString.get()));
+
+ if (fileString.First() == '/') {
+ // absolute filespec
+ // / -> []
+ // /a -> a (doesn't really make much sense)
+ // /a/b -> a:[000000]b
+ // /a/b/c -> a:[b]c
+ // /a/b/c/d -> a:[b.c]d
+ if (ntok == 1) {
+ if (fileString.Length() == 1) {
+ // Just a slash
+ fileString.Truncate();
+ fileString.AppendLiteral("[]");
+ } else {
+ // just copy the name part (drop the leading slash)
+ fileStringCopy = fileString;
+ fileString = Substring(fileStringCopy, 1, fileStringCopy.Length() - 1);
+ }
+ } else {
+ // Get another copy since the last one was written to.
+ fileStringCopy = fileString;
+ fileString.Truncate();
+ fileString.Append(
+ nsCRT::strtok(fileStringCopy.BeginWriting(), "/", &nextToken));
+ fileString.AppendLiteral(":[");
+ if (ntok > 2) {
+ for (int i = 2; i < ntok; i++) {
+ if (i > 2) fileString.Append('.');
+ fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken));
+ }
+ } else {
+ fileString.AppendLiteral("000000");
+ }
+ fileString.Append(']');
+ fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken));
+ }
+ } else {
+ // relative filespec
+ // a -> a
+ // a/b -> [.a]b
+ // a/b/c -> [.a.b]c
+ if (ntok == 1) {
+ // no slashes, just use the name as is
+ } else {
+ // Get another copy since the last one was written to.
+ fileStringCopy = fileString;
+ fileString.Truncate();
+ fileString.AppendLiteral("[.");
+ fileString.Append(
+ nsCRT::strtok(fileStringCopy.BeginWriting(), "/", &nextToken));
+ if (ntok > 2) {
+ for (int i = 2; i < ntok; i++) {
+ fileString.Append('.');
+ fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken));
+ }
+ }
+ fileString.Append(']');
+ fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken));
+ }
+ }
+ LOG(("FTP:(%p) ConvertFilespecToVMS to: \"%s\"\n", this, fileString.get()));
+}
+
+// Convert a unix-style dirspec to VMS format
+// /foo/fred/barney/rubble -> foo:[fred.barney.rubble]
+// /foo/fred -> foo:[fred]
+// /foo -> foo:[000000]
+// (null) -> (null)
+void nsFtpState::ConvertDirspecToVMS(nsCString& dirSpec) {
+ LOG(("FTP:(%p) ConvertDirspecToVMS from: \"%s\"\n", this, dirSpec.get()));
+ if (!dirSpec.IsEmpty()) {
+ if (dirSpec.Last() != '/') dirSpec.Append('/');
+ // we can use the filespec routine if we make it look like a file name
+ dirSpec.Append('x');
+ ConvertFilespecToVMS(dirSpec);
+ dirSpec.Truncate(dirSpec.Length() - 1);
+ }
+ LOG(("FTP:(%p) ConvertDirspecToVMS to: \"%s\"\n", this, dirSpec.get()));
+}
+
+// Convert an absolute VMS style dirspec to UNIX format
+void nsFtpState::ConvertDirspecFromVMS(nsCString& dirSpec) {
+ LOG(("FTP:(%p) ConvertDirspecFromVMS from: \"%s\"\n", this, dirSpec.get()));
+ if (dirSpec.IsEmpty()) {
+ dirSpec.Insert('.', 0);
+ } else {
+ dirSpec.Insert('/', 0);
+ dirSpec.ReplaceSubstring(":[", "/");
+ dirSpec.ReplaceChar('.', '/');
+ dirSpec.ReplaceChar(']', '/');
+ }
+ LOG(("FTP:(%p) ConvertDirspecFromVMS to: \"%s\"\n", this, dirSpec.get()));
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsFtpState::OnTransportStatus(nsITransport* transport, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ // Mix signals from both the control and data connections.
+
+ // Ignore data transfer events on the control connection.
+ if (mControlConnection && transport == mControlConnection->Transport()) {
+ switch (status) {
+ 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:
+ break;
+ default:
+ return NS_OK;
+ }
+ }
+
+ // Ignore the progressMax value from the socket. We know the true size of
+ // the file based on the response from our SIZE request. Additionally, only
+ // report the max progress based on where we started/resumed.
+ mChannel->OnTransportStatus(nullptr, status, progress,
+ mFileSize - mChannel->StartPos());
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsFtpState::OnStartRequest(nsIRequest* request) {
+ mStorReplyReceived = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpState::OnStopRequest(nsIRequest* request, nsresult status) {
+ mUploadRequest = nullptr;
+
+ // Close() will be called when reply to STOR command is received
+ // see bug #389394
+ if (!mStorReplyReceived) return NS_OK;
+
+ // We're done uploading. Let our consumer know that we're done.
+ Close();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsFtpState::Available(uint64_t* result) {
+ if (mDataStream) return mDataStream->Available(result);
+
+ return nsBaseContentStream::Available(result);
+}
+
+NS_IMETHODIMP
+nsFtpState::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t count, uint32_t* result) {
+ // Insert a thunk here so that the input stream passed to the writer is this
+ // input stream instead of mDataStream.
+
+ if (mDataStream) {
+ nsWriteSegmentThunk thunk = {this, writer, closure};
+ nsresult rv;
+ rv = mDataStream->ReadSegments(NS_WriteSegmentThunk, &thunk, count, result);
+ return rv;
+ }
+
+ return nsBaseContentStream::ReadSegments(writer, closure, count, result);
+}
+
+NS_IMETHODIMP
+nsFtpState::CloseWithStatus(nsresult status) {
+ LOG(("FTP:(%p) close [%" PRIx32 "]\n", this, static_cast<uint32_t>(status)));
+
+ // Shutdown the control connection processing if we are being closed with an
+ // error. Note: This method may be called several times.
+ if (!IsClosed() && NS_FAILED(status)) {
+ if (NS_SUCCEEDED(mInternalError)) mInternalError = status;
+ StopProcessing();
+ }
+
+ if (mUploadRequest) {
+ mUploadRequest->Cancel(NS_ERROR_ABORT);
+ mUploadRequest = nullptr;
+ }
+
+ if (mDataTransport) {
+ // Shutdown the data transport.
+ mDataTransport->Close(NS_ERROR_ABORT);
+ mDataTransport = nullptr;
+ }
+
+ if (mDataStream) {
+ mDataStream->CloseWithStatus(NS_ERROR_ABORT);
+ mDataStream = nullptr;
+ }
+
+ return nsBaseContentStream::CloseWithStatus(status);
+}
+
+static nsresult CreateHTTPProxiedChannel(nsIChannel* channel, nsIProxyInfo* pi,
+ nsIChannel** newChannel) {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ioService->GetProtocolHandler("http", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+
+ return pph->NewProxiedChannel(uri, pi, 0, nullptr, loadInfo, newChannel);
+}
+
+NS_IMETHODIMP
+nsFtpState::OnProxyAvailable(nsICancelable* request, nsIChannel* channel,
+ nsIProxyInfo* pi, nsresult status) {
+ mProxyRequest = nullptr;
+
+ // failed status code just implies DIRECT processing
+
+ if (NS_SUCCEEDED(status)) {
+ nsAutoCString type;
+ if (pi && NS_SUCCEEDED(pi->GetType(type)) && type.EqualsLiteral("http")) {
+ // Proxy the FTP url via HTTP
+ // This would have been easier to just return a HTTP channel directly
+ // from nsIIOService::NewChannelFromURI(), but the proxy type cannot
+ // be reliabliy determined synchronously without jank due to pac, etc..
+ LOG(("FTP:(%p) Configured to use a HTTP proxy channel\n", this));
+
+ nsCOMPtr<nsIChannel> newChannel;
+ if (NS_SUCCEEDED(CreateHTTPProxiedChannel(channel, pi,
+ getter_AddRefs(newChannel))) &&
+ NS_SUCCEEDED(mChannel->Redirect(
+ newChannel, nsIChannelEventSink::REDIRECT_INTERNAL, true))) {
+ LOG(("FTP:(%p) Redirected to use a HTTP proxy channel\n", this));
+ return NS_OK;
+ }
+ } else if (pi) {
+ // Proxy using the FTP protocol routed through a socks proxy
+ LOG(("FTP:(%p) Configured to use a SOCKS proxy channel\n", this));
+ mChannel->SetProxyInfo(pi);
+ }
+ }
+
+ if (mDeferredCallbackPending) {
+ mDeferredCallbackPending = false;
+ OnCallbackPending();
+ }
+ return NS_OK;
+}
+
+void nsFtpState::OnCallbackPending() {
+ if (mState == FTP_INIT) {
+ if (mProxyRequest) {
+ mDeferredCallbackPending = true;
+ return;
+ }
+ Connect();
+ } else if (mDataStream) {
+ mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
+ }
+}
diff --git a/netwerk/protocol/ftp/nsFtpConnectionThread.h b/netwerk/protocol/ftp/nsFtpConnectionThread.h
new file mode 100644
index 0000000000..ec1cf999c7
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpConnectionThread.h
@@ -0,0 +1,254 @@
+/* -*- 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 __nsFtpConnectionThread__h_
+#define __nsFtpConnectionThread__h_
+
+#include "nsBaseContentStream.h"
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIAsyncInputStream.h"
+#include "nsITransport.h"
+#include "mozilla/net/DNS.h"
+#include "nsFtpControlConnection.h"
+#include "nsIProtocolProxyCallback.h"
+
+// ftp server types
+#define FTP_GENERIC_TYPE 0
+#define FTP_UNIX_TYPE 1
+#define FTP_VMS_TYPE 8
+#define FTP_NT_TYPE 9
+#define FTP_OS2_TYPE 11
+
+// ftp states
+typedef enum _FTP_STATE {
+ ///////////////////////
+ //// Internal states
+ FTP_INIT,
+ FTP_COMMAND_CONNECT,
+ FTP_READ_BUF,
+ FTP_ERROR,
+ FTP_COMPLETE,
+
+ ///////////////////////
+ //// Command channel connection setup states
+ FTP_S_USER,
+ FTP_R_USER,
+ FTP_S_PASS,
+ FTP_R_PASS,
+ FTP_S_SYST,
+ FTP_R_SYST,
+ FTP_S_ACCT,
+ FTP_R_ACCT,
+ FTP_S_TYPE,
+ FTP_R_TYPE,
+ FTP_S_CWD,
+ FTP_R_CWD,
+ FTP_S_SIZE,
+ FTP_R_SIZE,
+ FTP_S_MDTM,
+ FTP_R_MDTM,
+ FTP_S_REST,
+ FTP_R_REST,
+ FTP_S_RETR,
+ FTP_R_RETR,
+ FTP_S_STOR,
+ FTP_R_STOR,
+ FTP_S_LIST,
+ FTP_R_LIST,
+ FTP_S_PASV,
+ FTP_R_PASV,
+ FTP_S_PWD,
+ FTP_R_PWD,
+ FTP_S_FEAT,
+ FTP_R_FEAT,
+ FTP_S_OPTS,
+ FTP_R_OPTS
+} FTP_STATE;
+
+// higher level ftp actions
+typedef enum _FTP_ACTION { GET, PUT } FTP_ACTION;
+
+class nsFtpChannel;
+class nsICancelable;
+class nsIProxyInfo;
+class nsIStreamListener;
+
+// The nsFtpState object is the content stream for the channel. It implements
+// nsIInputStreamCallback, so it can read data from the control connection. It
+// implements nsITransportEventSink so it can mix status events from both the
+// control connection and the data connection.
+
+class nsFtpState final : public nsBaseContentStream,
+ public nsIInputStreamCallback,
+ public nsITransportEventSink,
+ public nsIRequestObserver,
+ public nsFtpControlConnectionListener,
+ public nsIProtocolProxyCallback {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+
+ // Override input stream methods:
+ NS_IMETHOD CloseWithStatus(nsresult status) override;
+ NS_IMETHOD Available(uint64_t* result) override;
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun fun, void* closure, uint32_t count,
+ uint32_t* result) override;
+
+ // nsFtpControlConnectionListener methods:
+ virtual void OnControlDataAvailable(const char* data,
+ uint32_t dataLen) override;
+ virtual void OnControlError(nsresult status) override;
+
+ nsFtpState();
+ nsresult Init(nsFtpChannel* channel);
+
+ protected:
+ // Notification from nsBaseContentStream::AsyncWait
+ virtual void OnCallbackPending() override;
+
+ private:
+ virtual ~nsFtpState();
+
+ ///////////////////////////////////
+ // BEGIN: STATE METHODS
+ nsresult S_user();
+ FTP_STATE R_user();
+ nsresult S_pass();
+ FTP_STATE R_pass();
+ nsresult S_syst();
+ FTP_STATE R_syst();
+ nsresult S_acct();
+ FTP_STATE R_acct();
+
+ nsresult S_type();
+ FTP_STATE R_type();
+ nsresult S_cwd();
+ FTP_STATE R_cwd();
+
+ nsresult S_size();
+ FTP_STATE R_size();
+ nsresult S_mdtm();
+ FTP_STATE R_mdtm();
+ nsresult S_list();
+ FTP_STATE R_list();
+
+ nsresult S_rest();
+ FTP_STATE R_rest();
+ nsresult S_retr();
+ FTP_STATE R_retr();
+ nsresult S_stor();
+ FTP_STATE R_stor();
+ nsresult S_pasv();
+ FTP_STATE R_pasv();
+ nsresult S_pwd();
+ FTP_STATE R_pwd();
+ nsresult S_feat();
+ FTP_STATE R_feat();
+ nsresult S_opts();
+ FTP_STATE R_opts();
+ // END: STATE METHODS
+ ///////////////////////////////////
+
+ // internal methods
+ void MoveToNextState(FTP_STATE nextState);
+ nsresult Process();
+
+ void KillControlConnection();
+ nsresult StopProcessing();
+ nsresult EstablishControlConnection();
+ nsresult SendFTPCommand(const nsACString& command);
+ void ConvertFilespecToVMS(nsCString& fileSpec);
+ void ConvertDirspecToVMS(nsCString& fileSpec);
+ void ConvertDirspecFromVMS(nsCString& fileSpec);
+ nsresult BuildStreamConverter(nsIStreamListener** convertStreamListener);
+ nsresult SetContentType();
+
+ /**
+ * This method is called to kick-off the FTP state machine. mState is
+ * reset to FTP_COMMAND_CONNECT, and the FTP state machine progresses from
+ * there. This method is initially called (indirectly) from the channel's
+ * AsyncOpen implementation.
+ */
+ void Connect();
+
+ ///////////////////////////////////
+ // Private members
+
+ nsCOMPtr<nsIProxiedProtocolHandler> mHandler; // Ref to gFtpHandler
+
+ // ****** state machine vars
+ FTP_STATE mState; // the current state
+ FTP_STATE mNextState; // the next state
+ bool mKeepRunning; // thread event loop boolean
+ int32_t mResponseCode; // the last command response code
+ nsCString mResponseMsg; // the last command response text
+
+ // ****** channel/transport/stream vars
+ RefPtr<nsFtpControlConnection>
+ mControlConnection; // cacheable control connection (owns mCPipe)
+ bool mReceivedControlData;
+ bool mTryingCachedControl; // retrying the password
+ bool mRETRFailed; // Did we already try a RETR and it failed?
+ uint64_t mFileSize;
+ nsCString mModTime;
+
+ // ****** consumer vars
+ RefPtr<nsFtpChannel>
+ mChannel; // our owning FTP channel we pass through our events
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+
+ // ****** connection cache vars
+ int32_t mServerType; // What kind of server are we talking to
+
+ // ****** protocol interpretation related state vars
+ nsString mUsername; // username
+ nsString mPassword; // password
+ FTP_ACTION mAction; // the higher level action (GET/PUT)
+ bool mAnonymous; // try connecting anonymous (default)
+ bool mRetryPass; // retrying the password
+ bool mStorReplyReceived; // FALSE if waiting for STOR
+ // completion status from server
+ bool mRlist1xxReceived; // TRUE if we have received a LIST 1xx
+ // response from the server
+ bool mRretr1xxReceived; // TRUE if we have received a RETR 1xx
+ // response from the server
+ bool mRstor1xxReceived; // TRUE if we have received a STOR 1xx
+ // response from the server
+ nsresult mInternalError; // represents internal state errors
+ bool mReconnectAndLoginAgain;
+ bool mCacheConnection;
+
+ // ****** URI vars
+ int32_t mPort; // the port to connect to
+ nsString mFilename; // url filename (if any)
+ nsCString mPath; // the url's path
+ nsCString mPwd; // login Path
+
+ // ****** other vars
+ nsCOMPtr<nsITransport> mDataTransport;
+ nsCOMPtr<nsIAsyncInputStream> mDataStream;
+ nsCOMPtr<nsIRequest> mUploadRequest;
+ bool mAddressChecked;
+ bool mServerIsIPv6;
+ bool mUseUTF8;
+
+ mozilla::net::NetAddr mServerAddress;
+
+ // ***** control read gvars
+ nsresult mControlStatus;
+ nsCString mControlReadCarryOverBuf;
+
+ nsCString mSuppliedEntityID;
+
+ nsCOMPtr<nsICancelable> mProxyRequest;
+ bool mDeferredCallbackPending;
+};
+
+#endif //__nsFtpConnectionThread__h_
diff --git a/netwerk/protocol/ftp/nsFtpControlConnection.cpp b/netwerk/protocol/ftp/nsFtpControlConnection.cpp
new file mode 100644
index 0000000000..1d54ed9de2
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpControlConnection.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 "nsIOService.h"
+#include "nsFtpControlConnection.h"
+#include "nsFtpProtocolHandler.h"
+#include "mozilla/Logging.h"
+#include "nsIInputStream.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsThreadUtils.h"
+#include "nsNetCID.h"
+#include "nsTArray.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+extern LazyLogModule gFTPLog;
+#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
+#define LOG_INFO(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Info, args)
+
+//
+// nsFtpControlConnection implementation ...
+//
+
+NS_IMPL_ISUPPORTS(nsFtpControlConnection, nsIInputStreamCallback)
+
+NS_IMETHODIMP
+nsFtpControlConnection::OnInputStreamReady(nsIAsyncInputStream* stream) {
+ char data[4096];
+
+ // Consume data whether we have a listener or not.
+ uint64_t avail64;
+ uint32_t avail = 0;
+ nsresult rv = stream->Available(&avail64);
+ if (NS_SUCCEEDED(rv)) {
+ avail = (uint32_t)std::min(avail64, (uint64_t)sizeof(data));
+
+ uint32_t n;
+ rv = stream->Read(data, avail, &n);
+ if (NS_SUCCEEDED(rv)) avail = n;
+ }
+
+ // It's important that we null out mListener before calling one of its
+ // methods as it may call WaitData, which would queue up another read.
+
+ RefPtr<nsFtpControlConnectionListener> listener;
+ listener.swap(mListener);
+
+ if (!listener) return NS_OK;
+
+ if (NS_FAILED(rv)) {
+ listener->OnControlError(rv);
+ } else {
+ listener->OnControlDataAvailable(data, avail);
+ }
+
+ return NS_OK;
+}
+
+nsFtpControlConnection::nsFtpControlConnection(const nsACString& host,
+ uint32_t port)
+ : mServerType(0),
+ mSuspendedWrite(0),
+ mSessionId(gFtpHandler->GetSessionId()),
+ mUseUTF8(false),
+ mHost(host),
+ mPort(port) {
+ LOG_INFO(("FTP:CC created @%p", this));
+}
+
+nsFtpControlConnection::~nsFtpControlConnection() {
+ LOG_INFO(("FTP:CC destroyed @%p", this));
+}
+
+bool nsFtpControlConnection::IsAlive() {
+ if (!mSocket) return false;
+
+ bool isAlive = false;
+ mSocket->IsAlive(&isAlive);
+ return isAlive;
+}
+nsresult nsFtpControlConnection::Connect(nsIProxyInfo* proxyInfo,
+ nsITransportEventSink* eventSink) {
+ if (mSocket) return NS_OK;
+
+ // build our own
+ nsresult rv;
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = sts->CreateTransport(nsTArray<nsCString>(), mHost, mPort, proxyInfo,
+ getter_AddRefs(mSocket)); // the command transport
+ if (NS_FAILED(rv)) return rv;
+
+ mSocket->SetQoSBits(gFtpHandler->GetControlQoSBits());
+
+ // proxy transport events back to current thread
+ if (eventSink) mSocket->SetEventSink(eventSink, GetCurrentEventTarget());
+
+ // open buffered, blocking output stream to socket. so long as commands
+ // do not exceed 1024 bytes in length, the writing thread (the main thread)
+ // will not block. this should be OK.
+ rv = mSocket->OpenOutputStream(nsITransport::OPEN_BLOCKING, 1024, 1,
+ getter_AddRefs(mSocketOutput));
+ if (NS_FAILED(rv)) return rv;
+
+ // open buffered, non-blocking/asynchronous input stream to socket.
+ nsCOMPtr<nsIInputStream> inStream;
+ rv = mSocket->OpenInputStream(0, nsIOService::gDefaultSegmentSize,
+ nsIOService::gDefaultSegmentCount,
+ getter_AddRefs(inStream));
+ if (NS_SUCCEEDED(rv)) mSocketInput = do_QueryInterface(inStream);
+
+ return rv;
+}
+
+nsresult nsFtpControlConnection::WaitData(
+ nsFtpControlConnectionListener* listener) {
+ LOG(("FTP:(%p) wait data [listener=%p]\n", this, listener));
+
+ // If listener is null, then simply disconnect the listener. Otherwise,
+ // ensure that we are listening.
+ if (!listener) {
+ mListener = nullptr;
+ return NS_OK;
+ }
+
+ NS_ENSURE_STATE(mSocketInput);
+
+ mListener = listener;
+ return mSocketInput->AsyncWait(this, 0, 0, GetCurrentEventTarget());
+}
+
+nsresult nsFtpControlConnection::Disconnect(nsresult status) {
+ if (!mSocket) return NS_OK; // already disconnected
+
+ LOG_INFO(("FTP:(%p) CC disconnecting (%" PRIx32 ")", this,
+ static_cast<uint32_t>(status)));
+
+ if (NS_FAILED(status)) {
+ // break cyclic reference!
+ mSocket->Close(status);
+ mSocket = nullptr;
+ mSocketInput->AsyncWait(nullptr, 0, 0, nullptr); // clear any observer
+ mSocketInput = nullptr;
+ mSocketOutput = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsFtpControlConnection::Write(const nsACString& command) {
+ NS_ENSURE_STATE(mSocketOutput);
+
+ uint32_t len = command.Length();
+ uint32_t cnt;
+ nsresult rv = mSocketOutput->Write(command.Data(), len, &cnt);
+
+ if (NS_FAILED(rv)) return rv;
+
+ if (len != cnt) return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
diff --git a/netwerk/protocol/ftp/nsFtpControlConnection.h b/netwerk/protocol/ftp/nsFtpControlConnection.h
new file mode 100644
index 0000000000..c29e23eeed
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpControlConnection.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set et ts=4 sts=2 sw=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 nsFtpControlConnection_h___
+#define nsFtpControlConnection_h___
+
+#include "nsCOMPtr.h"
+
+#include "nsISocketTransport.h"
+#include "nsIAsyncInputStream.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+class nsIOutputStream;
+class nsIProxyInfo;
+class nsITransportEventSink;
+
+class nsFtpControlConnectionListener : public nsISupports {
+ public:
+ /**
+ * Called when a chunk of data arrives on the control connection.
+ * @param data
+ * The new data or null if an error occurred.
+ * @param dataLen
+ * The data length in bytes.
+ */
+ virtual void OnControlDataAvailable(const char* data, uint32_t dataLen) = 0;
+
+ /**
+ * Called when an error occurs on the control connection.
+ * @param status
+ * A failure code providing more info about the error.
+ */
+ virtual void OnControlError(nsresult status) = 0;
+};
+
+class nsFtpControlConnection final : public nsIInputStreamCallback {
+ ~nsFtpControlConnection();
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+
+ nsFtpControlConnection(const nsACString& host, uint32_t port);
+
+ nsresult Connect(nsIProxyInfo* proxyInfo, nsITransportEventSink* eventSink);
+ nsresult Disconnect(nsresult status);
+ nsresult Write(const nsACString& command);
+
+ bool IsAlive();
+
+ nsITransport* Transport() { return mSocket; }
+
+ /**
+ * Call this function to be notified asynchronously when there is data
+ * available for the socket. The listener passed to this method replaces
+ * any existing listener, and the listener can be null to disconnect the
+ * previous listener.
+ */
+ nsresult WaitData(nsFtpControlConnectionListener* listener);
+
+ uint32_t mServerType; // what kind of server is it.
+ nsString mPassword;
+ int32_t mSuspendedWrite;
+ nsCString mPwd;
+ uint32_t mSessionId;
+ bool mUseUTF8;
+
+ private:
+ nsCString mHost;
+ uint32_t mPort;
+
+ nsCOMPtr<nsISocketTransport> mSocket;
+ nsCOMPtr<nsIOutputStream> mSocketOutput;
+ nsCOMPtr<nsIAsyncInputStream> mSocketInput;
+
+ RefPtr<nsFtpControlConnectionListener> mListener;
+};
+
+#endif
diff --git a/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp b/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp
new file mode 100644
index 0000000000..99c8a19693
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp
@@ -0,0 +1,331 @@
+/* -*- 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/net/NeckoChild.h"
+#include "mozilla/net/FTPChannelChild.h"
+using namespace mozilla;
+using namespace mozilla::net;
+
+#include "nsFtpProtocolHandler.h"
+#include "nsFTPChannel.h"
+#include "mozilla/Logging.h"
+#include "nsIPrefBranch.h"
+#include "nsIObserverService.h"
+#include "nsEscape.h"
+#include "nsAlgorithm.h"
+
+//-----------------------------------------------------------------------------
+
+//
+// Log module for FTP Protocol logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=nsFtp:5
+// set MOZ_LOG_FILE=ftp.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file ftp.log.
+//
+LazyLogModule gFTPLog("nsFtp");
+#undef LOG
+#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args)
+
+//-----------------------------------------------------------------------------
+
+#define IDLE_TIMEOUT_PREF "network.ftp.idleConnectionTimeout"
+#define IDLE_CONNECTION_LIMIT 8 /* TODO pref me */
+
+#define ENABLED_PREF "network.ftp.enabled"
+#define QOS_DATA_PREF "network.ftp.data.qos"
+#define QOS_CONTROL_PREF "network.ftp.control.qos"
+
+nsFtpProtocolHandler* gFtpHandler = nullptr;
+
+//-----------------------------------------------------------------------------
+
+nsFtpProtocolHandler::nsFtpProtocolHandler()
+ : mIdleTimeout(-1),
+ mEnabled(true),
+ mSessionId(0),
+ mControlQoSBits(0x00),
+ mDataQoSBits(0x00) {
+ LOG(("FTP:creating handler @%p\n", this));
+
+ gFtpHandler = this;
+}
+
+nsFtpProtocolHandler::~nsFtpProtocolHandler() {
+ LOG(("FTP:destroying handler @%p\n", this));
+
+ NS_ASSERTION(mRootConnectionList.Length() == 0, "why wasn't Observe called?");
+
+ gFtpHandler = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsFtpProtocolHandler, nsIProtocolHandler,
+ nsIProxiedProtocolHandler, nsIObserver,
+ nsISupportsWeakReference)
+
+nsresult nsFtpProtocolHandler::Init() {
+ if (IsNeckoChild()) NeckoChild::InitNeckoChild();
+
+ if (mIdleTimeout == -1) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> branch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = branch->GetIntPref(IDLE_TIMEOUT_PREF, &mIdleTimeout);
+ if (NS_FAILED(rv)) mIdleTimeout = 5 * 60; // 5 minute default
+
+ rv = branch->AddObserver(IDLE_TIMEOUT_PREF, this, true);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = branch->GetBoolPref(ENABLED_PREF, &mEnabled);
+ if (NS_FAILED(rv)) mEnabled = true;
+
+ rv = branch->AddObserver(ENABLED_PREF, this, true);
+ if (NS_FAILED(rv)) return rv;
+
+ int32_t val;
+ rv = branch->GetIntPref(QOS_DATA_PREF, &val);
+ if (NS_SUCCEEDED(rv)) mDataQoSBits = (uint8_t)clamped(val, 0, 0xff);
+
+ rv = branch->AddObserver(QOS_DATA_PREF, this, true);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = branch->GetIntPref(QOS_CONTROL_PREF, &val);
+ if (NS_SUCCEEDED(rv)) mControlQoSBits = (uint8_t)clamped(val, 0, 0xff);
+
+ rv = branch->AddObserver(QOS_CONTROL_PREF, this, true);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, "network:offline-about-to-go-offline",
+ true);
+
+ observerService->AddObserver(this, "net:clear-active-logins", true);
+ }
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIProtocolHandler methods:
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::GetScheme(nsACString& result) {
+ result.AssignLiteral("ftp");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::GetDefaultPort(int32_t* result) {
+ *result = 21;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::GetProtocolFlags(uint32_t* result) {
+ *result = URI_STD | ALLOWS_PROXY | ALLOWS_PROXY_HTTP | URI_LOADABLE_BY_ANYONE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::NewChannel(nsIURI* url, nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ return NewProxiedChannel(url, nullptr, 0, nullptr, aLoadInfo, result);
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::NewProxiedChannel(nsIURI* uri, nsIProxyInfo* proxyInfo,
+ uint32_t proxyResolveFlags,
+ nsIURI* proxyURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** result) {
+ if (!mEnabled) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+
+ NS_ENSURE_ARG_POINTER(uri);
+ RefPtr<nsBaseChannel> channel;
+ if (IsNeckoChild())
+ channel = new FTPChannelChild(uri);
+ else
+ channel = new nsFtpChannel(uri, proxyInfo);
+
+ // set the loadInfo on the new channel
+ nsresult rv = channel->SetLoadInfo(aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ channel.forget(result);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::AllowPort(int32_t port, const char* scheme,
+ bool* _retval) {
+ *_retval = (port == 21 || port == 22);
+ return NS_OK;
+}
+
+// connection cache methods
+
+void nsFtpProtocolHandler::Timeout(nsITimer* aTimer, void* aClosure) {
+ LOG(("FTP:timeout reached for %p\n", aClosure));
+
+ bool found = gFtpHandler->mRootConnectionList.RemoveElement(aClosure);
+ if (!found) {
+ NS_ERROR("timerStruct not found");
+ return;
+ }
+
+ timerStruct* s = (timerStruct*)aClosure;
+ delete s;
+}
+
+nsresult nsFtpProtocolHandler::RemoveConnection(
+ nsIURI* aKey, nsFtpControlConnection** _retval) {
+ NS_ASSERTION(_retval, "null pointer");
+ NS_ASSERTION(aKey, "null pointer");
+
+ *_retval = nullptr;
+
+ nsAutoCString spec;
+ aKey->GetPrePath(spec);
+
+ LOG(("FTP:removing connection for %s\n", spec.get()));
+
+ timerStruct* ts = nullptr;
+ uint32_t i;
+ bool found = false;
+
+ for (i = 0; i < mRootConnectionList.Length(); ++i) {
+ ts = mRootConnectionList[i];
+ if (strcmp(spec.get(), ts->key) == 0) {
+ found = true;
+ mRootConnectionList.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ if (!found) return NS_ERROR_FAILURE;
+
+ // swap connection ownership
+ ts->conn.forget(_retval);
+ delete ts;
+
+ return NS_OK;
+}
+
+nsresult nsFtpProtocolHandler::InsertConnection(nsIURI* aKey,
+ nsFtpControlConnection* aConn) {
+ NS_ASSERTION(aConn, "null pointer");
+ NS_ASSERTION(aKey, "null pointer");
+
+ if (aConn->mSessionId != mSessionId) return NS_ERROR_FAILURE;
+
+ nsAutoCString spec;
+ aKey->GetPrePath(spec);
+
+ LOG(("FTP:inserting connection for %s\n", spec.get()));
+
+ timerStruct* ts = new timerStruct();
+ if (!ts) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsITimer> timer;
+ nsresult rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(timer), nsFtpProtocolHandler::Timeout, ts,
+ mIdleTimeout * 1000, nsITimer::TYPE_REPEATING_SLACK,
+ "nsFtpProtocolHandler::InsertConnection");
+ if (NS_FAILED(rv)) {
+ delete ts;
+ return rv;
+ }
+
+ ts->key = ToNewCString(spec, mozilla::fallible);
+ if (!ts->key) {
+ delete ts;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // ts->conn is a RefPtr
+ ts->conn = aConn;
+ ts->timer = timer;
+
+ //
+ // limit number of idle connections. if limit is reached, then prune
+ // eldest connection with matching key. if none matching, then prune
+ // eldest connection.
+ //
+ if (mRootConnectionList.Length() == IDLE_CONNECTION_LIMIT) {
+ uint32_t i;
+ for (i = 0; i < mRootConnectionList.Length(); ++i) {
+ timerStruct* candidate = mRootConnectionList[i];
+ if (strcmp(candidate->key, ts->key) == 0) {
+ mRootConnectionList.RemoveElementAt(i);
+ delete candidate;
+ break;
+ }
+ }
+ if (mRootConnectionList.Length() == IDLE_CONNECTION_LIMIT) {
+ timerStruct* eldest = mRootConnectionList[0];
+ mRootConnectionList.RemoveElementAt(0);
+ delete eldest;
+ }
+ }
+
+ mRootConnectionList.AppendElement(ts);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsIObserver
+
+NS_IMETHODIMP
+nsFtpProtocolHandler::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ LOG(("FTP:observing [%s]\n", aTopic));
+
+ if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+ nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(aSubject);
+ if (!branch) {
+ NS_ERROR("no prefbranch");
+ return NS_ERROR_UNEXPECTED;
+ }
+ int32_t val;
+ nsresult rv = branch->GetIntPref(IDLE_TIMEOUT_PREF, &val);
+ if (NS_SUCCEEDED(rv)) mIdleTimeout = val;
+ bool enabled;
+ rv = branch->GetBoolPref(ENABLED_PREF, &enabled);
+ if (NS_SUCCEEDED(rv)) mEnabled = enabled;
+
+ rv = branch->GetIntPref(QOS_DATA_PREF, &val);
+ if (NS_SUCCEEDED(rv)) mDataQoSBits = (uint8_t)clamped(val, 0, 0xff);
+
+ rv = branch->GetIntPref(QOS_CONTROL_PREF, &val);
+ if (NS_SUCCEEDED(rv)) mControlQoSBits = (uint8_t)clamped(val, 0, 0xff);
+ } else if (!strcmp(aTopic, "network:offline-about-to-go-offline")) {
+ ClearAllConnections();
+ } else if (!strcmp(aTopic, "net:clear-active-logins")) {
+ ClearAllConnections();
+ mSessionId++;
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected topic");
+ }
+
+ return NS_OK;
+}
+
+void nsFtpProtocolHandler::ClearAllConnections() {
+ uint32_t i;
+ for (i = 0; i < mRootConnectionList.Length(); ++i)
+ delete mRootConnectionList[i];
+ mRootConnectionList.Clear();
+}
diff --git a/netwerk/protocol/ftp/nsFtpProtocolHandler.h b/netwerk/protocol/ftp/nsFtpProtocolHandler.h
new file mode 100644
index 0000000000..38ce33f8b7
--- /dev/null
+++ b/netwerk/protocol/ftp/nsFtpProtocolHandler.h
@@ -0,0 +1,85 @@
+/* -*- 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 nsFtpProtocolHandler_h__
+#define nsFtpProtocolHandler_h__
+
+#include "nsFtpControlConnection.h"
+#include "nsIProxiedProtocolHandler.h"
+#include "nsTArray.h"
+#include "nsITimer.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+
+//-----------------------------------------------------------------------------
+
+class nsFtpProtocolHandler final : public nsIProxiedProtocolHandler,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIPROXIEDPROTOCOLHANDLER
+ NS_DECL_NSIOBSERVER
+
+ nsFtpProtocolHandler();
+
+ nsresult Init();
+
+ // FTP Connection list access
+ nsresult InsertConnection(nsIURI* aKey, nsFtpControlConnection* aConn);
+ nsresult RemoveConnection(nsIURI* aKey, nsFtpControlConnection** aConn);
+ uint32_t GetSessionId() { return mSessionId; }
+
+ uint8_t GetDataQoSBits() { return mDataQoSBits; }
+ uint8_t GetControlQoSBits() { return mControlQoSBits; }
+
+ private:
+ virtual ~nsFtpProtocolHandler();
+
+ // Stuff for the timer callback function
+ struct timerStruct {
+ nsCOMPtr<nsITimer> timer;
+ RefPtr<nsFtpControlConnection> conn;
+ char* key;
+
+ timerStruct() : key(nullptr) {}
+
+ ~timerStruct() {
+ if (timer) timer->Cancel();
+ if (key) free(key);
+ if (conn) {
+ conn->Disconnect(NS_ERROR_ABORT);
+ }
+ }
+ };
+
+ static void Timeout(nsITimer* aTimer, void* aClosure);
+ void ClearAllConnections();
+
+ nsTArray<timerStruct*> mRootConnectionList;
+
+ int32_t mIdleTimeout;
+ bool mEnabled;
+
+ // When "clear active logins" is performed, all idle connection are dropped
+ // and mSessionId is incremented. When nsFtpState wants to insert idle
+ // connection we refuse to cache if its mSessionId is different (i.e.
+ // control connection had been created before last "clear active logins" was
+ // performed.
+ uint32_t mSessionId;
+
+ uint8_t mControlQoSBits;
+ uint8_t mDataQoSBits;
+};
+
+//-----------------------------------------------------------------------------
+
+extern nsFtpProtocolHandler* gFtpHandler;
+
+#include "mozilla/Logging.h"
+extern mozilla::LazyLogModule gFTPLog;
+
+#endif // !nsFtpProtocolHandler_h__
diff --git a/netwerk/protocol/ftp/nsIFTPChannel.idl b/netwerk/protocol/ftp/nsIFTPChannel.idl
new file mode 100644
index 0000000000..de82983841
--- /dev/null
+++ b/netwerk/protocol/ftp/nsIFTPChannel.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface may be used to determine if a channel is a FTP channel.
+ */
+[scriptable, uuid(07f0d5cd-1fd5-4aa3-b6fc-665bdc5dbf9f)]
+interface nsIFTPChannel : nsISupports
+{
+ attribute PRTime lastModifiedTime;
+};
+
+/**
+ * This interface may be defined as a notification callback on the FTP
+ * channel. It allows a consumer to receive a log of the FTP control
+ * connection conversation.
+ */
+[scriptable, uuid(455d4234-0330-43d2-bbfb-99afbecbfeb0)]
+interface nsIFTPEventSink : nsISupports
+{
+ /**
+ * XXX document this method! (see bug 328915)
+ */
+ void OnFTPControlLog(in boolean server, in string msg);
+};
diff --git a/netwerk/protocol/ftp/nsIFTPChannelParentInternal.idl b/netwerk/protocol/ftp/nsIFTPChannelParentInternal.idl
new file mode 100644
index 0000000000..2642c804b1
--- /dev/null
+++ b/netwerk/protocol/ftp/nsIFTPChannelParentInternal.idl
@@ -0,0 +1,15 @@
+/* -*- 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"
+
+/**
+ * This is an internal interface for FTP parent channel.
+ */
+[builtinclass, uuid(87b58410-83cb-42a7-b57b-27c07ef828d7)]
+interface nsIFTPChannelParentInternal : nsISupports
+{
+ void setErrorMsg(in string msg, in boolean useUTF8);
+};
diff --git a/netwerk/protocol/ftp/test/frametest/contents.html b/netwerk/protocol/ftp/test/frametest/contents.html
new file mode 100644
index 0000000000..077b8d8f75
--- /dev/null
+++ b/netwerk/protocol/ftp/test/frametest/contents.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<h2>Click a link to the left</h2>
+</body>
+</html>
diff --git a/netwerk/protocol/ftp/test/frametest/index.html b/netwerk/protocol/ftp/test/frametest/index.html
new file mode 100644
index 0000000000..6a8a736251
--- /dev/null
+++ b/netwerk/protocol/ftp/test/frametest/index.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<title>FTP Frameset Test</title>
+
+</head>
+
+<frameset cols="30%,70%" name="ftp_main_frame">
+ <frame src="menu.html" name="ftp_menu" scrolling="yes" marginwidth="0" marginheight="0" noresize>
+ <frame src="contents.html" name="ftp_content" scrolling="YES" marginwidth="0" marginheight="0" noresize>
+</frameset>
+
+</html>
diff --git a/netwerk/protocol/ftp/test/frametest/menu.html b/netwerk/protocol/ftp/test/frametest/menu.html
new file mode 100644
index 0000000000..9f43f00ee8
--- /dev/null
+++ b/netwerk/protocol/ftp/test/frametest/menu.html
@@ -0,0 +1,371 @@
+<html>
+
+<script language="javascript">
+/* eslint-disable no-unsanitized/method */
+<!--
+
+function add_location(url) {
+ // faster to do this in one assignment, but who really cares.
+
+ var string = '<LI> <a href=\"javascript: ftp_open(\'';
+ string += url;
+ string += '\')\"';
+ string += 'onmouseover=\"window.status=\'ftp://';
+ string += url;
+ string += '\'; return true; \">';
+ string += url;
+ string += "</a>";
+ document.writeln(string);
+}
+
+function ftp_open(url) {
+ url = "ftp://" + url;
+ parent.ftp_content.location = url;
+}
+
+// I like this format.
+document.writeln("<pre>");
+
+document.writeln("<br><OL><b>Company sites</b>");
+ add_location("ftp.mozilla.org");
+ add_location("ftp.sun.com");
+ add_location("ftp.iplanet.com");
+ add_location("ftp.netscape.com");
+ add_location("ftp.apple.com");
+ add_location("ftp.microsoft.com");
+ add_location("ftp.netmanage.com");
+
+document.writeln("</OL><br><OL><b>Misc sites</b>");
+ add_location("cal044202.student.utwente.nl");
+ add_location("download.intel.com");
+ add_location("fddisunsite.oit.unc.edu");
+ add_location("ftp.abas.de");
+ add_location("ftp.acm.org");
+ add_location("ftp.acomp.hu");
+ add_location("ftp.acri.fr");
+ add_location("ftp.alaska.edu");
+ add_location("ftp.altera.com");
+ add_location("ftp.amsat.org");
+ add_location("ftp.amtp.cam.ac.uk");
+ add_location("ftp.ar.freebsd.org");
+ add_location("ftp.ari.net");
+ add_location("ftp.arl.mil");
+ add_location("ftp.astro.ulg.ac.be");
+ add_location("ftp.avery-zweckform.com");
+ add_location("ftp.awi-bremerhaven.de");
+ add_location("ftp.axime-is.fr");
+ add_location("ftp.ba.cnr.it");
+ add_location("ftp.bath.ac.uk");
+ add_location("ftp.bic.mni.mcgill.ca");
+ add_location("ftp.biomed.ruhr-uni-bochum.de");
+ add_location("ftp.boerde.de");
+ add_location("ftp.bond.edu.au");
+ add_location("ftp.boulder.ibm.com");
+ add_location("ftp.brics.dk");
+ add_location("ftp.bris.ac.uk");
+ add_location("ftp.cablelabs.com");
+ add_location("ftp.cac.psu.edu");
+ add_location("ftp.cadpoint.se");
+ add_location("ftp.cas.cz");
+ add_location("ftp.cciw.ca");
+ add_location("ftp.ccs.queensu.ca");
+ add_location("ftp.ccsi.com");
+ add_location("ftp.cdrom.com");
+ add_location("ftp.cea.fr");
+ add_location("ftp.celestial.com");
+ add_location("ftp.cert.fr");
+ add_location("ftp.cgd.ucar.edu");
+ add_location("ftp.chiba-u.ac.jp");
+ add_location("ftp.cis.ksu.edu");
+ add_location("ftp.citi2.fr");
+ add_location("ftp.cityline.net");
+ add_location("ftp.cnam.fr");
+ add_location("ftp.cohesive.com");
+ add_location("ftp.contrib.net");
+ add_location("ftp.create.ucsb.edu");
+ add_location("ftp.cronyx.ru");
+ add_location("ftp.cs.arizona.edu");
+ add_location("ftp.cs.colorado.edu");
+ add_location("ftp.cs.concordia.ca");
+ add_location("ftp.cs.helsinki.fi");
+ add_location("ftp.cs.jhu.edu");
+ add_location("ftp.cs.monash.edu.au");
+ add_location("ftp.cs.ohiou.edu");
+ add_location("ftp.cs.rug.nl");
+ add_location("ftp.cs.toronto.edu");
+ add_location("ftp.cs.umanitoba.ca");
+ add_location("ftp.cs.uni-dortmund.de");
+ add_location("ftp.cs.vu.nl");
+ add_location("ftp.cse.cuhk.edu.hk");
+ add_location("ftp.cse.unsw.edu.au");
+ add_location("ftp.csse.monash.edu.au");
+ add_location("ftp.csus.edu");
+ add_location("ftp.cullasaja.com");
+ add_location("ftp.daimi.au.dk");
+ add_location("ftp.dcs.qmw.ac.uk");
+ add_location("ftp.delorie.com");
+ add_location("ftp.dementia.org");
+ add_location("ftp.dfki.uni-kl.de");
+ add_location("ftp.dgs.monash.edu.au");
+ add_location("ftp.dis.strath.ac.uk");
+ add_location("ftp.dosis.uni-dortmund.de");
+ add_location("ftp.duke.edu");
+ add_location("ftp.duplexx.com");
+ add_location("ftp.ece.ucdavis.edu");
+ add_location("ftp.ee.lbl.gov");
+ add_location("ftp.ee.rochester.edu");
+ add_location("ftp.ee.uts.edu.au");
+ add_location("ftp.efrei.fr");
+ add_location("ftp.elet.polimi.it");
+ add_location("ftp.elite.net");
+ add_location("ftp.embl-hamburg.de");
+ add_location("ftp.eng.buffalo.edu");
+ add_location("ftp.engr.uark.edu");
+ add_location("ftp.eni.co.jp");
+ add_location("ftp.enst-bretagne.fr");
+ add_location("ftp.epix.net");
+ add_location("ftp.eskimo.com");
+ add_location("ftp.essential.org");
+ add_location("ftp.eunet.fi");
+ add_location("ftp.eurexpo.com");
+ add_location("ftp.ex.ac.uk");
+ add_location("ftp.faximum.com");
+ add_location("ftp.fernuni-hagen.de");
+ add_location("ftp.fh-dortmund.de");
+ add_location("ftp.fit.qut.edu.au");
+ add_location("ftp.forum.swarthmore.edu");
+ add_location("ftp.fsu.edu");
+ add_location("ftp.ftp.epson.com");
+ add_location("ftp.fu-berlin.de");
+ add_location("ftp.fujixerox.co.jp");
+ add_location("ftp.game.org");
+ add_location("ftp.ge.ucl.ac.uk");
+ add_location("ftp.genetics.wisc.edu");
+ add_location("ftp.geo.uu.nl");
+ add_location("ftp.geom.umn.edu");
+ add_location("ftp.gfdl.gov");
+ add_location("ftp.gigo.com");
+ add_location("ftp.giss.nasa.gov");
+ add_location("ftp.globalnet.co.uk");
+ add_location("ftp.gnu.org");
+ add_location("ftp.gnu.vbs.at");
+ add_location("ftp.gps.caltech.edu");
+ add_location("ftp.grau-wzs.de");
+ add_location("ftp.gsoc.dlr.de");
+ add_location("ftp.gutenberg.org");
+ add_location("ftp.hawaii.edu");
+ add_location("ftp.hep.net");
+ add_location("ftp.hgc.edu");
+ add_location("ftp.hgmp.mrc.ac.uk");
+ add_location("ftp.hugin.dk");
+ add_location("ftp.ic.tsu.ru");
+ add_location("ftp.icce.rug.nl");
+ add_location("ftp.icon-stl.net");
+ add_location("ftp.icor.fr");
+ add_location("ftp.ics.uci.edu");
+ add_location("ftp.idsia.ch");
+ add_location("ftp.ifm.liu.se");
+ add_location("ftp.ifm.uni-kiel.de");
+ add_location("ftp.iglou.com");
+ add_location("ftp.ign.fr");
+ add_location("ftp.imag.fr");
+ add_location("ftp.inel.gov");
+ add_location("ftp.inf.ethz.ch");
+ add_location("ftp.inf.puc-rio.br");
+ add_location("ftp.infoflex.se");
+ add_location("ftp.informatik.rwth-aachen.de");
+ add_location("ftp.informatik.uni-bremen.de");
+ add_location("ftp.informatik.uni-hannover.de");
+ add_location("ftp.infoscandic.se");
+ add_location("ftp.intel.com");
+ add_location("ftp.intergraph.com");
+ add_location("ftp.ionet.net");
+ add_location("ftp.ipc.chiba-u.ac.jp");
+ add_location("ftp.ips.cs.tu-bs.de");
+ add_location("ftp.iram.rwth-aachen.de");
+ add_location("ftp.is.co.za");
+ add_location("ftp.isoc.org");
+ add_location("ftp.iteso.mx");
+ add_location("ftp.ivd.uni-stuttgart.de");
+ add_location("ftp.iway.fr");
+ add_location("ftp.jcu.edu.au");
+ add_location("ftp.jhuapl.edu");
+ add_location("ftp.jpix.ad.jp");
+ add_location("ftp.karlin.mff.cuni.cz");
+ add_location("ftp.kfu.com");
+ add_location("ftp.kfunigraz.ac.at");
+ add_location("ftp.khm.de");
+ add_location("ftp.ki.se");
+ add_location("ftp.komkon.org");
+ add_location("ftp.laas.fr");
+ add_location("ftp.lanl.gov");
+ add_location("ftp.lantronix.com");
+ add_location("ftp.lava.net");
+ add_location("ftp.lcs.mit.edu");
+ add_location("ftp.legend.co.uk");
+ add_location("ftp.leidenuniv.nl");
+ add_location("ftp.let.rug.nl");
+ add_location("ftp.linux.co.uk");
+ add_location("ftp.linux.unife.it");
+ add_location("ftp.liv.ac.uk");
+ add_location("ftp.livingston.com");
+ add_location("ftp.lnt.e-technik.tu-muenchen.de");
+ add_location("ftp.lsu.edu");
+ add_location("ftp.lth.se");
+ add_location("ftp.lysator.liu.se");
+ add_location("ftp.mailbase.ac.uk");
+ add_location("ftp.mainstream.net");
+ add_location("ftp.maricopa.edu");
+ add_location("ftp.math.fu-berlin.de");
+ add_location("ftp.math.hr");
+ add_location("ftp.math.utah.edu");
+ add_location("ftp.mathematik.uni-marburg.de");
+ add_location("ftp.maths.tcd.ie");
+ add_location("ftp.maths.usyd.edu.au");
+ add_location("ftp.mathworks.com");
+ add_location("ftp.mbb.ki.se");
+ add_location("ftp.mbt.ru");
+ add_location("ftp.mcs.net");
+ add_location("ftp.mcs.vuw.ac.nz");
+ add_location("ftp.media.mit.edu");
+ add_location("ftp.meme.com");
+ add_location("ftp.merl.com");
+ add_location("ftp.microport.com");
+ add_location("ftp.mms.de");
+ add_location("ftp.mpce.mq.edu.au");
+ add_location("ftp.mpgn.com");
+ add_location("ftp.mpipf-muenchen.mpg.de");
+ add_location("ftp.mscf.uky.edu");
+ add_location("ftp.natinst.com");
+ add_location("ftp.ncsa.uiuc.edu");
+ add_location("ftp.net-tel.co.uk");
+ add_location("ftp.net.cmu.edu");
+ add_location("ftp.netsw.org");
+ add_location("ftp.new-york.net");
+ add_location("ftp.nis.net");
+ add_location("ftp.nlm.nih.gov");
+ add_location("ftp.nmt.edu");
+ add_location("ftp.noao.edu");
+ add_location("ftp.ntnu.no");
+ add_location("ftp.nwu.edu");
+ add_location("ftp.nysaes.cornell.edu");
+ add_location("ftp.observ.u-bordeaux.fr");
+ add_location("ftp.oit.unc.edu");
+ add_location("ftp.oldenbourg.de");
+ add_location("ftp.omg.unb.ca");
+ add_location("ftp.onecall.net");
+ add_location("ftp.ornl.gov");
+ add_location("ftp.ozone.fmi.fi");
+ add_location("ftp.pacific.net.hk");
+ add_location("ftp.panix.com");
+ add_location("ftp.pcnet.com");
+ add_location("ftp.phred.org");
+ add_location("ftp.pnl.gov");
+ add_location("ftp.prairienet.org");
+ add_location("ftp.proxad.net");
+ add_location("ftp.proximity.com.au");
+ add_location("ftp.psg.com");
+ add_location("ftp.psy.uq.edu.au");
+ add_location("ftp.psychologie.uni-freiburg.de");
+ add_location("ftp.pwr.wroc.pl");
+ add_location("ftp.python.org");
+ add_location("ftp.quantum.de");
+ add_location("ftp.ra.phy.cam.ac.uk");
+ add_location("ftp.rasip.fer.hr");
+ add_location("ftp.rbgkew.org.uk");
+ add_location("ftp.rcsb.org");
+ add_location("ftp.realtime.net");
+ add_location("ftp.red-bean.com");
+ add_location("ftp.redac.co.uk");
+ add_location("ftp.redac.fr");
+ add_location("ftp.rediris.es");
+ add_location("ftp.rgn.it");
+ add_location("ftp.rice.edu");
+ add_location("ftp.rkk.hu");
+ add_location("ftp.robelle.com");
+ add_location("ftp.rose.hp.com");
+ add_location("ftp.rt66.com");
+ add_location("ftp.ruhr-uni-bochum.de");
+ add_location("ftp.rz.uni-frankfurt.de");
+ add_location("ftp.sat.dundee.ac.uk");
+ add_location("ftp.saugus.net");
+ add_location("ftp.schrodinger.com");
+ add_location("ftp.science-computing.de");
+ add_location("ftp.science.unitn.it");
+ add_location("ftp.sco.com");
+ add_location("ftp.scs.leeds.ac.uk");
+ add_location("ftp.scsr.nevada.edu");
+ add_location("ftp.sd.monash.edu.au");
+ add_location("ftp.sdv.fr");
+ add_location("ftp.selapo.vwl.uni-muenchen.de");
+ add_location("ftp.serv.net");
+ add_location("ftp.sgi.leeds.ac.uk");
+ add_location("ftp.shore.net");
+ add_location("ftp.socsci.auc.dk");
+ add_location("ftp.space.net");
+ add_location("ftp.spec.org");
+ add_location("ftp.stallion.com");
+ add_location("ftp.starnet.de");
+ add_location("ftp.stat.math.ethz.ch");
+ add_location("ftp.stat.umn.edu");
+ add_location("ftp.std.com");
+ add_location("ftp.structchem.uni-essen.de");
+ add_location("ftp.sunsite.org.uk");
+ add_location("ftp.syd.dms.csiro.au");
+ add_location("ftp.tapr.org");
+ add_location("ftp.teco.uni-karlsruhe.de");
+ add_location("ftp.tenon.com");
+ add_location("ftp.tierzucht.uni-kiel.de");
+ add_location("ftp.tnt.uni-hannover.de");
+ add_location("ftp.tu-clausthal.de");
+ add_location("ftp.uci.edu");
+ add_location("ftp.ucsd.edu");
+ add_location("ftp.udel.edu");
+ add_location("ftp.uec.ac.jp");
+ add_location("ftp.uibk.ac.at");
+ add_location("ftp.uit.co.uk");
+ add_location("ftp.uji.es");
+ add_location("ftp.uke.uni-hamburg.de");
+ add_location("ftp.ulcc.ac.uk");
+ add_location("ftp.um.es");
+ add_location("ftp.umi.cs.tu-bs.de");
+ add_location("ftp.uni-augsburg.de");
+ add_location("ftp.uni-dortmund.de");
+ add_location("ftp.uni-hannover.de");
+ add_location("ftp.uni-magdeburg.de");
+ add_location("ftp.unidata.ucar.edu");
+ add_location("ftp.unige.ch");
+ add_location("ftp.univ-aix.fr");
+ add_location("ftp.upc.es");
+ add_location("ftp.uradio.ku.dk");
+ add_location("ftp.uralexpress.ru");
+ add_location("ftp.urc.ac.ru");
+ add_location("ftp.ut.ee");
+ add_location("ftp.uunet.ca");
+ add_location("ftp.uwo.ca");
+ add_location("ftp.vaxxine.com");
+ add_location("ftp.visi.com");
+ add_location("ftp.vub.ac.be");
+ add_location("ftp.wfu.edu");
+ add_location("ftp.win.tue.nl");
+ add_location("ftp.wolfe.net");
+ add_location("sunsite.cnlab-switch.ch");
+ add_location("sunsite.sut.ac.jp");
+ add_location("ftp.cuhk.edu.hk");
+ add_location("ftp.cetis.hvu.nl");
+ add_location("ftp.clinet.fi");
+ add_location("ftp.gamma.ru");
+ add_location("ftp.itv.se");
+ add_location("ftp.cs.rpi.edu");
+ add_location("ftp.carrier.kiev.ua");
+ add_location("ftp.rosnet.ru");
+ add_location("ftp.nsk.su");
+ add_location("ftp.southcom.com.au");
+
+// -->
+
+</script>
+<body>
+<br><br><br>
+</body>
+</html>
diff --git a/netwerk/protocol/gio/components.conf b/netwerk/protocol/gio/components.conf
new file mode 100644
index 0000000000..2db452b649
--- /dev/null
+++ b/netwerk/protocol/gio/components.conf
@@ -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/.
+
+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',
+ },
+]
diff --git a/netwerk/protocol/gio/moz.build b/netwerk/protocol/gio/moz.build
new file mode 100644
index 0000000000..cd659096bf
--- /dev/null
+++ b/netwerk/protocol/gio/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/.
+
+SOURCES += [
+ "nsGIOProtocolHandler.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+EXPORTS += [
+ "nsGIOProtocolHandler.h",
+]
+
+FINAL_LIBRARY = "xul"
+
+CXXFLAGS += CONFIG["TK_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..61c642d8f1
--- /dev/null
+++ b/netwerk/protocol/gio/nsGIOProtocolHandler.cpp
@@ -0,0 +1,991 @@
+/* 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 "mozilla/Components.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Logging.h"
+#include "mozilla/NullPrincipal.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIObserver.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 "plstr.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
+static mozilla::LazyLogModule sGIOLog("gio");
+#define LOG(args) MOZ_LOG(sGIOLog, 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.
+ */
+typedef enum {
+ MOUNT_OPERATION_IN_PROGRESS, /** \enum operation in progress */
+ MOUNT_OPERATION_SUCCESS, /** \enum operation successful */
+ MOUNT_OPERATION_FAILED /** \enum operation not successful */
+} MountOperationResult;
+//-----------------------------------------------------------------------------
+/**
+ * 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 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 {
+ ~nsGIOInputStream() { Close(); }
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+
+ explicit nsGIOInputStream(const nsCString& uriSpec)
+ : mSpec(uriSpec),
+ mChannel(nullptr),
+ mHandle(nullptr),
+ mStream(nullptr),
+ mBytesRemaining(UINT64_MAX),
+ mStatus(NS_OK),
+ mDirList(nullptr),
+ mDirListPtr(nullptr),
+ mDirBufCursor(0),
+ mDirOpen(false),
+ mMonitorMountInProgress("GIOInputStream::MountFinished") {}
+
+ 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.
+
+ NS_ADDREF(mChannel = channel);
+ }
+ void SetMountResult(MountOperationResult result, gint error_code);
+
+ private:
+ 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; // manually refcounted
+ GFile* mHandle;
+ GFileInputStream* mStream;
+ uint64_t mBytesRemaining;
+ nsresult mStatus;
+ GList* mDirList;
+ GList* mDirListPtr;
+ nsCString mDirBuf;
+ uint32_t mDirBufCursor;
+ bool mDirOpen;
+ MountOperationResult mMountRes;
+ mozilla::Monitor mMonitorMountInProgress;
+ 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 = 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 == MOUNT_OPERATION_IN_PROGRESS) mon.Wait();
+
+ g_object_unref(mount_op);
+
+ if (mMountRes == 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 base URL (make sure it ends with a '/')
+ mDirBuf.AppendLiteral("300: ");
+ mDirBuf.Append(mSpec);
+ if (mSpec.get()[mSpec.Length() - 1] != '/') mDirBuf.Append('/');
+ mDirBuf.Append('\n');
+
+ // Write column names
+ mDirBuf.AppendLiteral(
+ "200: filename content-length last-modified file-type\n");
+
+ // Write charset (assume UTF-8)
+ // XXX is this correct?
+ mDirBuf.AppendLiteral("301: UTF-8\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;
+}
+
+/**
+ * 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(MOUNT_OPERATION_FAILED, error->code);
+ g_error_free(error);
+ } else {
+ istream->SetMountResult(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() {
+ 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) {
+ // 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 smb and sftp
+ // protocols so far.
+ nsresult rv =
+ prefs->GetCharPref(MOZ_GIO_SUPPORTED_PROTOCOLS, mSupportedProtocols);
+ if (NS_SUCCEEDED(rv)) {
+ mSupportedProtocols.StripWhitespace();
+ ToLowerCase(mSupportedProtocols);
+ } else {
+ mSupportedProtocols.AssignLiteral(
+#ifdef MOZ_PROXY_BYPASS_PROTECTION
+ "" // use none
+#else
+ "smb:,sftp:" // use defaults
+#endif
+ );
+ }
+ LOG(("gio: supported protocols \"%s\"\n", mSupportedProtocols.get()));
+}
+
+bool nsGIOProtocolHandler::IsSupportedProtocol(const nsCString& aSpec) {
+ const char* specString = aSpec.get();
+ const char* colon = strchr(specString, ':');
+ if (!colon) return false;
+
+ uint32_t length = colon - specString + 1;
+
+ // <scheme> + ':'
+ nsCString scheme(specString, length);
+
+ char* found = PL_strcasestr(mSupportedProtocols.get(), scheme.get());
+ if (!found) return false;
+
+ if (found[length] != ',' && found[length] != '\0') return false;
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::GetScheme(nsACString& aScheme) {
+ aScheme.AssignLiteral(MOZ_GIO_SCHEME);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::GetDefaultPort(int32_t* aDefaultPort) {
+ *aDefaultPort = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOProtocolHandler::GetProtocolFlags(uint32_t* aProtocolFlags) {
+ // Is URI_STD true of all GnomeVFS URI types?
+ *aProtocolFlags = URI_STD | URI_DANGEROUS_TO_LOAD;
+ 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;
+
+ if (!IsSupportedProtocol(spec)) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+
+ nsAutoCString scheme;
+ rv = aURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!IsValidGIOScheme(scheme)) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+
+ 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..4c8d936402
--- /dev/null
+++ b/netwerk/protocol/gio/nsGIOProtocolHandler.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 nsGIOProtocolHandler_h___
+#define nsGIOProtocolHandler_h___
+
+#include "nsIProtocolHandler.h"
+#include "nsIObserver.h"
+#include "nsIPrefBranch.h"
+#include "nsStringFwd.h"
+
+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& spec);
+
+ private:
+ nsresult Init();
+ ~nsGIOProtocolHandler() = default;
+
+ void InitSupportedProtocolsPref(nsIPrefBranch* prefs);
+
+ static mozilla::StaticRefPtr<nsGIOProtocolHandler> sSingleton;
+ 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..cb91af13fb
--- /dev/null
+++ b/netwerk/protocol/http/ASpdySession.cpp
@@ -0,0 +1,113 @@
+/* -*- 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/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[0] = SpdyVersion::HTTP_2;
+ VersionString[0] = "h2"_ns;
+ ALPNCallbacks[0] = Http2Session::ALPNCallback;
+}
+
+bool SpdyInformation::ProtocolEnabled(uint32_t index) const {
+ MOZ_ASSERT(index < kCount, "index out of range");
+
+ return gHttpHandler->IsHttp2Enabled();
+}
+
+nsresult SpdyInformation::GetNPNIndex(const nsACString& npnString,
+ uint32_t* result) const {
+ if (npnString.IsEmpty()) return NS_ERROR_FAILURE;
+
+ for (uint32_t index = 0; index < kCount; ++index) {
+ if (npnString.Equals(VersionString[index])) {
+ *result = index;
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+//////////////////////////////////////////
+// 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.Put(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..ab2dbc722b
--- /dev/null
+++ b/netwerk/protocol/http/ASpdySession.h
@@ -0,0 +1,126 @@
+/* -*- 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 "nsString.h"
+
+class nsISocketTransport;
+
+namespace mozilla {
+namespace net {
+
+class ASpdySession : public nsAHttpTransaction {
+ public:
+ ASpdySession() = default;
+ virtual ~ASpdySession() = default;
+
+ [[nodiscard]] virtual bool AddStream(nsAHttpTransaction*, int32_t, bool, bool,
+ 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;
+
+ // MaybeReTunnel() is called by the connection manager when it cannot
+ // dispatch a tunneled transaction. That might be because the tunnels it
+ // expects to see are dead (and we may or may not be able to make more),
+ // or it might just need to wait longer for one of them to become free.
+ //
+ // return true if the session takes back ownership of the transaction from
+ // the connection manager.
+ virtual bool MaybeReTunnel(nsAHttpTransaction*) = 0;
+
+ virtual void PrintDiagnostics(nsCString& log) = 0;
+
+ bool ResponseTimeoutEnabled() const final { return true; }
+
+ virtual void SendPing() = 0;
+
+ const static uint32_t kSendingChunkSize = 4095;
+ const static uint32_t kTCPSendBufferSize = 131072;
+
+ // 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 bool CanAcceptWebsocket() = 0;
+};
+
+typedef bool (*ALPNCallback)(nsISupports*); // nsISSLSocketControl is typical
+
+// 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;
+
+ static const uint32_t kCount = 1;
+
+ // determine the index (0..kCount-1) of the spdy information that
+ // correlates to the npn string. NS_FAILED() if no match is found.
+ [[nodiscard]] nsresult GetNPNIndex(const nsACString& npnString,
+ uint32_t* result) const;
+
+ // determine if a version of the protocol is enabled for index < kCount
+ bool ProtocolEnabled(uint32_t index) const;
+
+ SpdyVersion Version[kCount]; // telemetry enum e.g. SPDY_VERSION_31
+ nsCString VersionString[kCount]; // 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[kCount];
+};
+
+} // 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..1c52cf74aa
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamChild.cpp
@@ -0,0 +1,196 @@
+/* -*- 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 uint32_t kChunkSize = 128 * 1024;
+ uint32_t next = std::min(data.Length(), kChunkSize);
+ for (uint32_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::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 = GetMainThreadEventTarget();
+ }
+
+ 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..e7e044e05e
--- /dev/null
+++ b/netwerk/protocol/http/AltDataOutputStreamParent.cpp
@@ -0,0 +1,82 @@
+/* -*- 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/Unused.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");
+}
+
+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) {
+ 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..b42e1cb267
--- /dev/null
+++ b/netwerk/protocol/http/AltServiceChild.cpp
@@ -0,0 +1,114 @@
+/* -*- 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(),
+ ci->GetTopWindowOrigin());
+ }
+ };
+
+ 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, const nsCString& aTopWindowOrigin,
+ bool aPrivateBrowsing, bool aIsolated, 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,
+ aTopWindowOrigin, aPrivateBrowsing, aIsolated, 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..7bf4cead50
--- /dev/null
+++ b/netwerk/protocol/http/AltServiceChild.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 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,
+ const nsCString& aTopWindowOrigin,
+ bool aPrivateBrowsing, bool aIsolated,
+ 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..c5a9efe332
--- /dev/null
+++ b/netwerk/protocol/http/AltServiceParent.cpp
@@ -0,0 +1,52 @@
+/* -*- 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,
+ const nsCString& aTopWindowOrigin) {
+ LOG(("AltServiceParent::RecvClearHostMapping [this=%p]\n", this));
+ if (gHttpHandler) {
+ gHttpHandler->AltServiceCache()->ClearHostMapping(
+ aHost, aPort, aOriginAttributes, aTopWindowOrigin);
+ }
+ 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 nsACString& aTopWindowOrigin,
+ const bool& aPrivateBrowsing, const bool& aIsolated,
+ 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, aTopWindowOrigin, aPrivateBrowsing,
+ aIsolated, 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..7cde277d37
--- /dev/null
+++ b/netwerk/protocol/http/AltServiceParent.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 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,
+ const nsCString& aTopWindowOrigin);
+
+ mozilla::ipc::IPCResult RecvProcessHeader(
+ const nsCString& aBuf, const nsCString& aOriginScheme,
+ const nsCString& aOriginHost, const int32_t& aOriginPort,
+ const nsACString& aUsername, const nsACString& aTopWindowOrigin,
+ const bool& aPrivateBrowsing, const bool& aIsolated,
+ 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..1bc38b273d
--- /dev/null
+++ b/netwerk/protocol/http/AlternateServices.cpp
@@ -0,0 +1,1330 @@
+/* -*- 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 "nsComponentManagerUtils.h"
+#include "nsEscape.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpChannel.h"
+#include "nsHttpHandler.h"
+#include "nsIOService.h"
+#include "nsThreadUtils.h"
+#include "nsHttpTransaction.h"
+#include "nsISSLSocketControl.h"
+#include "nsIWellKnownOpportunisticUtils.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/dom/PContent.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/net/AltSvcTransactionParent.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,
+ const nsACString& topWindowOrigin, bool privateBrowsing, bool isolated,
+ 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, topWindowOrigin);
+ 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;
+ }
+
+ uint32_t spdyIndex;
+ SpdyInformation* spdyInfo = gHttpHandler->SpdyInfo();
+ if (!(NS_SUCCEEDED(spdyInfo->GetNPNIndex(npnToken, &spdyIndex)) &&
+ spdyInfo->ProtocolEnabled(spdyIndex)) &&
+ !(isHttp3 && gHttpHandler->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, topWindowOrigin, privateBrowsing,
+ isolated, 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, topWindowOrigin);
+ } 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(
+ DataStorage* storage, int32_t epoch, const nsACString& originScheme,
+ const nsACString& originHost, int32_t originPort,
+ const nsACString& username, const nsACString& topWindowOrigin,
+ bool privateBrowsing, bool isolated, 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),
+ mTopWindowOrigin(topWindowOrigin),
+ mPrivate(privateBrowsing),
+ mIsolated(isolated),
+ mExpiresAt(expiresAt),
+ mValidated(false),
+ mMixedScheme(false),
+ mNPNToken(npnToken),
+ mOriginAttributes(originAttributes),
+ mSyncOnlyOnSuccess(false),
+ 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,
+ mIsolated, mTopWindowOrigin, mOriginAttributes, mIsHttp3);
+ }
+}
+
+void AltSvcMapping::MakeHashKey(
+ nsCString& outKey, const nsACString& originScheme,
+ const nsACString& originHost, int32_t originPort, bool privateBrowsing,
+ bool isolated, const nsACString& topWindowOrigin,
+ 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(':');
+
+ if (isolated) {
+ outKey.Append('I');
+ outKey.Append(':');
+ outKey.Append(topWindowOrigin);
+ outKey.Append(
+ '|'); // Be careful, the top window origin may contain colons!
+ }
+ outKey.Append(aHttp3 ? '3' : '.');
+}
+
+int32_t AltSvcMapping::TTL() { return mExpiresAt - NowInSeconds(); }
+
+void AltSvcMapping::SyncString(const nsCString& str) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mStorage->Put(HashKey(), str,
+ mPrivate ? DataStorage_Private : DataStorage_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;
+ }
+
+ mStorage->Put(HashKey(), value,
+ mPrivate ? DataStorage_Private : DataStorage_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, mTopWindowOrigin, pi,
+ originAttributes, mAlternateHost, mAlternatePort, mIsHttp3);
+
+ // 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->SetIsolated(mIsolated);
+ 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(mTopWindowOrigin);
+ out.Append('|'); // Be careful, the top window origin may contain colons!
+ out.Append(mIsolated ? 'y' : 'n');
+ out.Append(':');
+ out.Append(mIsHttp3 ? 'y' : 'n');
+ out.Append(':');
+ // Add code to serialize new members here!
+}
+
+AltSvcMapping::AltSvcMapping(DataStorage* storage, int32_t epoch,
+ const nsCString& str)
+ : mStorage(storage),
+ mStorageEpoch(epoch),
+ mSyncOnlyOnSuccess(false),
+ mIsHttp3(false) {
+ 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;
+ mTopWindowOrigin = Substring(str, start, idx - start);
+ separator = ':';
+ _NS_NEXT_TOKEN;
+ mIsolated = Substring(str, start, idx - start).EqualsLiteral("y");
+ _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, mIsolated, mTopWindowOrigin,
+ 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 & ~NS_HTTP_ALLOW_KEEPALIVE),
+ 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());
+}
+
+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<nsISupports> secInfo;
+ mConnection->GetSecurityInfo(getter_AddRefs(secInfo));
+ nsCOMPtr<nsISSLSocketControl> socketControl = do_QueryInterface(secInfo);
+
+ 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)) ||
+ NS_FAILED(chan->SetAllowAltSvc(false)) ||
+ NS_FAILED(chan->SetRedirectMode(
+ nsIHttpChannelInternal::REDIRECT_MODE_ERROR)) ||
+ NS_FAILED(chan->SetLoadInfo(loadInfo)) ||
+ 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());
+ uint32_t oldLen = mWKResponse.Length();
+ uint64_t newLen = 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 [%d]\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() {
+ if (mStorage) {
+ return;
+ }
+
+ auto initTask = [&]() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // DataStorage gives synchronous access to a memory based hash table
+ // that is backed by disk where those writes are done asynchronously
+ // on another thread
+ mStorage = DataStorage::Get(DataStorageClass::AlternateServices);
+ if (!mStorage) {
+ LOG(("AltSvcCache::EnsureStorageInited WARN NO STORAGE\n"));
+ return;
+ }
+
+ if (NS_FAILED(mStorage->Init(nullptr))) {
+ mStorage = nullptr;
+ }
+
+ mStorageEpoch = NowInSeconds();
+ };
+
+ if (NS_IsMainThread()) {
+ initTask();
+ return;
+ }
+
+ nsCOMPtr<nsIEventTarget> main = GetMainThreadEventTarget();
+ if (!main) {
+ return;
+ }
+
+ SyncRunnable::DispatchToThread(
+ main, new SyncRunnable(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() && !mStorage->IsReady()) {
+ LOG(("AltSvcCache::LookupMapping %p skip when storage is not ready\n",
+ this));
+ return nullptr;
+ }
+
+ nsCString val(mStorage->Get(
+ key, privateBrowsing ? DataStorage_Private : DataStorage_Persistent));
+ if (val.IsEmpty()) {
+ LOG(("AltSvcCache::LookupMapping %p MISS\n", this));
+ return nullptr;
+ }
+ RefPtr<AltSvcMapping> rv = new AltSvcMapping(mStorage, mStorageEpoch, val);
+ if (!rv->Validated() && (rv->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));
+ mStorage->Remove(
+ key, rv->Private() ? DataStorage_Private : DataStorage_Persistent);
+ return nullptr;
+ }
+
+ if (rv->IsHttp3() &&
+ (!gHttpHandler->IsHttp3Enabled() ||
+ !gHttpHandler->IsHttp3VersionSupported(rv->NPNToken()) ||
+ gHttpHandler->IsHttp3Excluded(rv->AlternateHost()))) {
+ // If Http3 is disabled or the version not supported anymore, remove the
+ // mapping.
+ mStorage->Remove(
+ key, rv->Private() ? DataStorage_Private : DataStorage_Persistent);
+ return nullptr;
+ }
+
+ if (rv->TTL() <= 0) {
+ LOG(("AltSvcCache::LookupMapping %p expired hit - MISS\n", this));
+ mStorage->Remove(
+ key, rv->Private() ? DataStorage_Private : DataStorage_Persistent);
+ return nullptr;
+ }
+
+ MOZ_ASSERT(rv->Private() == privateBrowsing);
+ LOG(("AltSvcCache::LookupMapping %p HIT %p\n", this, rv.get()));
+ return rv.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;
+ }
+
+ // 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, bool isolated, const nsACString& topWindowOrigin,
+ 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,
+ isolated, topWindowOrigin, 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,
+ isolated, topWindowOrigin, 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,
+ const nsACString& topWindowOrigin)
+ : Runnable("net::ProxyClearHostMapping"),
+ mHost(host),
+ mPort(port),
+ mOriginAttributes(originAttributes),
+ mTopWindowOrigin(topWindowOrigin) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ gHttpHandler->AltServiceCache()->ClearHostMapping(
+ mHost, mPort, mOriginAttributes, mTopWindowOrigin);
+ return NS_OK;
+ }
+
+ private:
+ nsCString mHost;
+ int32_t mPort;
+ OriginAttributes mOriginAttributes;
+ nsCString mTopWindowOrigin;
+};
+
+void AltSvcCache::ClearHostMapping(const nsACString& host, int32_t port,
+ const OriginAttributes& originAttributes,
+ const nsACString& topWindowOrigin) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> event = new ProxyClearHostMapping(
+ host, port, originAttributes, topWindowOrigin);
+ 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) {
+ for (int isolate = 0; isolate < 2; ++isolate) {
+ AltSvcMapping::MakeHashKey(key, scheme, host, port, bool(pb),
+ bool(isolate), topWindowOrigin,
+ originAttributes, false);
+ RefPtr<AltSvcMapping> existing = LookupMapping(key, bool(pb));
+ if (existing) {
+ existing->SetExpired();
+ }
+ AltSvcMapping::MakeHashKey(key, scheme, host, port, bool(pb),
+ bool(isolate), topWindowOrigin,
+ 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(), ci->GetTopWindowOrigin());
+ }
+}
+
+void AltSvcCache::ClearAltServiceMappings() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mStorage) {
+ mStorage->Clear();
+ }
+}
+
+nsresult AltSvcCache::GetAltSvcCacheKeys(nsTArray<nsCString>& value) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (gHttpHandler->AllowAltSvc() && mStorage) {
+ nsTArray<mozilla::psm::DataStorageItem> items;
+ mStorage->GetAll(&items);
+
+ for (const auto& item : items) {
+ value.AppendElement(item.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;
+}
+
+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..8288ad6095
--- /dev/null
+++ b/netwerk/protocol/http/AlternateServices.h
@@ -0,0 +1,277 @@
+/* -*- 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 "mozilla/DataStorage.h"
+#include "nsRefPtrHashtable.h"
+#include "nsString.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(DataStorage* storage, int32_t storageEpoch,
+ const nsACString& originScheme, const nsACString& originHost,
+ int32_t originPort, const nsACString& username,
+ const nsACString& topWindowOrigin, bool privateBrowsing,
+ bool isolated, uint32_t expiresAt,
+ const nsACString& alternateHost, int32_t alternatePort,
+ const nsACString& npnToken,
+ const OriginAttributes& originAttributes, bool aIsHttp3);
+
+ public:
+ AltSvcMapping(DataStorage* storage, int32_t storageEpoch,
+ const nsCString& serialized);
+
+ static void ProcessHeader(
+ const nsCString& buf, const nsCString& originScheme,
+ const nsCString& originHost, int32_t originPort,
+ const nsACString& username, const nsACString& topWindowOrigin,
+ bool privateBrowsing, bool isolated, 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* pi);
+
+ 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; }
+ bool Isolated() { return mIsolated; }
+
+ 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, bool isolated,
+ const nsACString& topWindowOrigin,
+ const OriginAttributes& originAttributes,
+ bool aIsHttp3);
+
+ bool IsHttp3() { return mIsHttp3; }
+ const nsCString& NPNToken() const { return mNPNToken; }
+
+ private:
+ virtual ~AltSvcMapping() = default;
+ void SyncString(const nsCString& val);
+ RefPtr<DataStorage> mStorage;
+ int32_t mStorageEpoch;
+ void Serialize(nsCString& out);
+
+ nsCString mHashKey;
+
+ // If you change any of these members, update Serialize()
+ nsCString mAlternateHost;
+ MOZ_INIT_OUTSIDE_CTOR int32_t mAlternatePort;
+
+ nsCString mOriginHost;
+ MOZ_INIT_OUTSIDE_CTOR int32_t mOriginPort;
+
+ nsCString mUsername;
+ nsCString mTopWindowOrigin;
+ MOZ_INIT_OUTSIDE_CTOR bool mPrivate;
+ MOZ_INIT_OUTSIDE_CTOR bool mIsolated;
+
+ MOZ_INIT_OUTSIDE_CTOR uint32_t mExpiresAt; // alt-svc mappping
+
+ MOZ_INIT_OUTSIDE_CTOR bool mValidated;
+ MOZ_INIT_OUTSIDE_CTOR bool mHttps; // origin is https://
+ MOZ_INIT_OUTSIDE_CTOR bool
+ mMixedScheme; // .wk allows http and https on same con
+
+ nsCString mNPNToken;
+
+ OriginAttributes mOriginAttributes;
+
+ bool mSyncOnlyOnSuccess;
+ bool mIsHttp3;
+};
+
+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() : mStorageEpoch(0) {}
+ 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 pb,
+ bool isolated, const nsACString& topWindowOrigin,
+ const OriginAttributes& originAttributes, bool aHttp2Allowed,
+ bool aHttp3Allowed);
+ void ClearAltServiceMappings();
+ void ClearHostMapping(const nsACString& host, int32_t port,
+ const OriginAttributes& originAttributes,
+ const nsACString& topWindowOrigin);
+ void ClearHostMapping(nsHttpConnectionInfo* ci);
+ DataStorage* 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);
+ RefPtr<DataStorage> mStorage;
+ int32_t mStorageEpoch;
+};
+
+// 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..c78ba43fc4
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundChannelRegistrar.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 "BackgroundChannelRegistrar.h"
+
+#include "HttpBackgroundChannelParent.h"
+#include "HttpChannelParent.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/StaticPtr.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();
+ }
+ return do_AddRef(gSingleton);
+}
+
+// static
+void BackgroundChannelRegistrar::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ gSingleton = nullptr;
+}
+
+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.Put(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.Put(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..fe6ba88ac4
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundChannelRegistrar.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_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 {
+ typedef nsRefPtrHashtable<nsUint64HashKey, HttpChannelParent>
+ ChannelHashtable;
+ typedef nsRefPtrHashtable<nsUint64HashKey, HttpBackgroundChannelParent>
+ BackgroundChannelHashtable;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIBACKGROUNDCHANNELREGISTRAR
+
+ explicit BackgroundChannelRegistrar();
+
+ // Singleton accessor
+ static already_AddRefed<nsIBackgroundChannelRegistrar> GetOrCreate();
+
+ static void Shutdown();
+
+ 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..3bd302273f
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundDataBridgeChild.cpp
@@ -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 "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 nsDependentCSubstring& data) {
+ if (!mBgChild) {
+ return IPC_OK();
+ }
+
+ if (mBgChild->ChannelClosed()) {
+ Unused << Send__delete__(this);
+ return IPC_OK();
+ }
+
+ return mBgChild->RecvOnTransportAndData(NS_OK, NS_NET_STATUS_RECEIVING_FROM,
+ offset, count, data, true);
+}
+
+mozilla::ipc::IPCResult BackgroundDataBridgeChild::RecvOnStopRequest(
+ nsresult aStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers) {
+ if (!mBgChild) {
+ return IPC_OK();
+ }
+
+ if (mBgChild->ChannelClosed()) {
+ Unused << Send__delete__(this);
+ return IPC_OK();
+ }
+
+ return mBgChild->RecvOnStopRequest(aStatus, aTiming, aLastActiveTabOptHit,
+ aResponseTrailers,
+ nsTArray<ConsoleReportCollected>(), true);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/BackgroundDataBridgeChild.h b/netwerk/protocol/http/BackgroundDataBridgeChild.h
new file mode 100644
index 0000000000..42df66ad68
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundDataBridgeChild.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 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 nsDependentCSubstring& data);
+ mozilla::ipc::IPCResult RecvOnStopRequest(
+ nsresult aStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers);
+};
+
+} // 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..195af7b1c1
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundDataBridgeParent.cpp
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "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(NS_GetCurrentThread()) {
+ SocketProcessChild::GetSingleton()->AddDataBridgeToMap(aChannelID, this);
+}
+
+void BackgroundDataBridgeParent::ActorDestroy(ActorDestroyReason aWhy) {
+ SocketProcessChild::GetSingleton()->RemoveDataBridgeFromMap(mChannelID);
+}
+
+already_AddRefed<nsIThread> BackgroundDataBridgeParent::GetBackgroundThread() {
+ nsCOMPtr<nsIThread> thread = mBackgroundThread;
+ return thread.forget();
+}
+
+void BackgroundDataBridgeParent::Destroy() {
+ RefPtr<BackgroundDataBridgeParent> self = this;
+ MOZ_ALWAYS_SUCCEEDS(mBackgroundThread->Dispatch(
+ NS_NewRunnableFunction("BackgroundDataBridgeParent::Destroy",
+ [self]() {
+ if (self->CanSend()) {
+ Unused << self->Send__delete__(self);
+ }
+ }),
+ NS_DISPATCH_NORMAL));
+}
+
+void BackgroundDataBridgeParent::OnStopRequest(
+ nsresult aStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers) {
+ RefPtr<BackgroundDataBridgeParent> self = this;
+ MOZ_ALWAYS_SUCCEEDS(mBackgroundThread->Dispatch(
+ NS_NewRunnableFunction(
+ "BackgroundDataBridgeParent::OnStopRequest",
+ [self, aStatus, aTiming, aLastActiveTabOptHit, aResponseTrailers]() {
+ if (self->CanSend()) {
+ Unused << self->SendOnStopRequest(
+ aStatus, aTiming, aLastActiveTabOptHit, aResponseTrailers);
+ Unused << self->Send__delete__(self);
+ }
+ }),
+ 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..15fd4ca4ec
--- /dev/null
+++ b/netwerk/protocol/http/BackgroundDataBridgeParent.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/. */
+
+#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<nsIThread> GetBackgroundThread();
+ void Destroy();
+ void OnStopRequest(nsresult aStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers);
+
+ private:
+ virtual ~BackgroundDataBridgeParent() = default;
+
+ uint64_t mChannelID;
+ nsCOMPtr<nsIThread> mBackgroundThread;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_BackgroundDataBridgeParent_h
diff --git a/netwerk/protocol/http/CacheControlParser.cpp b/netwerk/protocol/http/CacheControlParser.cpp
new file mode 100644
index 0000000000..00d5b42d4f
--- /dev/null
+++ b/netwerk/protocol/http/CacheControlParser.cpp
@@ -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/. */
+
+#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() {
+ 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;
+ }
+ if (CheckChar(',')) {
+ SkipWhites();
+ Directive();
+ return;
+ }
+
+ 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..11dd509846
--- /dev/null
+++ b/netwerk/protocol/http/CachePushChecker.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/. */
+
+#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"
+
+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(GetCurrentEventTarget()) {}
+
+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, false, 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,
+ nsIApplicationCache* appCache,
+ 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,
+ 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,
+ nsIApplicationCache* appCache,
+ 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/ClassifierDummyChannel.cpp b/netwerk/protocol/http/ClassifierDummyChannel.cpp
new file mode 100644
index 0000000000..8306c43936
--- /dev/null
+++ b/netwerk/protocol/http/ClassifierDummyChannel.cpp
@@ -0,0 +1,775 @@
+/* -*- 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 "ClassifierDummyChannel.h"
+
+#include "mozilla/ContentBlocking.h"
+#include "mozilla/net/ClassifierDummyChannelChild.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "nsContentSecurityManager.h"
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "nsProxyRelease.h"
+#include "nsQueryObject.h"
+
+namespace mozilla {
+namespace net {
+
+/* static */ ClassifierDummyChannel::StorageAllowedState
+ClassifierDummyChannel::StorageAllowed(
+ nsIChannel* aChannel, const std::function<void(bool)>& aCallback) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ if (!httpChannel) {
+ // Any non-http channel is allowed.
+ return eStorageGranted;
+ }
+
+ if (!nsContentUtils::IsNonSubresourceRequest(aChannel)) {
+ // If this is a sub-resource, we don't need to check if the channel is
+ // annotated as tracker because:
+ // - if the SW doesn't respond, or it sends us back to the network, the
+ // channel will be annotated on the parent process.
+ // - if the SW answers, the content is taken from the cache, which is
+ // considered trusted.
+ return eStorageGranted;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+
+ if (StaticPrefs::privacy_trackingprotection_annotate_channels()) {
+ dom::ContentChild* cc =
+ static_cast<dom::ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return eStorageDenied;
+ }
+
+ if (!ClassifierDummyChannelChild::Create(httpChannel, uri, aCallback)) {
+ return eStorageDenied;
+ }
+
+ return eAsyncNeeded;
+ }
+
+ if (ContentBlocking::ShouldAllowAccessFor(httpChannel, uri, nullptr)) {
+ return eStorageGranted;
+ }
+
+ return eStorageDenied;
+}
+
+NS_IMPL_ADDREF(ClassifierDummyChannel)
+NS_IMPL_RELEASE(ClassifierDummyChannel)
+
+NS_INTERFACE_MAP_BEGIN(ClassifierDummyChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY(nsIClassifiedChannel)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(ClassifierDummyChannel)
+NS_INTERFACE_MAP_END
+
+ClassifierDummyChannel::ClassifierDummyChannel(nsIURI* aURI,
+ nsIURI* aTopWindowURI,
+ nsresult aTopWindowURIResult,
+ nsILoadInfo* aLoadInfo)
+ : mTopWindowURI(aTopWindowURI),
+ mTopWindowURIResult(aTopWindowURIResult),
+ mFirstPartyClassificationFlags(0),
+ mThirdPartyClassificationFlags(0) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ SetOriginalURI(aURI);
+ SetLoadInfo(aLoadInfo);
+}
+
+ClassifierDummyChannel::~ClassifierDummyChannel() {
+ NS_ReleaseOnMainThread("ClassifierDummyChannel::mLoadInfo",
+ mLoadInfo.forget());
+ NS_ReleaseOnMainThread("ClassifierDummyChannel::mURI", mURI.forget());
+ NS_ReleaseOnMainThread("ClassifierDummyChannel::mTopWindowURI",
+ mTopWindowURI.forget());
+}
+
+void ClassifierDummyChannel::AddClassificationFlags(
+ uint32_t aClassificationFlags, bool aThirdParty) {
+ if (aThirdParty) {
+ mThirdPartyClassificationFlags |= aClassificationFlags;
+ } else {
+ mFirstPartyClassificationFlags |= aClassificationFlags;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// ClassifierDummyChannel::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetOriginalURI(nsIURI** aOriginalURI) {
+ NS_IF_ADDREF(*aOriginalURI = mURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetOriginalURI(nsIURI* aOriginalURI) {
+ mURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetURI(nsIURI** aURI) {
+ NS_IF_ADDREF(*aURI = mURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetOwner(nsISupports** aOwner) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetOwner(nsISupports* aOwner) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aNotificationCallbacks) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetSecurityInfo(nsISupports** aSecurityInfo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetContentType(nsACString& aContentType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetContentType(const nsACString& aContentType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetContentCharset(nsACString& aContentCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetContentCharset(const nsACString& aContentCharset) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetContentLength(int64_t* aContentLength) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetContentLength(int64_t aContentLength) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::Open(nsIInputStream** aStream) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::AsyncOpen(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener);
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetContentDisposition(uint32_t* aContentDisposition) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetContentDisposition(uint32_t aContentDisposition) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// ClassifierDummyChannel::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetName(nsACString& aName) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::IsPending(bool* aRetval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetStatus(nsresult* aStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::Cancel(nsresult aStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+ClassifierDummyChannel::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetIsDocument(bool* aIsDocument) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+//-----------------------------------------------------------------------------
+// ClassifierDummyChannel::nsIHttpChannelInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetDocumentURI(nsIURI** aDocumentURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetDocumentURI(nsIURI* aDocumentURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetRequestVersion(uint32_t* aMajor, uint32_t* aMinor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetResponseVersion(uint32_t* aMajor, uint32_t* aMinor) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::TakeAllSecurityMessages(
+ nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetCookie(const nsACString& aCookieHeader) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetupFallbackChannel(const char* aFallbackKey) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetIsAuthChannel(bool* aIsAuthChannel) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetThirdPartyFlags(uint32_t* aThirdPartyFlags) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetThirdPartyFlags(uint32_t aThirdPartyFlags) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetForceAllowThirdPartyCookie(
+ bool* aForceAllowThirdPartyCookie) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetForceAllowThirdPartyCookie(
+ bool aForceAllowThirdPartyCookie) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetCanceled(bool* aCanceled) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetChannelIsForDownload(bool* aChannlIsForDownload) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetChannelIsForDownload(bool aChannlIsForDownload) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetLocalAddress(nsACString& aLocalAddress) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetLocalPort(int32_t* aLocalPort) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetRemoteAddress(nsACString& aRemoteAddress) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetRemotePort(int32_t* aRemotePort) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetCacheKeysRedirectChain(
+ nsTArray<nsCString>* aCacheKeys) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::HTTPUpgrade(const nsACString& aProtocolName,
+ nsIHttpUpgradeListener* aListener) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetOnlyConnect(bool* aOnlyConnect) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetConnectOnly() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetAllowSpdy(bool* aAllowSpdy) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetAllowSpdy(bool aAllowSpdy) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetAllowHttp3(bool* aAllowHttp3) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetAllowHttp3(bool aAllowHttp3) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetResponseTimeoutEnabled(
+ bool* aResponseTimeoutEnabled) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetResponseTimeoutEnabled(
+ bool aResponseTimeoutEnabled) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetInitialRwin(uint32_t* aInitialRwin) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetInitialRwin(uint32_t aInitialRwin) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetApiRedirectToURI(nsIURI** aApiRedirectToURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetAllowAltSvc(bool* aAllowAltSvc) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetAllowAltSvc(bool aAllowAltSvc) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetBeConservative(bool* aBeConservative) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetBeConservative(bool aBeConservative) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetIsTRRServiceChannel(bool* aTrr) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetIsTRRServiceChannel(bool aTrr) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetIsResolvedByTRR(bool* aResolvedByTRR) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetTlsFlags(uint32_t* aTlsFlags) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetTlsFlags(uint32_t aTlsFlags) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetLastModifiedTime(PRTime* aLastModifiedTime) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetCorsIncludeCredentials(
+ bool* aCorsIncludeCredentials) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetCorsIncludeCredentials(
+ bool aCorsIncludeCredentials) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetCorsMode(uint32_t* aCorsMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetCorsMode(uint32_t aCorsMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetRedirectMode(uint32_t* aRedirectMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetRedirectMode(uint32_t aRedirectMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetFetchCacheMode(uint32_t* aFetchCacheMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetFetchCacheMode(uint32_t aFetchCacheMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetTopWindowURI(nsIURI** aTopWindowURI) {
+ nsCOMPtr<nsIURI> topWindowURI = mTopWindowURI;
+ topWindowURI.forget(aTopWindowURI);
+ return mTopWindowURIResult;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetTopWindowURIIfUnknown(nsIURI* aTopWindowURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetProxyURI(nsIURI** aProxyURI) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void ClassifierDummyChannel::SetCorsPreflightParameters(
+ const nsTArray<nsCString>& aUnsafeHeaders,
+ bool aShouldStripRequestBodyHeader) {}
+
+void ClassifierDummyChannel::SetAltDataForChild(bool aIsForChild) {}
+void ClassifierDummyChannel::DisableAltDataCache() {}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetBlockAuthPrompt(bool* aBlockAuthPrompt) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetBlockAuthPrompt(bool aBlockAuthPrompt) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetIntegrityMetadata(nsAString& aIntegrityMetadata) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetIntegrityMetadata(
+ const nsAString& aIntegrityMetadata) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetConnectionInfoHashKey(
+ nsACString& aConnectionInfoHashKey) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetLastRedirectFlags(uint32_t* aLastRedirectFlags) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetLastRedirectFlags(uint32_t aLastRedirectFlags) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetNavigationStartTimeStamp(
+ TimeStamp* aNavigationStartTimeStamp) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::SetNavigationStartTimeStamp(
+ TimeStamp aNavigationStartTimeStamp) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::CancelByURLClassifier(nsresult aErrorCode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void ClassifierDummyChannel::SetIPv4Disabled() {}
+
+void ClassifierDummyChannel::SetIPv6Disabled() {}
+
+bool ClassifierDummyChannel::GetHasNonEmptySandboxingFlag() { return false; }
+
+void ClassifierDummyChannel::SetHasNonEmptySandboxingFlag(
+ bool aHasNonEmptySandboxingFlag) {}
+
+NS_IMETHODIMP ClassifierDummyChannel::ComputeCrossOriginOpenerPolicy(
+ nsILoadInfo::CrossOriginOpenerPolicy aInitiatorPolicy,
+ nsILoadInfo::CrossOriginOpenerPolicy* aOutPolicy) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetCrossOriginOpenerPolicy(
+ nsILoadInfo::CrossOriginOpenerPolicy* aPolicy) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ClassifierDummyChannel::HasCrossOriginOpenerPolicyMismatch(
+ bool* aIsMismatch) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ClassifierDummyChannel::SetMatchedInfo(
+ const nsACString& aList, const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ClassifierDummyChannel::GetMatchedList(nsACString& aMatchedList) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ClassifierDummyChannel::GetMatchedProvider(
+ nsACString& aMatchedProvider) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ClassifierDummyChannel::GetMatchedFullHash(
+ nsACString& aMatchedFullHash) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ClassifierDummyChannel::SetMatchedTrackingInfo(
+ const nsTArray<nsCString>& aLists, const nsTArray<nsCString>& aFullHashes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ClassifierDummyChannel::GetMatchedTrackingLists(
+ nsTArray<nsCString>& aMatchedTrackingLists) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ClassifierDummyChannel::GetMatchedTrackingFullHashes(
+ nsTArray<nsCString>& aMatchedTrackingFullHashes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ClassifierDummyChannel::GetFirstPartyClassificationFlags(
+ uint32_t* aFirstPartyClassificationFlags) {
+ *aFirstPartyClassificationFlags = mFirstPartyClassificationFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ClassifierDummyChannel::GetThirdPartyClassificationFlags(
+ uint32_t* aThirdPartyClassificationFlags) {
+ *aThirdPartyClassificationFlags = mThirdPartyClassificationFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ClassifierDummyChannel::GetClassificationFlags(
+ uint32_t* aClassificationFlags) {
+ if (mThirdPartyClassificationFlags) {
+ *aClassificationFlags = mThirdPartyClassificationFlags;
+ } else {
+ *aClassificationFlags = mFirstPartyClassificationFlags;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP ClassifierDummyChannel::IsThirdPartyTrackingResource(
+ bool* aIsTrackingResource) {
+ MOZ_ASSERT(
+ !(mFirstPartyClassificationFlags && mThirdPartyClassificationFlags));
+ *aIsTrackingResource = UrlClassifierCommon::IsTrackingClassificationFlag(
+ mThirdPartyClassificationFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ClassifierDummyChannel::IsThirdPartySocialTrackingResource(
+ bool* aIsThirdPartySocialTrackingResource) {
+ MOZ_ASSERT(!mFirstPartyClassificationFlags ||
+ !mThirdPartyClassificationFlags);
+ *aIsThirdPartySocialTrackingResource =
+ UrlClassifierCommon::IsSocialTrackingClassificationFlag(
+ mThirdPartyClassificationFlags);
+ return NS_OK;
+}
+
+void ClassifierDummyChannel::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() {}
+
+NS_IMETHODIMP ClassifierDummyChannel::GetResponseEmbedderPolicy(
+ nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ClassifierDummyChannel::SetWaitForHTTPSSVCRecord() {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+ClassifierDummyChannel::GetSupportsHTTP3(bool* aSupportsHTTP3) {
+ *aSupportsHTTP3 = false;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ClassifierDummyChannel.h b/netwerk/protocol/http/ClassifierDummyChannel.h
new file mode 100644
index 0000000000..ed97cddf0f
--- /dev/null
+++ b/netwerk/protocol/http/ClassifierDummyChannel.h
@@ -0,0 +1,91 @@
+/* -*- 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_ClassifierDummyChannel_h
+#define mozilla_net_ClassifierDummyChannel_h
+
+#include "nsIChannel.h"
+#include "nsIClassifiedChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include <functional>
+
+#define CLASSIFIER_DUMMY_CHANNEL_IID \
+ { \
+ 0x70ceb97d, 0xbfa6, 0x4255, { \
+ 0xb7, 0x08, 0xe1, 0xb4, 0x4a, 0x1e, 0x0e, 0x9a \
+ } \
+ }
+
+namespace mozilla {
+namespace net {
+
+/**
+ * In child intercept mode, the decision to intercept a channel is made in the
+ * child process without consulting the parent process. The decision is based
+ * on whether there is a ServiceWorker with a scope covering the URL in question
+ * and whether storage is allowed for the origin/URL. When the
+ * "network.cookie.cookieBehavior" preference is set to BEHAVIOR_REJECT_TRACKER,
+ * annotated channels are denied storage which means that the ServiceWorker
+ * should not intercept the channel. However, the decision for tracking
+ * protection to annotate a channel only occurs in the parent process. The
+ * dummy channel is a hack to allow the intercept decision process to ask the
+ * parent process if the channel should be annotated. Because this round-trip
+ * to the parent has overhead, the dummy channel is only created 1) if the
+ * ServiceWorker initially determines that the channel should be intercepted and
+ * 2) it's a navigation request.
+ *
+ * This hack can be removed once Bug 1231208's new "parent intercept" mechanism
+ * fully lands, the pref is enabled by default it stays enabled for long enough
+ * to be confident we will never need/want to turn it off. Then as part of bug
+ * 1496997 we can remove this implementation. Bug 1498259 covers removing this
+ * hack in particular.
+ */
+class ClassifierDummyChannel final : public nsIChannel,
+ public nsIHttpChannelInternal,
+ public nsIClassifiedChannel {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(CLASSIFIER_DUMMY_CHANNEL_IID)
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIHTTPCHANNELINTERNAL
+ NS_DECL_NSICLASSIFIEDCHANNEL
+
+ enum StorageAllowedState {
+ eStorageGranted,
+ eStorageDenied,
+ eAsyncNeeded,
+ };
+
+ static StorageAllowedState StorageAllowed(
+ nsIChannel* aChannel, const std::function<void(bool)>& aCallback);
+
+ ClassifierDummyChannel(nsIURI* aURI, nsIURI* aTopWindowURI,
+ nsresult aTopWindowURIResult, nsILoadInfo* aLoadInfo);
+
+ void AddClassificationFlags(uint32_t aClassificationFlags, bool aThirdParty);
+
+ private:
+ ~ClassifierDummyChannel();
+
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mTopWindowURI;
+ nsresult mTopWindowURIResult;
+
+ uint32_t mFirstPartyClassificationFlags;
+ uint32_t mThirdPartyClassificationFlags;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(ClassifierDummyChannel,
+ CLASSIFIER_DUMMY_CHANNEL_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ClassifierDummyChannel_h
diff --git a/netwerk/protocol/http/ClassifierDummyChannelChild.cpp b/netwerk/protocol/http/ClassifierDummyChannelChild.cpp
new file mode 100644
index 0000000000..de96216b0e
--- /dev/null
+++ b/netwerk/protocol/http/ClassifierDummyChannelChild.cpp
@@ -0,0 +1,91 @@
+/* -*- 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 "ClassifierDummyChannelChild.h"
+#include "mozilla/ContentBlocking.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "nsIURI.h"
+
+namespace mozilla {
+namespace net {
+
+/* static */
+bool ClassifierDummyChannelChild::Create(
+ nsIHttpChannel* aChannel, nsIURI* aURI,
+ const std::function<void(bool)>& aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_ASSERT(aChannel);
+ MOZ_ASSERT(aURI);
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
+ do_QueryInterface(aChannel);
+ if (!httpChannelInternal) {
+ // Any non-http channel is allowed.
+ return true;
+ }
+
+ nsCOMPtr<nsIURI> topWindowURI;
+ nsresult topWindowURIResult =
+ httpChannelInternal->GetTopWindowURI(getter_AddRefs(topWindowURI));
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ Maybe<LoadInfoArgs> loadInfoArgs;
+ mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs);
+
+ PClassifierDummyChannelChild* actor =
+ gNeckoChild->SendPClassifierDummyChannelConstructor(
+ aURI, topWindowURI, topWindowURIResult, loadInfoArgs);
+ if (!actor) {
+ return false;
+ }
+
+ bool isThirdParty =
+ nsContentUtils::IsThirdPartyWindowOrChannel(nullptr, aChannel, aURI);
+
+ static_cast<ClassifierDummyChannelChild*>(actor)->Initialize(
+ aChannel, aURI, isThirdParty, aCallback);
+ return true;
+}
+
+ClassifierDummyChannelChild::ClassifierDummyChannelChild()
+ : mIsThirdParty(false) {}
+
+ClassifierDummyChannelChild::~ClassifierDummyChannelChild() = default;
+
+void ClassifierDummyChannelChild::Initialize(
+ nsIHttpChannel* aChannel, nsIURI* aURI, bool aIsThirdParty,
+ const std::function<void(bool)>& aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mChannel = aChannel;
+ mURI = aURI;
+ mIsThirdParty = aIsThirdParty;
+ mCallback = aCallback;
+}
+
+mozilla::ipc::IPCResult ClassifierDummyChannelChild::Recv__delete__(
+ const uint32_t& aClassificationFlags) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mChannel) {
+ return IPC_OK();
+ }
+
+ nsCOMPtr<nsIHttpChannel> channel = std::move(mChannel);
+
+ RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(channel);
+ httpChannel->AddClassificationFlags(aClassificationFlags, mIsThirdParty);
+
+ bool storageGranted =
+ ContentBlocking::ShouldAllowAccessFor(httpChannel, mURI, nullptr);
+ mCallback(storageGranted);
+ return IPC_OK();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ClassifierDummyChannelChild.h b/netwerk/protocol/http/ClassifierDummyChannelChild.h
new file mode 100644
index 0000000000..7af23d7ae8
--- /dev/null
+++ b/netwerk/protocol/http/ClassifierDummyChannelChild.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_ClassifierDummyChannelChild_h
+#define mozilla_net_ClassifierDummyChannelChild_h
+
+#include "mozilla/net/PClassifierDummyChannelChild.h"
+#include "nsCOMPtr.h"
+
+#include <functional>
+
+class nsIHttpChannel;
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+class ClassifierDummyChannelChild final : public PClassifierDummyChannelChild {
+ friend class PClassifierDummyChannelChild;
+
+ public:
+ static bool Create(nsIHttpChannel* aChannel, nsIURI* aURI,
+ const std::function<void(bool)>& aCallback);
+
+ // Used by PNeckoChild only!
+ ClassifierDummyChannelChild();
+ ~ClassifierDummyChannelChild();
+
+ private:
+ void Initialize(nsIHttpChannel* aChannel, nsIURI* aURI, bool aIsThirdParty,
+ const std::function<void(bool)>& aCallback);
+
+ mozilla::ipc::IPCResult Recv__delete__(const uint32_t& aClassificationFlags);
+
+ nsCOMPtr<nsIHttpChannel> mChannel;
+ nsCOMPtr<nsIURI> mURI;
+ std::function<void(bool)> mCallback;
+ bool mIsThirdParty;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ClassifierDummyChannelChild_h
diff --git a/netwerk/protocol/http/ClassifierDummyChannelParent.cpp b/netwerk/protocol/http/ClassifierDummyChannelParent.cpp
new file mode 100644
index 0000000000..043b739044
--- /dev/null
+++ b/netwerk/protocol/http/ClassifierDummyChannelParent.cpp
@@ -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/. */
+
+#include "ClassifierDummyChannelParent.h"
+#include "mozilla/net/AsyncUrlChannelClassifier.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Unused.h"
+#include "nsIPrincipal.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace net {
+
+ClassifierDummyChannelParent::ClassifierDummyChannelParent()
+ : mIPCActive(true) {}
+
+ClassifierDummyChannelParent::~ClassifierDummyChannelParent() = default;
+
+void ClassifierDummyChannelParent::Init(nsIURI* aURI, nsIURI* aTopWindowURI,
+ nsresult aTopWindowURIResult,
+ nsILoadInfo* aLoadInfo) {
+ MOZ_ASSERT(mIPCActive);
+
+ RefPtr<ClassifierDummyChannelParent> self = this;
+ auto onExit =
+ MakeScopeExit([self] { Unused << Send__delete__(self, false); });
+
+ if (!aURI) {
+ return;
+ }
+
+ RefPtr<ClassifierDummyChannel> channel = new ClassifierDummyChannel(
+ aURI, aTopWindowURI, aTopWindowURIResult, aLoadInfo);
+
+ bool willCallback = NS_SUCCEEDED(AsyncUrlChannelClassifier::CheckChannel(
+ channel, [self = std::move(self), channel]() {
+ if (self->mIPCActive) {
+ Unused << Send__delete__(self, channel->GetClassificationFlags());
+ }
+ }));
+
+ if (willCallback) {
+ onExit.release();
+ }
+}
+
+void ClassifierDummyChannelParent::ActorDestroy(ActorDestroyReason aWhy) {
+ mIPCActive = false;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ClassifierDummyChannelParent.h b/netwerk/protocol/http/ClassifierDummyChannelParent.h
new file mode 100644
index 0000000000..fe9397e4f7
--- /dev/null
+++ b/netwerk/protocol/http/ClassifierDummyChannelParent.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_ClassifierDummyChannelParent_h
+#define mozilla_net_ClassifierDummyChannelParent_h
+
+#include "mozilla/net/PClassifierDummyChannelParent.h"
+#include "nsISupportsImpl.h"
+
+class nsILoadInfo;
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+class ClassifierDummyChannelParent final
+ : public PClassifierDummyChannelParent {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(ClassifierDummyChannelParent)
+
+ ClassifierDummyChannelParent();
+
+ void Init(nsIURI* aURI, nsIURI* aTopWindowURI, nsresult aTopWindowURIResult,
+ nsILoadInfo* aLoadInfo);
+
+ private:
+ ~ClassifierDummyChannelParent();
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ bool mIPCActive;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_ClassifierDummyChannelParent_h
diff --git a/netwerk/protocol/http/ConnectionDiagnostics.cpp b/netwerk/protocol/http/ConnectionDiagnostics.cpp
new file mode 100644
index 0000000000..7dc8550b0b
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionDiagnostics.cpp
@@ -0,0 +1,236 @@
+/* -*- 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"
+
+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",
+ gHttpHandler->IsSpdyEnabled());
+ mLogData.AppendPrintf("MaxSocketCount() = %d\n",
+ gHttpHandler->MaxSocketCount());
+ mLogData.AppendPrintf("mNumActiveConns = %d\n", mNumActiveConns);
+ mLogData.AppendPrintf("mNumIdleConns = %d\n", mNumIdleConns);
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ConnectionEntry> ent = iter.Data();
+
+ 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(" Half Opens Length = %zu\n", mHalfOpens.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 < mHalfOpens.Length(); ++i) {
+ log.AppendPrintf(" :: Half Open #%u\n", i);
+ mHalfOpens[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 (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) {
+ log.AppendPrintf(
+ " :: Pending Transactions with Window ID = %" PRIu64 "\n", it.Key());
+ for (uint32_t j = 0; j < it.UserData()->Length(); ++j) {
+ log.AppendPrintf(" ::: Pending Transaction #%u\n", i);
+ it.UserData()->ElementAt(j)->PrintDiagnostics(log);
+ ++i;
+ }
+ }
+}
+
+void HalfOpenSocket::PrintDiagnostics(nsCString& log) {
+ log.AppendPrintf(" has connected = %d, isSpeculative = %d\n",
+ HasConnected(), IsSpeculative());
+
+ TimeStamp now = TimeStamp::Now();
+
+ if (mPrimarySynStarted.IsNull())
+ log.AppendPrintf(" primary not started\n");
+ else
+ log.AppendPrintf(" primary started %.2fms ago\n",
+ (now - mPrimarySynStarted).ToMilliseconds());
+
+ if (mBackupSynStarted.IsNull())
+ log.AppendPrintf(" backup not started\n");
+ else
+ log.AppendPrintf(" backup started %.2f ago\n",
+ (now - mBackupSynStarted).ToMilliseconds());
+
+ log.AppendPrintf(" primary transport %d, backup transport %d\n",
+ !!mSocketTransport.get(), !!mBackupTransport.get());
+}
+
+void nsHttpConnection::PrintDiagnostics(nsCString& log) {
+ log.AppendPrintf(" CanDirectlyActivate = %d\n", CanDirectlyActivate());
+
+ log.AppendPrintf(" npncomplete = %d setupSSLCalled = %d\n", mNPNComplete,
+ mSetupSSLCalled);
+
+ 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.get(), !!mSpdySession.get());
+
+ 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);
+
+ PRIntervalTime now = PR_IntervalNow();
+ log.AppendPrintf(" time since last read = %ums\n",
+ PR_IntervalToMilliseconds(now - mLastReadTime));
+
+ log.AppendPrintf(" read/written %" PRId64 "/%" PRId64 "\n",
+ mTotalBytesRead, mTotalBytesWritten);
+
+ 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.GetSize());
+
+ 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", 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<HalfOpenSocket> halfOpen = do_QueryReferent(mHalfOpen);
+ log.AppendPrintf(" Waiting for half open sock: %p or connection: %p\n",
+ halfOpen.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..b7651fb169
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionEntry.cpp
@@ -0,0 +1,962 @@
+/* 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"
+
+namespace mozilla {
+namespace net {
+
+// ConnectionEntry
+ConnectionEntry::~ConnectionEntry() {
+ LOG(("ConnectionEntry::~ConnectionEntry this=%p", this));
+
+ MOZ_ASSERT(!mIdleConns.Length());
+ MOZ_ASSERT(!mActiveConns.Length());
+ MOZ_ASSERT(!mHalfOpens.Length());
+ MOZ_ASSERT(!PendingQueueLength());
+ MOZ_ASSERT(!UrgentStartQueueLength());
+ MOZ_ASSERT(!mHalfOpenFastOpenBackups.Length());
+ MOZ_ASSERT(!mDoNotDestroy);
+}
+
+ConnectionEntry::ConnectionEntry(nsHttpConnectionInfo* ci)
+ : mConnInfo(ci),
+ mUsingSpdy(false),
+ mCanUseSpdy(true),
+ mPreferIPv4(false),
+ mPreferIPv6(false),
+ mUsedForConnection(false),
+ mDoNotDestroy(false) {
+ if (mConnInfo->FirstHopSSL() && !mConnInfo->IsHttp3()) {
+ mUseFastOpen = gHttpHandler->UseFastOpen();
+ } else {
+ // Only allow the TCP fast open on a secure connection.
+ mUseFastOpen = 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)
+ ? true
+ : false;
+}
+
+uint32_t ConnectionEntry::UnconnectedHalfOpens() const {
+ uint32_t unconnectedHalfOpens = 0;
+ for (uint32_t i = 0; i < mHalfOpens.Length(); ++i) {
+ if (!mHalfOpens[i]->HasConnected()) {
+ ++unconnectedHalfOpens;
+ }
+ }
+ return unconnectedHalfOpens;
+}
+
+bool ConnectionEntry::RemoveHalfOpen(HalfOpenSocket* halfOpen) {
+ bool isPrimary = false;
+ // A failure to create the transport object at all
+ // will result in it not being present in the halfopen table. That's expected.
+ if (mHalfOpens.RemoveElement(halfOpen)) {
+ isPrimary = true;
+ if (halfOpen->IsSpeculative()) {
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_UNUSED_SPECULATIVE_CONN>
+ unusedSpeculativeConn;
+ ++unusedSpeculativeConn;
+
+ if (halfOpen->IsFromPredictor()) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_UNUSED>
+ totalPreconnectsUnused;
+ ++totalPreconnectsUnused;
+ }
+ }
+
+ gHttpHandler->ConnMgr()->DecreaseNumHalfOpenConns();
+
+ } else {
+ mHalfOpenFastOpenBackups.RemoveElement(halfOpen);
+ }
+
+ if (!UnconnectedHalfOpens()) {
+ // 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::RemoveHalfOpen\n"
+ " failed to process pending queue\n"));
+ }
+ }
+
+ return isPrimary;
+}
+
+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();
+}
+
+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() && gHttpHandler->IsSpdyEnabled() &&
+ mUsingSpdy &&
+ (mHalfOpens.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 (UnconnectedHalfOpens()) {
+ 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 half-open'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() + UnconnectedHalfOpens();
+}
+
+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++;
+ }
+}
+
+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;
+}
+
+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();
+ }
+
+ for (int32_t index = mHalfOpenFastOpenBackups.Length() - 1; index >= 0;
+ --index) {
+ RefPtr<HalfOpenSocket> half = mHalfOpenFastOpenBackups[index];
+ half->CancelFastOpenConnection();
+ }
+}
+
+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()) {
+ // Iterate over all active connections and check them.
+ for (uint32_t index = 0; index < mActiveConns.Length(); ++index) {
+ RefPtr<nsHttpConnection> conn = do_QueryObject(mActiveConns[index]);
+ if (conn) {
+ conn->CheckForTraffic(true);
+ }
+ }
+ // 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();
+}
+
+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->DontReuse();
+ }
+ }
+
+ // Cancel any other pending connections - their associated transactions
+ // are in the pending queue and will be dispatched onto this new connection
+ for (int32_t index = HalfOpensLength() - 1; index >= 0; --index) {
+ RefPtr<HalfOpenSocket> half = mHalfOpens[index];
+ LOG((
+ "ConnectionEntry::MakeAllDontReuseExcept forcing halfopen abandon %p\n",
+ half.get()));
+ mHalfOpens[index]->Abandon();
+ }
+
+ for (int32_t index = mHalfOpenFastOpenBackups.Length() - 1; index >= 0;
+ --index) {
+ LOG(
+ ("ConnectionEntry::MakeAllDontReuseExcept shutting down connection in "
+ "fast "
+ "open state (%p) because new spdy connection (%p) takes "
+ "precedence\n",
+ mHalfOpenFastOpenBackups[index].get(), conn));
+ RefPtr<HalfOpenSocket> half = mHalfOpenFastOpenBackups[index];
+ half->CancelFastOpenConnection();
+ }
+}
+
+bool ConnectionEntry::FindConnToClaim(
+ PendingTransactionInfo* pendingTransInfo) {
+ nsHttpTransaction* trans = pendingTransInfo->Transaction();
+
+ uint32_t halfOpenLength = HalfOpensLength();
+ for (uint32_t i = 0; i < halfOpenLength; i++) {
+ auto halfOpen = mHalfOpens[i];
+ if (halfOpen->AcceptsTransaction(trans) &&
+ pendingTransInfo->TryClaimingHalfOpen(halfOpen)) {
+ // We've found a speculative connection or a connection that
+ // is free to be used in the half open 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 half open connection\n",
+ mConnInfo->HashKey().get()));
+
+ // return OK because we have essentially opened a new connection
+ // by converting a speculative half-open 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();
+ }
+ }
+ for (int32_t index = mHalfOpenFastOpenBackups.Length() - 1; index >= 0;
+ --index) {
+ LOG(
+ ("GetH2orH3ActiveConn() shutting down connection in fast "
+ "open state (%p) because we have an experienced spdy "
+ "connection (%p).\n",
+ mHalfOpenFastOpenBackups[index].get(), experienced));
+ RefPtr<HalfOpenSocket> half = mHalfOpenFastOpenBackups[index];
+ half->CancelFastOpenConnection();
+ }
+
+ 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::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"
+ " half-len=%zu pending=%zu"
+ " urgentStart pending=%zu\n",
+ this, mConnInfo->Origin(), IdleConnectionsLength(), ActiveConnsLength(),
+ mHalfOpens.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 half open sockets.
+ if (mHalfOpens.Length()) {
+ TimeStamp currentTime = TimeStamp::Now();
+ double maxConnectTime_ms = gHttpHandler->ConnectTimeout();
+
+ for (uint32_t index = mHalfOpens.Length(); index > 0;) {
+ index--;
+
+ HalfOpenSocket* half = mHalfOpens[index];
+ double delta = half->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 half open to %s after %.2fms.\n",
+ mConnInfo->HashKey().get(), delta));
+ if (half->SocketTransport()) {
+ half->SocketTransport()->Close(NS_ERROR_NET_TIMEOUT);
+ }
+ if (half->BackupTransport()) {
+ half->BackupTransport()->Close(NS_ERROR_NET_TIMEOUT);
+ }
+ }
+
+ // If this half open hangs around for 5 seconds after we've
+ // closed() it then just abandon the socket.
+ if (delta > maxConnectTime_ms + 5000) {
+ LOG(("Abandon half open to %s after %.2fms.\n",
+ mConnInfo->HashKey().get(), delta));
+ half->Abandon();
+ }
+ }
+ }
+ if (mHalfOpens.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;
+ }
+ }
+}
+
+void ConnectionEntry::InsertIntoHalfOpens(HalfOpenSocket* sock) {
+ mHalfOpens.AppendElement(sock);
+ gHttpHandler->ConnMgr()->IncreaseNumHalfOpenConns();
+}
+
+void ConnectionEntry::InsertIntoHalfOpenFastOpenBackups(HalfOpenSocket* sock) {
+ mHalfOpenFastOpenBackups.AppendElement(sock);
+}
+
+void ConnectionEntry::CloseAllHalfOpens() {
+ for (int32_t i = int32_t(HalfOpensLength()) - 1; i >= 0; i--) {
+ mHalfOpens[i]->Abandon();
+ }
+}
+
+void ConnectionEntry::RemoveHalfOpenFastOpenBackups(HalfOpenSocket* sock) {
+ mHalfOpenFastOpenBackups.RemoveElement(sock);
+}
+
+bool ConnectionEntry::IsInHalfOpens(HalfOpenSocket* sock) {
+ return mHalfOpens.Contains(sock);
+}
+
+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 < mHalfOpens.Length(); i++) {
+ HalfOpenSockets hSocket;
+ hSocket.speculative = mHalfOpens[i]->IsSpeculative();
+ data.halfOpens.AppendElement(hSocket);
+ }
+ 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 half-open sockets 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 half-open sockets belonging to the given transaction.
+ pendingTransInfo->AbandonHalfOpenAndForgetActiveConn();
+ 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 half opens and idle
+ // connections. This is to make sure the new echConfig will be used for the
+ // next connection.
+ CloseAllHalfOpens();
+ CloseIdleConnections();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ConnectionEntry.h b/netwerk/protocol/http/ConnectionEntry.h
new file mode 100644
index 0000000000..b654889af7
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionEntry.h
@@ -0,0 +1,210 @@
+/* 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 "HalfOpenSocket.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);
+ 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);
+ void MakeAllDontReuseExcept(HttpConnectionBase* conn);
+ bool FindConnToClaim(PendingTransactionInfo* pendingTransInfo);
+ void CloseActiveConnections();
+ void CloseAllActiveConnsWithNullTransactcion(nsresult aCloseCode);
+
+ 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 HalfOpensLength() const { return mHalfOpens.Length(); }
+ size_t HalfOpenFastOpenBackupsLength() const {
+ return mHalfOpenFastOpenBackups.Length();
+ }
+ void InsertIntoHalfOpens(HalfOpenSocket* sock);
+ void InsertIntoHalfOpenFastOpenBackups(HalfOpenSocket* sock);
+ void CloseAllHalfOpens();
+ void RemoveHalfOpenFastOpenBackups(HalfOpenSocket* sock);
+ bool IsInHalfOpens(HalfOpenSocket* sock);
+
+ HttpRetParams GetConnectionData();
+ void LogConnections();
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+
+ bool AvailableForDispatchNow();
+
+ // calculate the number of half open sockets that have not had at least 1
+ // connection complete
+ uint32_t UnconnectedHalfOpens() const;
+
+ // Remove a particular half open socket from the mHalfOpens array
+ bool RemoveHalfOpen(HalfOpenSocket*);
+
+ // 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;
+
+ // Try using TCP Fast Open.
+ bool mUseFastOpen : 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);
+
+ 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
+
+ nsTArray<HalfOpenSocket*> mHalfOpens; // half open connections
+ nsTArray<RefPtr<HalfOpenSocket>>
+ mHalfOpenFastOpenBackups; // backup half open connections for
+ // connection in fast open phase
+
+ PendingTransactionQueue mPendingQ;
+ ~ConnectionEntry();
+};
+
+} // 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..aa231e0b3e
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionHandle.cpp
@@ -0,0 +1,87 @@
+/* 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"
+
+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\n"));
+ }
+ }
+}
+
+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);
+}
+
+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::TopLevelOuterContentWindowIdChanged(uint64_t windowId) {
+ // Do nothing.
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/ConnectionHandle.h b/netwerk/protocol/http/ConnectionHandle.h
new file mode 100644
index 0000000000..098c0adc17
--- /dev/null
+++ b/netwerk/protocol/http/ConnectionHandle.h
@@ -0,0 +1,38 @@
+/* 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__
+
+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/DelayHttpChannelQueue.cpp b/netwerk/protocol/http/DelayHttpChannelQueue.cpp
new file mode 100644
index 0000000000..03500ef943
--- /dev/null
+++ b/netwerk/protocol/http/DelayHttpChannelQueue.cpp
@@ -0,0 +1,122 @@
+/* -*- 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/. */
+
+#include "DelayHttpChannelQueue.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsIObserverService.h"
+#include "nsHttpChannel.h"
+#include "nsThreadManager.h"
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+namespace {
+StaticRefPtr<DelayHttpChannelQueue> sDelayHttpChannelQueue;
+}
+
+bool DelayHttpChannelQueue::AttemptQueueChannel(nsHttpChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!TimeStamp::GetFuzzyfoxEnabled()) {
+ return false;
+ }
+
+ if (!sDelayHttpChannelQueue) {
+ RefPtr<DelayHttpChannelQueue> queue = new DelayHttpChannelQueue();
+ if (!queue->Initialize()) {
+ return false;
+ }
+
+ sDelayHttpChannelQueue = queue;
+ }
+
+ if (NS_WARN_IF(
+ !sDelayHttpChannelQueue->mQueue.AppendElement(aChannel, fallible))) {
+ return false;
+ }
+
+ return true;
+}
+
+DelayHttpChannelQueue::DelayHttpChannelQueue() {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+DelayHttpChannelQueue::~DelayHttpChannelQueue() {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+bool DelayHttpChannelQueue::Initialize() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return false;
+ }
+
+ nsresult rv = obs->AddObserver(this, "fuzzyfox-fire-outbound", false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ rv = obs->AddObserver(this, "xpcom-shutdown", false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+DelayHttpChannelQueue::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!strcmp(aTopic, "fuzzyfox-fire-outbound")) {
+ FireQueue();
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"));
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (NS_WARN_IF(!obs)) {
+ return NS_OK;
+ }
+
+ obs->RemoveObserver(this, "fuzzyfox-fire-outbound");
+ obs->RemoveObserver(this, "xpcom-shutdown");
+
+ return NS_OK;
+}
+
+void DelayHttpChannelQueue::FireQueue() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mQueue.IsEmpty()) {
+ return;
+ }
+
+ // TODO: get this from the DOM clock?
+ TimeStamp ts = TimeStamp::Now();
+
+ FallibleTArray<RefPtr<nsHttpChannel>> queue = std::move(mQueue);
+
+ for (RefPtr<nsHttpChannel>& channel : queue) {
+ channel->AsyncOpenFinal(ts);
+ }
+}
+
+NS_INTERFACE_MAP_BEGIN(DelayHttpChannelQueue)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(DelayHttpChannelQueue)
+NS_IMPL_RELEASE(DelayHttpChannelQueue)
diff --git a/netwerk/protocol/http/DelayHttpChannelQueue.h b/netwerk/protocol/http/DelayHttpChannelQueue.h
new file mode 100644
index 0000000000..491160734d
--- /dev/null
+++ b/netwerk/protocol/http/DelayHttpChannelQueue.h
@@ -0,0 +1,46 @@
+/* -*- 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 mozilla_net_DelayHttpChannelQueue_h
+#define mozilla_net_DelayHttpChannelQueue_h
+
+#include "nsIObserver.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace net {
+
+class nsHttpChannel;
+
+/**
+ * DelayHttpChannelQueue stores a set of nsHttpChannels that
+ * are ready to fire out onto the network. However, with FuzzyFox,
+ * we can only fire those events at a specific interval, so we
+ * delay them here, in an instance of this class, until we observe
+ * the topic notificaion that we can send them outbound.
+ */
+class DelayHttpChannelQueue final : public nsIObserver {
+ public:
+ static bool AttemptQueueChannel(nsHttpChannel* aChannel);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ private:
+ DelayHttpChannelQueue();
+ ~DelayHttpChannelQueue();
+
+ bool Initialize();
+
+ void FireQueue();
+
+ FallibleTArray<RefPtr<nsHttpChannel>> mQueue;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_DelayHttpChannelQueue_h
diff --git a/netwerk/protocol/http/HTTPSRecordResolver.cpp b/netwerk/protocol/http/HTTPSRecordResolver.cpp
new file mode 100644
index 0000000000..d83212eca6
--- /dev/null
+++ b/netwerk/protocol/http/HTTPSRecordResolver.cpp
@@ -0,0 +1,99 @@
+/* -*- 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 "nsIDNSService.h"
+#include "nsHttpConnectionInfo.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;
+ }
+
+ uint32_t flags = nsIDNSService::GetFlagsFromTRRMode(mConnInfo->GetTRRMode());
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ flags |= nsIDNSService::RESOLVE_BYPASS_CACHE;
+ }
+
+ return dns->AsyncResolveNative(
+ mConnInfo->GetOrigin(), nsIDNSService::RESOLVE_TYPE_HTTPSSVC, flags,
+ nullptr, 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;
+ }
+
+ 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) {
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ if (!dns) {
+ return;
+ }
+
+ uint32_t 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,
+ GetCurrentEventTarget(),
+ mTransaction->ConnectionInfo()->GetOriginAttributes(),
+ getter_AddRefs(tmpOutstanding));
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HTTPSRecordResolver.h b/netwerk/protocol/http/HTTPSRecordResolver.h
new file mode 100644
index 0000000000..15eb997634
--- /dev/null
+++ b/netwerk/protocol/http/HTTPSRecordResolver.h
@@ -0,0 +1,41 @@
+/* -*- 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"
+
+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);
+
+ 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/HalfOpenSocket.cpp b/netwerk/protocol/http/HalfOpenSocket.cpp
new file mode 100644
index 0000000000..815f2f41c4
--- /dev/null
+++ b/netwerk/protocol/http/HalfOpenSocket.cpp
@@ -0,0 +1,1307 @@
+/* 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 "HalfOpenSocket.h"
+#include "TCPFastOpenLayer.h"
+#include "nsHttpConnection.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsQueryObject.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 {
+
+//////////////////////// HalfOpenSocket
+NS_IMPL_ADDREF(HalfOpenSocket)
+NS_IMPL_RELEASE(HalfOpenSocket)
+
+NS_INTERFACE_MAP_BEGIN(HalfOpenSocket)
+ 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_CONCRETE(HalfOpenSocket)
+NS_INTERFACE_MAP_END
+
+HalfOpenSocket::HalfOpenSocket(ConnectionEntry* ent, nsAHttpTransaction* trans,
+ uint32_t caps, bool speculative,
+ bool isFromPredictor, bool urgentStart)
+ : mTransaction(trans),
+ mDispatchedMTransaction(false),
+ mCaps(caps),
+ mSpeculative(speculative),
+ mUrgentStart(urgentStart),
+ mIsFromPredictor(isFromPredictor),
+ mAllow1918(true),
+ mHasConnected(false),
+ mPrimaryConnectedOK(false),
+ mBackupConnectedOK(false),
+ mBackupConnStatsSet(false),
+ mFreeToUse(true),
+ mPrimaryStreamStatus(NS_OK),
+ mFastOpenInProgress(false),
+ mEnt(ent) {
+ MOZ_ASSERT(ent && trans, "constructor with null arguments");
+ LOG(("Creating HalfOpenSocket [this=%p trans=%p ent=%s key=%s]\n", this,
+ trans, ent->mConnInfo->Origin(), ent->mConnInfo->HashKey().get()));
+
+ mIsHttp3 = mEnt->mConnInfo->IsHttp3();
+ if (speculative) {
+ Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_TOTAL_SPECULATIVE_CONN>
+ totalSpeculativeConn;
+ ++totalSpeculativeConn;
+
+ if (isFromPredictor) {
+ Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS_CREATED>
+ totalPreconnectsCreated;
+ ++totalPreconnectsCreated;
+ }
+ }
+
+ if (mEnt->mConnInfo->FirstHopSSL()) {
+ mFastOpenStatus = TFO_UNKNOWN;
+ } else {
+ mFastOpenStatus = TFO_HTTP;
+ }
+ MOZ_ASSERT(mEnt);
+}
+
+HalfOpenSocket::~HalfOpenSocket() {
+ MOZ_ASSERT(!mStreamOut);
+ MOZ_ASSERT(!mBackupStreamOut);
+ LOG(("Destroying HalfOpenSocket [this=%p]\n", this));
+
+ if (mEnt) {
+ bool inqueue = mEnt->RemoveHalfOpen(this);
+ LOG(("Destroying HalfOpenSocket was in the HalfOpenList=%d [this=%p]\n",
+ inqueue, this));
+ }
+}
+
+nsresult HalfOpenSocket::SetupStreams(nsISocketTransport** transport,
+ nsIAsyncInputStream** instream,
+ nsIAsyncOutputStream** outstream,
+ bool isBackup) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MOZ_ASSERT(mEnt);
+ nsresult rv;
+ nsTArray<nsCString> socketTypes;
+ const nsHttpConnectionInfo* ci = mEnt->mConnInfo;
+ if (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 = services::GetSocketTransportService();
+ if (!sts) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ LOG(
+ ("HalfOpenSocket::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(), 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(
+ ("HalfOpenSocket 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(), getter_AddRefs(socketTransport));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t tmpFlags = 0;
+ if (mCaps & NS_HTTP_REFRESH_DNS) {
+ tmpFlags = nsISocketTransport::BYPASS_CACHE;
+ }
+
+ tmpFlags |= nsISocketTransport::GetFlagsFromTRRMode(
+ NS_HTTP_TRR_MODE_FROM_FLAGS(mCaps));
+
+ if (mCaps & NS_HTTP_LOAD_ANONYMOUS) {
+ tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
+ }
+
+ if (ci->GetPrivate() || ci->GetIsolated()) {
+ tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
+ }
+
+ Unused << socketTransport->SetIsPrivate(ci->GetPrivate());
+
+ if (ci->GetLessThanTls13()) {
+ tmpFlags |= nsISocketTransport::DONT_TRY_ECH;
+ }
+
+ if (((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);
+ 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(ci->GetRoutedHost(), nsIDNSService::RESOLVE_OFFLINE,
+ mEnt->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 (mCaps & NS_HTTP_DISABLE_IPV4) {
+ tmpFlags |= nsISocketTransport::DISABLE_IPV4;
+ } else if (mCaps & NS_HTTP_DISABLE_IPV6) {
+ tmpFlags |= nsISocketTransport::DISABLE_IPV6;
+ } else if (mEnt->PreferenceKnown()) {
+ if (mEnt->mPreferIPv6) {
+ tmpFlags |= nsISocketTransport::DISABLE_IPV4;
+ } else if (mEnt->mPreferIPv4) {
+ tmpFlags |= nsISocketTransport::DISABLE_IPV6;
+ }
+
+ // In case the host is no longer accessible via the preferred IP family,
+ // try the opposite one and potentially restate the preference.
+ tmpFlags |= nsISocketTransport::RETRY_WITH_DIFFERENT_IP_FAMILY;
+
+ // From the same reason, let the backup socket fail faster to try the other
+ // family.
+ uint16_t fallbackTimeout =
+ isBackup ? gHttpHandler->GetFallbackSynTimeout() : 0;
+ if (fallbackTimeout) {
+ socketTransport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT,
+ fallbackTimeout);
+ }
+ } else if (isBackup && 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".
+ tmpFlags |= nsISocketTransport::DISABLE_IPV6;
+ }
+
+ if (!Allow1918()) {
+ tmpFlags |= nsISocketTransport::DISABLE_RFC1918;
+ }
+
+ if ((mFastOpenStatus != TFO_HTTP) && !isBackup) {
+ if (mEnt->mUseFastOpen) {
+ socketTransport->SetFastOpenCallback(this);
+ } else {
+ mFastOpenStatus = TFO_DISABLED;
+ }
+ }
+
+ MOZ_ASSERT(!(tmpFlags & nsISocketTransport::DISABLE_IPV4) ||
+ !(tmpFlags & nsISocketTransport::DISABLE_IPV6),
+ "Both types should not be disabled at the same time.");
+ socketTransport->SetConnectionFlags(tmpFlags);
+ socketTransport->SetTlsFlags(ci->GetTlsFlags());
+
+ const OriginAttributes& originAttributes =
+ mEnt->mConnInfo->GetOriginAttributes();
+ if (originAttributes != OriginAttributes()) {
+ socketTransport->SetOriginAttributes(originAttributes);
+ }
+
+ socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
+
+ rv = socketTransport->SetEventSink(this, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = socketTransport->SetSecurityCallbacks(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (gHttpHandler->EchConfigEnabled()) {
+ rv = socketTransport->SetEchConfig(ci->GetEchConfig());
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ Telemetry::Accumulate(Telemetry::HTTP_CONNECTION_ENTRY_CACHE_HIT_1,
+ mEnt->mUsedForConnection);
+ mEnt->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);
+
+ socketTransport.forget(transport);
+ CallQueryInterface(sin, instream);
+ CallQueryInterface(sout, outstream);
+
+ rv = (*outstream)->AsyncWait(this, 0, 0, nullptr);
+ if (NS_SUCCEEDED(rv)) {
+ gHttpHandler->ConnMgr()->StartedConnect();
+ }
+
+ return rv;
+}
+
+nsresult HalfOpenSocket::SetupPrimaryStreams() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv;
+
+ mPrimarySynStarted = TimeStamp::Now();
+ rv = SetupStreams(getter_AddRefs(mSocketTransport), getter_AddRefs(mStreamIn),
+ getter_AddRefs(mStreamOut), false);
+
+ LOG(("HalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%" PRIx32 "]",
+ this, mEnt->mConnInfo->Origin(), static_cast<uint32_t>(rv)));
+ if (NS_FAILED(rv)) {
+ if (mStreamOut) {
+ mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+ if (mSocketTransport) {
+ mSocketTransport->SetFastOpenCallback(nullptr);
+ }
+ mStreamOut = nullptr;
+ mStreamIn = nullptr;
+ mSocketTransport = nullptr;
+ }
+ return rv;
+}
+
+nsresult HalfOpenSocket::SetupBackupStreams() {
+ MOZ_ASSERT(mTransaction);
+
+ mBackupSynStarted = TimeStamp::Now();
+ nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport),
+ getter_AddRefs(mBackupStreamIn),
+ getter_AddRefs(mBackupStreamOut), true);
+
+ LOG(("HalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%" PRIx32 "]",
+ this, mEnt->mConnInfo->Origin(), static_cast<uint32_t>(rv)));
+ if (NS_FAILED(rv)) {
+ if (mBackupStreamOut) {
+ mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+ mBackupStreamOut = nullptr;
+ mBackupStreamIn = nullptr;
+ mBackupTransport = nullptr;
+ }
+ return rv;
+}
+
+void HalfOpenSocket::SetupBackupTimer() {
+ MOZ_ASSERT(mEnt);
+ uint16_t timeout = gHttpHandler->GetIdleSynTimeout();
+ MOZ_ASSERT(!mSynTimer, "timer already initd");
+ if (!timeout && mFastOpenInProgress) {
+ timeout = 250;
+ }
+ // 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 (mFastOpenInProgress || (timeout && !mSpeculative)) {
+ // 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(("HalfOpenSocket::SetupBackupTimer() [this=%p]", this));
+ } else if (timeout) {
+ LOG(("HalfOpenSocket::SetupBackupTimer() [this=%p], did not arm\n", this));
+ }
+}
+
+void HalfOpenSocket::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(("HalfOpenSocket::CancelBackupTimer()"));
+ mSynTimer->Cancel();
+
+ // Keeping the reference to the timer to remember we have already
+ // performed the backup connection.
+}
+
+void HalfOpenSocket::Abandon() {
+ LOG(("HalfOpenSocket::Abandon [this=%p ent=%s] %p %p %p %p", this,
+ mEnt->mConnInfo->Origin(), mSocketTransport.get(),
+ mBackupTransport.get(), mStreamOut.get(), mBackupStreamOut.get()));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ RefPtr<HalfOpenSocket> deleteProtector(this);
+
+ // Tell socket (and backup socket) to forget the half open socket.
+ if (mSocketTransport) {
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mSocketTransport->SetFastOpenCallback(nullptr);
+ mSocketTransport = nullptr;
+ }
+ if (mBackupTransport) {
+ mBackupTransport->SetEventSink(nullptr, nullptr);
+ mBackupTransport->SetSecurityCallbacks(nullptr);
+ mBackupTransport = nullptr;
+ }
+
+ // Tell output stream (and backup) to forget the half open socket.
+ if (mStreamOut) {
+ if (!mFastOpenInProgress) {
+ // If mFastOpenInProgress is true HalfOpen are not in mHalfOpen
+ // list and are not counted so we do not need to decrease counter.
+ gHttpHandler->ConnMgr()->RecvdConnect();
+ }
+ mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mStreamOut = nullptr;
+ }
+ if (mBackupStreamOut) {
+ gHttpHandler->ConnMgr()->RecvdConnect();
+ mBackupStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mBackupStreamOut = nullptr;
+ }
+
+ // Lose references to input stream (and backup).
+ if (mStreamIn) {
+ mStreamIn->AsyncWait(nullptr, 0, 0, nullptr);
+ mStreamIn = nullptr;
+ }
+ if (mBackupStreamIn) {
+ mBackupStreamIn->AsyncWait(nullptr, 0, 0, nullptr);
+ mBackupStreamIn = nullptr;
+ }
+
+ // Stop the timer - we don't want any new backups.
+ CancelBackupTimer();
+
+ // Remove the half open from the connection entry.
+ if (mEnt) {
+ mEnt->mDoNotDestroy = false;
+ mEnt->RemoveHalfOpen(this);
+ }
+ mEnt = nullptr;
+}
+
+double HalfOpenSocket::Duration(TimeStamp epoch) {
+ if (mPrimarySynStarted.IsNull()) {
+ return 0;
+ }
+
+ return (epoch - mPrimarySynStarted).ToMilliseconds();
+}
+
+NS_IMETHODIMP // method for nsITimerCallback
+HalfOpenSocket::Notify(nsITimer* timer) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(timer == mSynTimer, "wrong timer");
+
+ MOZ_ASSERT(!mBackupTransport);
+ MOZ_ASSERT(mSynTimer);
+ MOZ_ASSERT(mEnt);
+
+ DebugOnly<nsresult> rv = SetupBackupStreams();
+ 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
+HalfOpenSocket::GetName(nsACString& aName) {
+ aName.AssignLiteral("HalfOpenSocket");
+ return NS_OK;
+}
+
+// method for nsIAsyncOutputStreamCallback
+NS_IMETHODIMP
+HalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream* out) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mStreamOut || mBackupStreamOut);
+ MOZ_ASSERT(out == mStreamOut || out == mBackupStreamOut, "stream mismatch");
+ MOZ_ASSERT(mEnt);
+
+ LOG(("HalfOpenSocket::OnOutputStreamReady [this=%p ent=%s %s]\n", this,
+ mEnt->mConnInfo->Origin(), out == mStreamOut ? "primary" : "backup"));
+
+ mEnt->mDoNotDestroy = true;
+ gHttpHandler->ConnMgr()->RecvdConnect();
+
+ CancelBackupTimer();
+
+ if (mFastOpenInProgress) {
+ LOG(
+ ("HalfOpenSocket::OnOutputStreamReady backup stream is ready, "
+ "close the fast open socket %p [this=%p ent=%s]\n",
+ mSocketTransport.get(), this, mEnt->mConnInfo->Origin()));
+ // If fast open is used, right after a socket for the primary stream is
+ // created a HttpConnectionBase is created for that socket. The connection
+ // listens for OnOutputStreamReady not HalfOpenSocket. So this stream
+ // cannot be mStreamOut.
+ MOZ_ASSERT((out == mBackupStreamOut) && mConnectionNegotiatingFastOpen);
+ // Here the backup, non-TFO connection has connected successfully,
+ // before the TFO connection.
+ //
+ // The primary, TFO connection will be cancelled and the transaction
+ // will be rewind. CloseConnectionFastOpenTakesTooLongOrError will
+ // return the rewind transaction. The transaction will be put back to
+ // the pending queue and as well connected to this halfOpenSocket.
+ // SetupConn should set up a new HttpConnectionBase with the backup
+ // socketTransport and the rewind transaction.
+ mSocketTransport->SetFastOpenCallback(nullptr);
+ mConnectionNegotiatingFastOpen->SetFastOpen(false);
+ mEnt->RemoveHalfOpenFastOpenBackups(this);
+ RefPtr<nsAHttpTransaction> trans =
+ mConnectionNegotiatingFastOpen
+ ->CloseConnectionFastOpenTakesTooLongOrError(true);
+ mSocketTransport = nullptr;
+ mStreamOut = nullptr;
+ mStreamIn = nullptr;
+
+ if (trans && trans->QueryHttpTransaction()) {
+ RefPtr<PendingTransactionInfo> pendingTransInfo =
+ new PendingTransactionInfo(trans->QueryHttpTransaction());
+ pendingTransInfo->AddHalfOpen(this);
+ mEnt->InsertTransaction(pendingTransInfo, true);
+ }
+ if (mEnt->mUseFastOpen) {
+ gHttpHandler->IncrementFastOpenConsecutiveFailureCounter();
+ mEnt->mUseFastOpen = false;
+ }
+
+ mFastOpenInProgress = false;
+ mConnectionNegotiatingFastOpen = nullptr;
+ if (mFastOpenStatus == TFO_NOT_TRIED) {
+ mFastOpenStatus = TFO_FAILED_BACKUP_CONNECTION_TFO_NOT_TRIED;
+ } else if (mFastOpenStatus == TFO_TRIED) {
+ mFastOpenStatus = TFO_FAILED_BACKUP_CONNECTION_TFO_TRIED;
+ } else if (mFastOpenStatus == TFO_DATA_SENT) {
+ mFastOpenStatus = TFO_FAILED_BACKUP_CONNECTION_TFO_DATA_SENT;
+ } else {
+ // This is TFO_DATA_COOKIE_NOT_ACCEPTED (I think this cannot
+ // happened, because the primary connection will be already
+ // connected or in recovery and mFastOpenInProgress==false).
+ mFastOpenStatus =
+ TFO_FAILED_BACKUP_CONNECTION_TFO_DATA_COOKIE_NOT_ACCEPTED;
+ }
+ }
+
+ if (((mFastOpenStatus == TFO_DISABLED) || (mFastOpenStatus == TFO_HTTP)) &&
+ !mBackupConnStatsSet) {
+ // Collect telemetry for backup connection being faster than primary
+ // connection. We want to collect this telemetry only for cases where
+ // TFO is not used.
+ mBackupConnStatsSet = true;
+ Telemetry::Accumulate(Telemetry::NETWORK_HTTP_BACKUP_CONN_WON_1,
+ (out == mBackupStreamOut));
+ }
+
+ if (mFastOpenStatus == TFO_UNKNOWN) {
+ MOZ_ASSERT(out == mStreamOut);
+ if (mPrimaryStreamStatus == NS_NET_STATUS_RESOLVING_HOST) {
+ mFastOpenStatus = TFO_UNKNOWN_RESOLVING;
+ } else if (mPrimaryStreamStatus == NS_NET_STATUS_RESOLVED_HOST) {
+ mFastOpenStatus = TFO_UNKNOWN_RESOLVED;
+ } else if (mPrimaryStreamStatus == NS_NET_STATUS_CONNECTING_TO) {
+ mFastOpenStatus = TFO_UNKNOWN_CONNECTING;
+ } else if (mPrimaryStreamStatus == NS_NET_STATUS_CONNECTED_TO) {
+ mFastOpenStatus = TFO_UNKNOWN_CONNECTED;
+ }
+ }
+ nsresult rv = SetupConn(out, false);
+ if (mEnt) {
+ mEnt->mDoNotDestroy = false;
+ }
+ return rv;
+}
+
+bool HalfOpenSocket::FastOpenEnabled() {
+ LOG(("HalfOpenSocket::FastOpenEnabled [this=%p]\n", this));
+
+ MOZ_ASSERT(mEnt);
+
+ if (!mEnt) {
+ return false;
+ }
+
+ MOZ_ASSERT(mEnt->mConnInfo->FirstHopSSL());
+
+ // If mEnt is present this HalfOpen must be in the mHalfOpens,
+ // but we want to be sure!!!
+ if (!mEnt->IsInHalfOpens(this)) {
+ return false;
+ }
+
+ if (!gHttpHandler->UseFastOpen()) {
+ // fast open was turned off.
+ LOG(("HalfOpenSocket::FastEnabled - fast open was turned off.\n"));
+ mEnt->mUseFastOpen = false;
+ mFastOpenStatus = TFO_DISABLED;
+ return false;
+ }
+ // We can use FastOpen if we have a transaction or if it is ssl
+ // connection. For ssl we will use a null transaction to drive the SSL
+ // handshake to completion if there is not a pending transaction. 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.
+ if (mEnt->mConnInfo->UsingConnect()) {
+ LOG(("HalfOpenSocket::FastOpenEnabled - It is using Connect."));
+ mFastOpenStatus = TFO_DISABLED_CONNECT;
+ return false;
+ }
+ return true;
+}
+
+nsresult HalfOpenSocket::StartFastOpen() {
+ MOZ_ASSERT(mStreamOut);
+ MOZ_ASSERT(!mBackupTransport);
+ MOZ_ASSERT(mEnt);
+ MOZ_ASSERT(mFastOpenStatus == TFO_UNKNOWN);
+
+ LOG(("HalfOpenSocket::StartFastOpen [this=%p]\n", this));
+
+ RefPtr<HalfOpenSocket> deleteProtector(this);
+
+ mFastOpenInProgress = true;
+ mEnt->mDoNotDestroy = true;
+ // Remove this HalfOpen from mEnt->mHalfOpens.
+ // The new connection will take care of closing this HalfOpen from now on!
+ if (!mEnt->RemoveHalfOpen(this)) {
+ MOZ_ASSERT(false, "HalfOpen is not in mHalfOpens!");
+ mSocketTransport->SetFastOpenCallback(nullptr);
+ CancelBackupTimer();
+ mFastOpenInProgress = false;
+ Abandon();
+ mFastOpenStatus = TFO_INIT_FAILED;
+ return NS_ERROR_ABORT;
+ }
+
+ gHttpHandler->ConnMgr()->DecreaseNumHalfOpenConns();
+
+ // Count this socketTransport as connected.
+ gHttpHandler->ConnMgr()->RecvdConnect();
+
+ // Remove HalfOpen from callbacks, the new connection will take them.
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mStreamOut->AsyncWait(nullptr, 0, 0, nullptr);
+
+ nsresult rv = SetupConn(mStreamOut, true);
+ if (!mConnectionNegotiatingFastOpen) {
+ LOG(
+ ("HalfOpenSocket::StartFastOpen SetupConn failed "
+ "[this=%p rv=%x]\n",
+ this, static_cast<uint32_t>(rv)));
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_ERROR_ABORT;
+ }
+ // If SetupConn failed this will CloseTransaction and socketTransport
+ // with an error, therefore we can close this HalfOpen. socketTransport
+ // will remove reference to this HalfOpen as well.
+ mSocketTransport->SetFastOpenCallback(nullptr);
+ CancelBackupTimer();
+ mFastOpenInProgress = false;
+
+ // The connection is responsible to take care of the halfOpen so we
+ // need to clean it up.
+ Abandon();
+ mFastOpenStatus = TFO_INIT_FAILED;
+ } else {
+ LOG(("HalfOpenSocket::StartFastOpen [this=%p conn=%p]\n", this,
+ mConnectionNegotiatingFastOpen.get()));
+
+ mEnt->InsertIntoHalfOpenFastOpenBackups(this);
+ // SetupBackupTimer should setup timer which will hold a ref to this
+ // halfOpen. It will failed only if it cannot create timer. Anyway just
+ // to be sure I will add this deleteProtector!!!
+ if (!mSynTimer) {
+ // For Fast Open we will setup backup timer also for
+ // NullTransaction.
+ // So maybe it is not set and we need to set it here.
+ SetupBackupTimer();
+ }
+ }
+ if (mEnt) {
+ mEnt->mDoNotDestroy = false;
+ }
+ return rv;
+}
+
+void HalfOpenSocket::SetFastOpenConnected(nsresult aError, bool aWillRetry) {
+ MOZ_ASSERT(mFastOpenInProgress);
+ MOZ_ASSERT(mEnt);
+
+ LOG(("HalfOpenSocket::SetFastOpenConnected [this=%p conn=%p error=%x]\n",
+ this, mConnectionNegotiatingFastOpen.get(),
+ static_cast<uint32_t>(aError)));
+
+ // mConnectionNegotiatingFastOpen is set after a StartFastOpen creates
+ // and activates a HttpConnectionBase successfully (SetupConn calls
+ // DispatchTransaction and DispatchAbstractTransaction which calls
+ // conn->Activate).
+ // HttpConnectionBase::Activate can fail which will close socketTransport
+ // and socketTransport will call this function. The FastOpen clean up
+ // in case HttpConnectionBase::Activate fails will be done in StartFastOpen.
+ // Also OnMsgReclaimConnection can decided that we do not need this
+ // transaction and cancel it as well.
+ // In all other cases mConnectionNegotiatingFastOpen must not be nullptr.
+ if (!mConnectionNegotiatingFastOpen) {
+ return;
+ }
+
+ MOZ_ASSERT((mFastOpenStatus == TFO_NOT_TRIED) ||
+ (mFastOpenStatus == TFO_DATA_SENT) ||
+ (mFastOpenStatus == TFO_TRIED) ||
+ (mFastOpenStatus == TFO_DATA_COOKIE_NOT_ACCEPTED) ||
+ (mFastOpenStatus == TFO_DISABLED));
+
+ RefPtr<HalfOpenSocket> deleteProtector(this);
+
+ mEnt->mDoNotDestroy = true;
+
+ // Delete 2 points of entry to FastOpen function so that we do not reenter.
+ mEnt->RemoveHalfOpenFastOpenBackups(this);
+ mSocketTransport->SetFastOpenCallback(nullptr);
+
+ mConnectionNegotiatingFastOpen->SetFastOpen(false);
+
+ // Check if we want to restart connection!
+ if (aWillRetry && ((aError == NS_ERROR_CONNECTION_REFUSED) ||
+#if defined(_WIN64) && defined(WIN95)
+ // On Windows PR_ContinueConnect can return
+ // NS_ERROR_FAILURE. This will be fixed in bug 1386719 and
+ // this is just a temporary work around.
+ (aError == NS_ERROR_FAILURE) ||
+#endif
+ (aError == NS_ERROR_PROXY_CONNECTION_REFUSED) ||
+ (aError == NS_ERROR_NET_TIMEOUT))) {
+ if (mEnt->mUseFastOpen) {
+ gHttpHandler->IncrementFastOpenConsecutiveFailureCounter();
+ mEnt->mUseFastOpen = false;
+ }
+ // This is called from nsSocketTransport::RecoverFromError. The
+ // socket will try connect and we need to rewind nsHttpTransaction.
+
+ RefPtr<nsAHttpTransaction> trans =
+ mConnectionNegotiatingFastOpen
+ ->CloseConnectionFastOpenTakesTooLongOrError(false);
+ if (trans && trans->QueryHttpTransaction()) {
+ RefPtr<PendingTransactionInfo> pendingTransInfo =
+ new PendingTransactionInfo(trans->QueryHttpTransaction());
+ pendingTransInfo->AddHalfOpen(this);
+ mEnt->InsertTransaction(pendingTransInfo, true);
+ }
+ // We are doing a restart without fast open, so the easiest way is to
+ // return mSocketTransport to the halfOpenSock and destroy connection.
+ // This makes http2 implemenntation easier.
+ // mConnectionNegotiatingFastOpen is going away and halfOpen is taking
+ // this mSocketTransport so add halfOpen to mEnt and update
+ // mNumActiveConns.
+ mEnt->InsertIntoHalfOpens(this);
+ gHttpHandler->ConnMgr()->StartedConnect();
+
+ // Restore callbacks.
+ mStreamOut->AsyncWait(this, 0, 0, nullptr);
+ mSocketTransport->SetEventSink(this, nullptr);
+ mSocketTransport->SetSecurityCallbacks(this);
+ mStreamIn->AsyncWait(nullptr, 0, 0, nullptr);
+
+ if ((aError == NS_ERROR_CONNECTION_REFUSED) ||
+ (aError == NS_ERROR_PROXY_CONNECTION_REFUSED)) {
+ mFastOpenStatus = TFO_FAILED_CONNECTION_REFUSED;
+ } else {
+ mFastOpenStatus = TFO_FAILED_NET_TIMEOUT;
+ }
+
+ } else {
+ // On success or other error we proceed with connection, we just need
+ // to close backup timer and halfOpenSock.
+ CancelBackupTimer();
+ if (NS_SUCCEEDED(aError)) {
+ NetAddr peeraddr;
+ if (NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr))) {
+ mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
+ }
+ gHttpHandler->ResetFastOpenConsecutiveFailureCounter();
+ }
+ mSocketTransport = nullptr;
+ mStreamOut = nullptr;
+ mStreamIn = nullptr;
+
+ // If backup transport has already started put this HalfOpen back to
+ // mEnt list.
+ if (mBackupTransport) {
+ mFastOpenStatus = TFO_BACKUP_CONN;
+ mEnt->InsertIntoHalfOpens(this);
+ }
+ }
+
+ mFastOpenInProgress = false;
+ mConnectionNegotiatingFastOpen = nullptr;
+ if (mEnt) {
+ mEnt->mDoNotDestroy = false;
+ } else {
+ MOZ_ASSERT(!mBackupTransport);
+ MOZ_ASSERT(!mBackupStreamOut);
+ }
+}
+
+void HalfOpenSocket::SetFastOpenStatus(uint8_t tfoStatus) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mFastOpenInProgress);
+
+ mFastOpenStatus = tfoStatus;
+ mConnectionNegotiatingFastOpen->SetFastOpenStatus(tfoStatus);
+ if (mConnectionNegotiatingFastOpen->Transaction()) {
+ // The transaction could already be canceled in the meantime, hence
+ // nullified.
+ mConnectionNegotiatingFastOpen->Transaction()->SetFastOpenStatus(tfoStatus);
+ }
+}
+
+void HalfOpenSocket::CancelFastOpenConnection() {
+ MOZ_ASSERT(mFastOpenInProgress);
+
+ LOG(("HalfOpenSocket::CancelFastOpenConnection [this=%p conn=%p]\n", this,
+ mConnectionNegotiatingFastOpen.get()));
+
+ RefPtr<HalfOpenSocket> deleteProtector(this);
+ mEnt->RemoveHalfOpenFastOpenBackups(this);
+ mSocketTransport->SetFastOpenCallback(nullptr);
+ mConnectionNegotiatingFastOpen->SetFastOpen(false);
+ RefPtr<nsAHttpTransaction> trans =
+ mConnectionNegotiatingFastOpen
+ ->CloseConnectionFastOpenTakesTooLongOrError(true);
+ mSocketTransport = nullptr;
+ mStreamOut = nullptr;
+ mStreamIn = nullptr;
+
+ if (trans && trans->QueryHttpTransaction()) {
+ RefPtr<PendingTransactionInfo> pendingTransInfo =
+ new PendingTransactionInfo(trans->QueryHttpTransaction());
+
+ mEnt->InsertTransaction(pendingTransInfo, true);
+ }
+
+ mFastOpenInProgress = false;
+ mConnectionNegotiatingFastOpen = nullptr;
+ Abandon();
+
+ MOZ_ASSERT(!mBackupTransport);
+ MOZ_ASSERT(!mBackupStreamOut);
+}
+
+void HalfOpenSocket::FastOpenNotSupported() {
+ MOZ_ASSERT(mFastOpenInProgress);
+ gHttpHandler->SetFastOpenNotSupported();
+}
+
+nsresult HalfOpenSocket::SetupConn(nsIAsyncOutputStream* out, bool aFastOpen) {
+ MOZ_ASSERT(!aFastOpen || (out == mStreamOut));
+ // We cannot ask for a connection for TFO and Http3 ata the same time.
+ MOZ_ASSERT(!(mIsHttp3 && aFastOpen));
+ // assign the new socket to the http connection
+ RefPtr<HttpConnectionBase> conn;
+ if (!mIsHttp3) {
+ conn = new nsHttpConnection();
+ } else {
+ conn = new HttpConnectionUDP();
+ }
+
+ LOG(
+ ("HalfOpenSocket::SetupConn "
+ "Created new nshttpconnection %p %s\n",
+ conn.get(), mIsHttp3 ? "using http3" : ""));
+
+ NullHttpTransaction* nullTrans = mTransaction->QueryNullTransaction();
+ if (nullTrans) {
+ conn->BootstrapTimings(nullTrans->Timings());
+ }
+
+ // Some capabilities are needed before a transaciton actually gets
+ // scheduled (e.g. how to negotiate false start)
+ conn->SetTransactionCaps(mTransaction->Caps());
+
+ NetAddr peeraddr;
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ nsresult rv;
+ if (out == mStreamOut) {
+ TimeDuration rtt = TimeStamp::Now() - mPrimarySynStarted;
+ rv = conn->Init(
+ mEnt->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay,
+ mSocketTransport, mStreamIn, mStreamOut,
+ mPrimaryConnectedOK || aFastOpen, callbacks,
+ PR_MillisecondsToInterval(static_cast<uint32_t>(rtt.ToMilliseconds())));
+
+ bool resetPreference = false;
+ mSocketTransport->GetResetIPFamilyPreference(&resetPreference);
+ if (resetPreference) {
+ mEnt->ResetIPFamilyPreference();
+ }
+
+ if (!aFastOpen && NS_SUCCEEDED(mSocketTransport->GetPeerAddr(&peeraddr))) {
+ mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
+ }
+
+ // The nsHttpConnection object now owns these streams and sockets
+ if (!aFastOpen) {
+ mStreamOut = nullptr;
+ mStreamIn = nullptr;
+ mSocketTransport = nullptr;
+ } else {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ MOZ_ASSERT(connTCP);
+ if (connTCP) {
+ connTCP->SetFastOpen(true);
+ }
+ }
+ } else if (out == mBackupStreamOut) {
+ TimeDuration rtt = TimeStamp::Now() - mBackupSynStarted;
+ rv = conn->Init(
+ mEnt->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay,
+ mBackupTransport, mBackupStreamIn, mBackupStreamOut, mBackupConnectedOK,
+ callbacks,
+ PR_MillisecondsToInterval(static_cast<uint32_t>(rtt.ToMilliseconds())));
+
+ bool resetPreference = false;
+ mBackupTransport->GetResetIPFamilyPreference(&resetPreference);
+ if (resetPreference) {
+ mEnt->ResetIPFamilyPreference();
+ }
+
+ if (NS_SUCCEEDED(mBackupTransport->GetPeerAddr(&peeraddr))) {
+ mEnt->RecordIPFamilyPreference(peeraddr.raw.family);
+ }
+
+ // The nsHttpConnection object now owns these streams and sockets
+ mBackupStreamOut = nullptr;
+ mBackupStreamIn = nullptr;
+ mBackupTransport = nullptr;
+ } else {
+ MOZ_ASSERT(false, "unexpected stream");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("HalfOpenSocket::SetupConn "
+ "conn->init (%p) failed %" PRIx32 "\n",
+ conn.get(), static_cast<uint32_t>(rv)));
+
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ if (connTCP) {
+ // Set TFO status.
+ if ((mFastOpenStatus == TFO_HTTP) || (mFastOpenStatus == TFO_DISABLED) ||
+ (mFastOpenStatus == TFO_DISABLED_CONNECT)) {
+ connTCP->SetFastOpenStatus(mFastOpenStatus);
+ } else {
+ connTCP->SetFastOpenStatus(TFO_INIT_FAILED);
+ }
+ }
+
+ if (nsHttpTransaction* trans = mTransaction->QueryHttpTransaction()) {
+ if (mIsHttp3) {
+ trans->DisableHttp3();
+ gHttpHandler->ExcludeHttp3(mEnt->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.
+ mEnt->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.
+ if (!aFastOpen) {
+ mHasConnected = true;
+ }
+
+ // if this is still in the pending list, remove it and dispatch it
+ RefPtr<PendingTransactionInfo> pendingTransInfo =
+ gHttpHandler->ConnMgr()->FindTransactionHelper(true, mEnt, mTransaction);
+ if (pendingTransInfo) {
+ MOZ_ASSERT(!mSpeculative, "Speculative Half Open found mTransaction");
+
+ mEnt->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(
+ mEnt, 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 ||
+ (mEnt->mConnInfo->FirstHopSSL() && !mEnt->UrgentStartQueueLength() &&
+ !mEnt->PendingQueueLength() && !mEnt->mConnInfo->UsingConnect())) {
+ LOG(
+ ("HalfOpenSocket::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(mEnt->mConnInfo, callbacks, mCaps);
+ }
+
+ mEnt->InsertIntoActiveConns(conn);
+ rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(mEnt, trans,
+ mCaps, conn, 0);
+ } else {
+ // otherwise just put this in the persistent connection pool
+ LOG(
+ ("HalfOpenSocket::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!
+
+ // !!! It can be that mEnt is null after OnMsgReclaimConnection.!!!
+ if (mEnt && mEnt->mConnInfo->FirstHopSSL() &&
+ !mEnt->mConnInfo->UsingConnect()) {
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ // If RemoveIdleConnection succeeds that means that conn is in the
+ // idle queue.
+ if (connTCP && NS_SUCCEEDED(mEnt->RemoveIdleConnection(connTCP))) {
+ RefPtr<nsAHttpTransaction> trans;
+ if (mTransaction->IsNullTransaction() && !mDispatchedMTransaction) {
+ mDispatchedMTransaction = true;
+ trans = mTransaction;
+ } else {
+ trans = new NullHttpTransaction(mEnt->mConnInfo, callbacks, mCaps);
+ }
+ mEnt->InsertIntoActiveConns(conn);
+ rv = gHttpHandler->ConnMgr()->DispatchAbstractTransaction(
+ mEnt, trans, mCaps, conn, 0);
+ }
+ }
+ }
+ }
+
+ // If this connection has a transaction get reference to its
+ // ConnectionHandler.
+ RefPtr<nsHttpConnection> connTCP = do_QueryObject(conn);
+ if (connTCP) {
+ if (aFastOpen) {
+ MOZ_ASSERT(mEnt);
+ MOZ_ASSERT(!mEnt->IsInIdleConnections(connTCP));
+ if (NS_SUCCEEDED(rv) && mEnt->IsInActiveConns(conn)) {
+ mConnectionNegotiatingFastOpen = connTCP;
+ } else {
+ connTCP->SetFastOpen(false);
+ connTCP->SetFastOpenStatus(TFO_INIT_FAILED);
+ }
+ } else {
+ connTCP->SetFastOpenStatus(mFastOpenStatus);
+ if ((mFastOpenStatus != TFO_HTTP) && (mFastOpenStatus != TFO_DISABLED) &&
+ (mFastOpenStatus != TFO_DISABLED_CONNECT)) {
+ mFastOpenStatus = TFO_BACKUP_CONN; // Set this to TFO_BACKUP_CONN
+ // so that if a backup
+ // connection is established we
+ // do not report values twice.
+ }
+ }
+ }
+
+ // 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
+HalfOpenSocket::OnTransportStatus(nsITransport* trans, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ MOZ_ASSERT((trans == mSocketTransport) || (trans == mBackupTransport));
+ MOZ_ASSERT(mEnt);
+ if (mTransaction) {
+ if ((trans == mSocketTransport) ||
+ ((trans == mBackupTransport) &&
+ (status == NS_NET_STATUS_CONNECTED_TO) && 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_RESOLVING_HOST, NS_NET_STATUS_RESOLVED_HOST,
+ // 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 all
+ // mBackupTransport events until NS_NET_STATUS_CONNECTED_TO.
+ // mBackupTransport must be connected before mSocketTransport.
+ mTransaction->OnTransportStatus(trans, status, progress);
+ }
+ }
+
+ MOZ_ASSERT(trans == mSocketTransport || trans == mBackupTransport);
+ if (status == NS_NET_STATUS_CONNECTED_TO) {
+ if (trans == mSocketTransport) {
+ mPrimaryConnectedOK = true;
+ } else {
+ mBackupConnectedOK = true;
+ }
+ }
+
+ // The rest of this method only applies to the primary transport
+ if (trans != mSocketTransport) {
+ return NS_OK;
+ }
+
+ mPrimaryStreamStatus = status;
+
+ // 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.
+
+ if (status == NS_NET_STATUS_CONNECTING_TO && gHttpHandler->IsSpdyEnabled() &&
+ gHttpHandler->CoalesceSpdy() && mEnt && mEnt->mConnInfo &&
+ mEnt->mConnInfo->EndToEndSSL() && mEnt->AllowHttp2() &&
+ !mEnt->mConnInfo->UsingProxy() && mEnt->mCoalescingKeys.IsEmpty()) {
+ nsCOMPtr<nsIDNSAddrRecord> dnsRecord(do_GetInterface(mSocketTransport));
+ nsTArray<NetAddr> addressSet;
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ if (dnsRecord) {
+ rv = dnsRecord->GetAddresses(addressSet);
+ }
+
+ if (NS_SUCCEEDED(rv) && !addressSet.IsEmpty()) {
+ 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(("HalfOpenSocket: skip creating Coalescing Key for host [%s]",
+ mEnt->mConnInfo->Origin()));
+ continue;
+ }
+ nsCString* newKey = mEnt->mCoalescingKeys.AppendElement(nsCString());
+ newKey->SetLength(kIPv6CStrBufSize + 26);
+ addressSet[i].ToStringBuffer(newKey->BeginWriting(), kIPv6CStrBufSize);
+ newKey->SetLength(strlen(newKey->BeginReading()));
+ if (mEnt->mConnInfo->GetAnonymous()) {
+ newKey->AppendLiteral("~A:");
+ } else {
+ newKey->AppendLiteral("~.:");
+ }
+ newKey->AppendInt(mEnt->mConnInfo->OriginPort());
+ newKey->AppendLiteral("/[");
+ nsAutoCString suffix;
+ mEnt->mConnInfo->GetOriginAttributes().CreateSuffix(suffix);
+ newKey->Append(suffix);
+ newKey->AppendLiteral("]viaDNS");
+ LOG((
+ "HalfOpenSocket::OnTransportStatus "
+ "STATUS_CONNECTING_TO Established New Coalescing Key # %d for host "
+ "%s [%s]",
+ i, mEnt->mConnInfo->Origin(), newKey->get()));
+ }
+ gHttpHandler->ConnMgr()->ProcessSpdyPendingQ(mEnt);
+ }
+ }
+
+ switch (status) {
+ case NS_NET_STATUS_CONNECTING_TO:
+ // Passed DNS resolution, now trying to connect, start the backup timer
+ // only prevent creating another backup transport.
+ // We also check for mEnt presence to not instantiate the timer after
+ // this half open socket has already been abandoned. It may happen
+ // when we get this notification right between main-thread calls to
+ // nsHttpConnectionMgr::Shutdown and nsSocketTransportService::Shutdown
+ // where the first abandons all half open socket instances and only
+ // after that the second stops the socket thread.
+ // Http3 has its own syn-retransmission, therefore it does not need a
+ // backup connection.
+ if (mEnt && !mBackupTransport && !mSynTimer && !mIsHttp3) {
+ SetupBackupTimer();
+ }
+ break;
+
+ case NS_NET_STATUS_CONNECTED_TO:
+ // TCP connection's up, now transfer or SSL negotiantion starts,
+ // no need for backup socket
+ CancelBackupTimer();
+ break;
+
+ default:
+ break;
+ }
+
+ return NS_OK;
+}
+
+// method for nsIInterfaceRequestor
+NS_IMETHODIMP
+HalfOpenSocket::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 HalfOpenSocket::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 HalfOpenSocket::Claim() {
+ if (mSpeculative) {
+ mSpeculative = false;
+ uint32_t flags;
+ if (mSocketTransport &&
+ NS_SUCCEEDED(mSocketTransport->GetConnectionFlags(&flags))) {
+ flags &= ~nsISocketTransport::DISABLE_RFC1918;
+ mSocketTransport->SetConnectionFlags(flags);
+ }
+
+ 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 ((mPrimaryStreamStatus == NS_NET_STATUS_CONNECTING_TO) && mEnt &&
+ !mBackupTransport && !mSynTimer && !mIsHttp3) {
+ SetupBackupTimer();
+ }
+ }
+
+ if (mFreeToUse) {
+ mFreeToUse = false;
+ return true;
+ }
+ return false;
+}
+
+void HalfOpenSocket::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;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HalfOpenSocket.h b/netwerk/protocol/http/HalfOpenSocket.h
new file mode 100644
index 0000000000..d1a753da12
--- /dev/null
+++ b/netwerk/protocol/http/HalfOpenSocket.h
@@ -0,0 +1,165 @@
+/* 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 HalfOpenSocket_h__
+#define HalfOpenSocket_h__
+
+#include "mozilla/TimeStamp.h"
+#include "nsAHttpConnection.h"
+#include "nsHttpConnection.h"
+#include "nsHttpTransaction.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsINamed.h"
+#include "nsITransport.h"
+#include "nsWeakReference.h"
+#include "TCPFastOpen.h"
+
+namespace mozilla {
+namespace net {
+
+// 8d411b53-54bc-4a99-8b78-ff125eab1564
+#define NS_HALFOPENSOCKET_IID \
+ { \
+ 0x8d411b53, 0x54bc, 0x4a99, { \
+ 0x8b, 0x78, 0xff, 0x12, 0x5e, 0xab, 0x15, 0x64 \
+ } \
+ }
+
+class PendingTransactionInfo;
+class ConnectionEntry;
+
+class HalfOpenSocket final : public nsIOutputStreamCallback,
+ public nsITransportEventSink,
+ public nsIInterfaceRequestor,
+ public nsITimerCallback,
+ public nsINamed,
+ public nsSupportsWeakReference,
+ public TCPFastOpen {
+ ~HalfOpenSocket();
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HALFOPENSOCKET_IID)
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ HalfOpenSocket(ConnectionEntry* ent, nsAHttpTransaction* trans, uint32_t caps,
+ bool speculative, bool isFromPredictor, bool urgentStart);
+
+ [[nodiscard]] nsresult SetupStreams(nsISocketTransport**,
+ nsIAsyncInputStream**,
+ nsIAsyncOutputStream**, bool isBackup);
+ [[nodiscard]] nsresult SetupPrimaryStreams();
+ [[nodiscard]] nsresult SetupBackupStreams();
+ void SetupBackupTimer();
+ void CancelBackupTimer();
+ void Abandon();
+ double Duration(TimeStamp epoch);
+ nsISocketTransport* SocketTransport() { return mSocketTransport; }
+ nsISocketTransport* BackupTransport() { return mBackupTransport; }
+
+ nsAHttpTransaction* Transaction() { return mTransaction; }
+
+ bool IsSpeculative() { return mSpeculative; }
+
+ bool IsFromPredictor() { return mIsFromPredictor; }
+
+ 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();
+
+ bool FastOpenEnabled() override;
+ nsresult StartFastOpen() override;
+ void SetFastOpenConnected(nsresult, bool aWillRetry) override;
+ void FastOpenNotSupported() override;
+ void SetFastOpenStatus(uint8_t tfoStatus) override;
+ void CancelFastOpenConnection();
+
+ private:
+ nsresult SetupConn(nsIAsyncOutputStream* out, bool aFastOpen);
+
+ // 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);
+
+ RefPtr<nsAHttpTransaction> mTransaction;
+ bool mDispatchedMTransaction;
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+ nsCOMPtr<nsIAsyncOutputStream> mStreamOut;
+ nsCOMPtr<nsIAsyncInputStream> mStreamIn;
+ 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;
+
+ TimeStamp mPrimarySynStarted;
+ TimeStamp mBackupSynStarted;
+
+ // mHasConnected tracks whether one of the sockets has completed the
+ // connection process. It may have completed unsuccessfully.
+ bool mHasConnected;
+
+ bool mPrimaryConnectedOK;
+ bool mBackupConnectedOK;
+ bool mBackupConnStatsSet;
+
+ // A HalfOpenSocket 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;
+ nsresult mPrimaryStreamStatus;
+
+ bool mFastOpenInProgress;
+ RefPtr<nsHttpConnection> mConnectionNegotiatingFastOpen;
+ uint8_t mFastOpenStatus;
+
+ RefPtr<ConnectionEntry> mEnt;
+ nsCOMPtr<nsITimer> mSynTimer;
+ nsCOMPtr<nsISocketTransport> mBackupTransport;
+ nsCOMPtr<nsIAsyncOutputStream> mBackupStreamOut;
+ nsCOMPtr<nsIAsyncInputStream> mBackupStreamIn;
+
+ bool mIsHttp3;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HalfOpenSocket, NS_HALFOPENSOCKET_IID)
+
+} // namespace net
+} // namespace mozilla
+
+#endif // HalfOpenSocket_h__
diff --git a/netwerk/protocol/http/Http2Compression.cpp b/netwerk/protocol/http/Http2Compression.cpp
new file mode 100644
index 0000000000..8dba007688
--- /dev/null
+++ b/netwerk/protocol/http/Http2Compression.cpp
@@ -0,0 +1,1443 @@
+/* -*- 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 {
+ 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;
+
+ Http2BaseCompressor* mCompressor;
+
+ 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() : mByteCount(0), mTable() { InitializeStaticHeaders(); }
+
+nvFIFO::~nvFIFO() { Clear(); }
+
+void nvFIFO::AddElement(const nsCString& name, const nsCString& value) {
+ nvPair* pair = new nvPair(name, value);
+ mByteCount += pair->Size();
+ mTable.PushFront(pair);
+}
+
+void nvFIFO::AddElement(const nsCString& name) { AddElement(name, ""_ns); }
+
+void nvFIFO::RemoveElement() {
+ nvPair* 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;
+ 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()
+ : mOutput(nullptr),
+ mMaxBuffer(kDefaultMaxBuffer),
+ mMaxBufferSetting(kDefaultMaxBuffer),
+ mSetInitialMaxBufferSizeAllowed(true),
+ mPeakSize(0),
+ mPeakCount(0),
+ mDumpTables(false) {
+ mDynamicReporter = new HpackDynamicTableReporter(this);
+ RegisterStrongMemoryReporter(mDynamicReporter);
+}
+
+Http2BaseCompressor::~Http2BaseCompressor() {
+ if (mPeakSize) {
+ Telemetry::Accumulate(mPeakSizeID, mPeakSize);
+ }
+ if (mPeakCount) {
+ Telemetry::Accumulate(mPeakCountID, mPeakCount);
+ }
+ UnregisterStrongMemoryReporter(mDynamicReporter);
+ mDynamicReporter->mCompressor = nullptr;
+ mDynamicReporter = nullptr;
+}
+
+void Http2BaseCompressor::ClearHeaderTable() { mHeaderTable.Clear(); }
+
+size_t Http2BaseCompressor::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t size = 0;
+ for (uint32_t i = mHeaderTable.StaticLength(); i < mHeaderTable.Length();
+ ++i) {
+ size += mHeaderTable[i]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return size;
+}
+
+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);
+
+ uint32_t removedCount = 0;
+
+ LOG(("Http2BaseCompressor::SetMaxBufferSizeInternal %u called",
+ maxBufferSize));
+
+ while (mHeaderTable.VariableLength() &&
+ (mHeaderTable.ByteCount() > maxBufferSize)) {
+ mHeaderTable.RemoveElement();
+ ++removedCount;
+ }
+
+ 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;
+ }
+
+ // 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 OR LF in value - could be smuggling Sec 10.3
+ // can map to space safely
+ for (const char* cPtr = value.BeginReading();
+ cPtr && cPtr < value.EndReading(); ++cPtr) {
+ if (*cPtr == '\r' || *cPtr == '\n') {
+ char* wPtr = const_cast<char*>(cPtr);
+ *wPtr = ' ';
+ }
+ }
+
+ // 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;
+ } else 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", false, startIndex);
+ if (crlfIndex == -1) {
+ break;
+ }
+
+ int32_t colonIndex =
+ nvInput.Find(":", false, startIndex, crlfIndex - 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;
+ } else 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 =
+ nvInput.Find("; ", false, nextCookie, crlfIndex - 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..1ab6661449
--- /dev/null
+++ b/netwerk/protocol/http/Http2Compression.h
@@ -0,0 +1,206 @@
+/* -*- 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"
+
+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;
+
+ private:
+ uint32_t mByteCount;
+ nsDeque<nvPair> mTable;
+};
+
+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;
+ nvFIFO mHeaderTable;
+
+ uint32_t mMaxBuffer;
+ uint32_t mMaxBufferSetting;
+ bool mSetInitialMaxBufferSizeAllowed;
+
+ uint32_t mPeakSize;
+ uint32_t mPeakCount;
+ MOZ_INIT_OUTSIDE_CTOR
+ Telemetry::HistogramID mPeakSizeID;
+ MOZ_INIT_OUTSIDE_CTOR
+ Telemetry::HistogramID mPeakCountID;
+
+ bool mDumpTables;
+
+ private:
+ RefPtr<HpackDynamicTableReporter> mDynamicReporter;
+};
+
+class Http2Compressor;
+
+class Http2Decompressor final : public Http2BaseCompressor {
+ public:
+ Http2Decompressor()
+ : mOffset(0),
+ mData(nullptr),
+ mDataLen(0),
+ mSeenNonColonHeader(false),
+ mIsPush(false) {
+ 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& result);
+ [[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 index, nsACString& val);
+ uint8_t ExtractByte(uint8_t bitsLeft, uint32_t& bytesConsumed);
+ [[nodiscard]] nsresult CopyHuffmanStringFromInput(uint32_t index,
+ 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;
+ const uint8_t* mData;
+ uint32_t mDataLen;
+ bool mSeenNonColonHeader;
+ bool mIsPush;
+};
+
+class Http2Compressor final : public Http2BaseCompressor {
+ public:
+ Http2Compressor()
+ : mParsedContentLength(-1),
+ mBufferSizeChangeWaiting(false),
+ mLowestBufferSizeWaiting(0) {
+ 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;
+ bool mBufferSizeChangeWaiting;
+ uint32_t mLowestBufferSizeWaiting;
+};
+
+} // 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..702ccea895
--- /dev/null
+++ b/netwerk/protocol/http/Http2Push.cpp
@@ -0,0 +1,498 @@
+/* -*- 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 "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) {
+ Http2Stream* stream = mStream;
+ return static_cast<Http2PushedStream*>(stream);
+ }
+ return nullptr;
+}
+
+void Http2PushedStreamWrapper::OnPushFailed() {
+ if (OnSocketThread()) {
+ if (mStream) {
+ Http2Stream* 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,
+ Http2Stream* aAssociatedStream, uint32_t aID,
+ uint64_t aCurrentForegroundTabOuterContentWindowId)
+ : Http2Stream(aTransaction, aSession, 0,
+ aCurrentForegroundTabOuterContentWindowId),
+ mConsumerStream(nullptr),
+ mAssociatedTransaction(aAssociatedStream->Transaction()),
+ mBufferedPush(aTransaction),
+ mStatus(NS_OK),
+ mPushCompleted(false),
+ mDeferCleanupOnSuccess(true),
+ mDeferCleanupOnPush(false),
+ mOnPushFailed(false) {
+ 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.
+ mTransactionTabId = aAssociatedStream->TransactionTabId();
+}
+
+bool Http2PushedStream::GetPushComplete() { return mPushCompleted; }
+
+nsresult Http2PushedStream::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ nsresult rv = Http2Stream::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 Http2Stream implements
+// nsIHttpPushListener
+bool Http2PushedStream::TestOnPush(Http2Stream* 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);
+ CreatePushHashKey(mHeaderScheme, mHeaderHost, originAttributes,
+ mSession->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);
+ Http2Stream::mRequestHeadersDone = 1;
+ Http2Stream::mOpenGenerated = 1;
+ Http2Stream::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()));
+ Http2Stream::AdjustInitialWindow();
+ // Http2PushedStream::ReadSegments is needed to call TransmitFrame()
+ // and actually get this information into the session bytestream
+ mSession->TransactionHasDataToWrite(this);
+ }
+ // Otherwise, when we get hooked up, the initial window will get bumped
+ // anyway, so we're good to go.
+}
+
+void Http2PushedStream::SetConsumerStream(Http2Stream* 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(Http2Stream* stream) {
+ mSession->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::TopLevelOuterContentWindowIdChanged(uint64_t windowId) {
+ if (mConsumerStream) {
+ // Pass through to our sink, who will handle things appropriately.
+ mConsumerStream->TopLevelOuterContentWindowIdChangedInternal(windowId);
+ return;
+ }
+
+ MOZ_ASSERT(gHttpHandler->ActiveTabPriority());
+
+ mCurrentForegroundTabOuterContentWindowId = windowId;
+
+ if (!mSession->UseH2Deps()) {
+ return;
+ }
+
+ uint32_t oldDependency = mPriorityDependency;
+ if (mTransactionTabId != mCurrentForegroundTabOuterContentWindowId) {
+ mPriorityDependency = Http2Session::kBackgroundGroupID;
+ nsHttp::NotifyActiveTabLoadOptimization();
+ } else {
+ mPriorityDependency = mDefaultPriorityDependency;
+ }
+
+ if (mPriorityDependency != oldDependency) {
+ mSession->SendPriorityFrame(mStreamID, mPriorityDependency,
+ mPriorityWeight);
+ }
+}
+
+//////////////////////////////////////////
+// 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()
+ : mStatus(NS_OK),
+ mRequestHead(nullptr),
+ mPushStream(nullptr),
+ mIsDone(false),
+ mBufferedHTTP1Size(kDefaultBufferSize),
+ mBufferedHTTP1Used(0),
+ mBufferedHTTP1Consumed(0) {
+ 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) {
+ Http2Stream* 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..c1dfc433a4
--- /dev/null
+++ b/netwerk/protocol/http/Http2Push.h
@@ -0,0 +1,161 @@
+/* -*- 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 "Http2Stream.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 Http2Stream {
+ public:
+ Http2PushedStream(Http2PushTransactionBuffer* aTransaction,
+ Http2Session* aSession, Http2Stream* aAssociatedStream,
+ uint32_t aID,
+ uint64_t aCurrentForegroundTabOuterContentWindowId);
+
+ bool GetPushComplete();
+
+ // The consumer stream is the synthetic pull stream hooked up to this push
+ virtual Http2Stream* GetConsumerStream() override { return mConsumerStream; };
+
+ void SetConsumerStream(Http2Stream* aStream);
+ [[nodiscard]] bool GetHashKey(nsCString& key);
+
+ // override of Http2Stream
+ [[nodiscard]] nsresult ReadSegments(nsAHttpSegmentReader*, uint32_t,
+ uint32_t*) override;
+ [[nodiscard]] nsresult WriteSegments(nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*) override;
+ void AdjustInitialWindow() override;
+
+ nsIRequestContext* RequestContext() override { return mRequestContext; };
+ void ConnectPushedStream(Http2Stream* consumer);
+
+ [[nodiscard]] bool TryOnPush();
+ [[nodiscard]] static bool TestOnPush(Http2Stream* consumer);
+
+ 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 Http2Stream
+ virtual bool HasSink() override { return !!mConsumerStream; }
+ virtual void SetPushComplete() override { mPushCompleted = true; }
+ virtual void TopLevelOuterContentWindowIdChanged(uint64_t) override;
+
+ nsCString& GetRequestString() { return mRequestString; }
+ nsCString& GetResourceUrl() { return mResourceUrl; }
+
+ private:
+ virtual ~Http2PushedStream() = default;
+ Http2Stream*
+ mConsumerStream; // paired request stream that consumes from
+ // real http/2 one.. null until a match is made.
+
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+
+ nsAHttpTransaction* mAssociatedTransaction;
+
+ Http2PushTransactionBuffer* mBufferedPush;
+ mozilla::TimeStamp mLastRead;
+
+ nsCString mHashKey;
+ nsresult mStatus;
+ bool mPushCompleted; // server push FIN received
+ bool mDeferCleanupOnSuccess;
+
+ // 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;
+ bool mOnPushFailed;
+ nsCString mRequestString;
+ nsCString mResourceUrl;
+
+ uint32_t mDefaultPriorityDependency;
+};
+
+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;
+ nsHttpRequestHead* mRequestHead;
+ Http2PushedStream* mPushStream;
+ bool mIsDone;
+
+ UniquePtr<char[]> mBufferedHTTP1;
+ uint32_t mBufferedHTTP1Size;
+ uint32_t mBufferedHTTP1Used;
+ uint32_t mBufferedHTTP1Consumed;
+};
+
+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<Http2Stream> 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..c7e6aa14c0
--- /dev/null
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -0,0 +1,4653 @@
+/* -*- 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 "Http2Session.h"
+#include "Http2Stream.h"
+#include "Http2Push.h"
+
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Preferences.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpConnection.h"
+#include "nsIRequestContext.h"
+#include "nsISSLSocketControl.h"
+#include "nsISupportsPriority.h"
+#include "nsStandardURL.h"
+#include "nsURLHelper.h"
+#include "prnetdb.h"
+#include "sslt.h"
+#include "mozilla/Sprintf.h"
+#include "nsSocketTransportService2.h"
+#include "nsNetUtil.h"
+#include "CacheControlParser.h"
+#include "CachePushChecker.h"
+#include "LoadContextInfo.h"
+#include "TCPFastOpenLayer.h"
+#include "nsQueryObject.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_AMBIGUOUS(nsISupports, nsAHttpConnection)
+NS_INTERFACE_MAP_END
+
+// "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),
+ mCheckNetworkStallsWithTFO(false),
+ mLastRequestBytesSentTime(0),
+ 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;
+ mCurrentForegroundTabOuterContentWindowId =
+ gHttpHandler->ConnMgr()->CurrentTopLevelOuterContentWindowId();
+
+ mEnableWebsockets = gHttpHandler->IsH2WebsocketsEnabled();
+
+ bool dumpHpackTables = gHttpHandler->DumpHpackTables();
+ mCompressor.SetDumpTables(dumpHpackTables);
+ mDecompressor.SetDumpTables(dumpHpackTables);
+}
+
+void Http2Session::Shutdown() {
+ for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
+ auto stream = iter.UserData();
+
+ // 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 &&
+ (stream->StreamID() > mGoAwayID || !stream->HasRegisteredID())) {
+ CloseStream(stream, NS_ERROR_NET_RESET); // can be restarted
+ } else if (stream->RecvdData()) {
+ CloseStream(stream, NS_ERROR_NET_PARTIAL_TRANSFER);
+ } else if (mGoAwayReason == INADEQUATE_SECURITY) {
+ CloseStream(stream, NS_ERROR_NET_INADEQUATE_SECURITY);
+ } else if (!mCleanShutdown && (mGoAwayReason != NO_HTTP_ERROR)) {
+ CloseStream(stream, NS_ERROR_NET_HTTP2_SENT_GOAWAY);
+ } else {
+ CloseStream(stream, NS_ERROR_ABORT);
+ }
+ }
+}
+
+Http2Session::~Http2Session() {
+ LOG3(("Http2Session::~Http2Session %p mDownstreamState=%X", this,
+ mDownstreamState));
+
+ Shutdown();
+
+ 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, Http2Stream* 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));
+ }
+}
+
+typedef nsresult (*Http2ControlFx)(Http2Session* self);
+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)));
+
+ uint32_t nextTick = UINT32_MAX;
+ if (mCheckNetworkStallsWithTFO && mLastRequestBytesSentTime) {
+ PRIntervalTime initialResponseDelta = now - mLastRequestBytesSentTime;
+ if (initialResponseDelta >= gHttpHandler->FastOpenStallsTimeout()) {
+ gHttpHandler->IncrementFastOpenStallsCounter();
+ mCheckNetworkStallsWithTFO = false;
+ } else {
+ nextTick = PR_IntervalToSeconds(gHttpHandler->FastOpenStallsTimeout()) -
+ PR_IntervalToSeconds(initialResponseDelta);
+ }
+ }
+ if (!mPingThreshold) return nextTick;
+
+ 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 std::min(nextTick, PR_IntervalToSeconds(mPingThreshold) -
+ PR_IntervalToSeconds(now - mLastReadEpoch));
+ }
+
+ if (mPingSentEpoch) {
+ LOG3(("Http2Session::ReadTimeoutTick %p handle outstanding ping\n", this));
+ if ((now - mPingSentEpoch) >= gHttpHandler->SpdyPingTimeout()) {
+ LOG3(("Http2Session::ReadTimeoutTick %p Ping Timer Exhaustion\n", this));
+ mPingSentEpoch = 0;
+ 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(Http2Stream* 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.Put(aNewID, stream);
+
+ // If TCP fast Open has been used and conection was idle for some time
+ // we will be cautious and watch out for bug 1395494.
+ if (!mCheckNetworkStallsWithTFO && mConnection) {
+ RefPtr<HttpConnectionBase> connBase = mConnection->HttpConnection();
+ RefPtr<nsHttpConnection> conn = do_QueryObject(connBase);
+ if (conn && (conn->GetFastOpenStatus() == TFO_DATA_SENT) &&
+ gHttpHandler
+ ->CheckIfConnectionIsStalledOnlyIfIdleForThisAmountOfSeconds() &&
+ IdleTime() >=
+ gHttpHandler
+ ->CheckIfConnectionIsStalledOnlyIfIdleForThisAmountOfSeconds()) {
+ // If a connection was using the TCP FastOpen and it was idle for a
+ // long time we should check for stalls like bug 1395494.
+ mCheckNetworkStallsWithTFO = true;
+ mLastRequestBytesSentTime = PR_IntervalNow();
+ }
+ }
+
+ if (aNewID & 1) {
+ // don't count push streams here
+ MOZ_ASSERT(stream->Transaction(), "no transation for the stream!");
+ RefPtr<nsHttpConnectionInfo> ci(stream->Transaction()->ConnectionInfo());
+ if (ci && ci->GetIsTrrServiceChannel()) {
+ IncrementTrrCounter();
+ }
+ }
+ return aNewID;
+}
+
+bool Http2Session::AddStream(nsAHttpTransaction* aHttpTransaction,
+ int32_t aPriority, bool aUseTunnel,
+ bool aIsWebsocket,
+ 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();
+
+ if (aIsWebsocket) {
+ MOZ_ASSERT(!aUseTunnel, "Websocket on tunnel?!");
+ nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction();
+ MOZ_ASSERT(trans, "Websocket without transaction?!");
+ if (!trans) {
+ LOG3(("Http2Session::AddStream %p websocket without transaction. WAT?!",
+ this));
+ return true;
+ }
+
+ if (!mEnableWebsockets) {
+ LOG3(
+ ("Http2Session::AddStream %p Re-queuing websocket as h1 due to "
+ "mEnableWebsockets=false",
+ this));
+ aHttpTransaction->SetConnection(nullptr);
+ aHttpTransaction->DisableSpdy();
+ nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2Session::AddStream %p failed to reinitiate websocket "
+ "transaction (0x%08x).",
+ this, static_cast<uint32_t>(rv)));
+ }
+
+ return true;
+ }
+
+ if (!mPeerAllowsWebsockets) {
+ LOG3(("Http2Session::AddStream %p mPeerAllowsWebsockets=false", this));
+ if (!mProcessedWaitingWebsockets) {
+ LOG3(
+ ("Http2Session::AddStream %p waiting for SETTINGS to determine "
+ "fate of websocket",
+ this));
+ mWaitingWebsockets.AppendElement(aHttpTransaction);
+ mWaitingWebsocketCallbacks.AppendElement(aCallbacks);
+ } else {
+ LOG3(
+ ("Http2Session::AddStream %p Re-queuing websocket as h1 due to "
+ "mPeerAllowsWebsockets=false",
+ this));
+ aHttpTransaction->SetConnection(nullptr);
+ aHttpTransaction->DisableSpdy();
+ if (trans) {
+ nsresult rv =
+ gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2Session::AddStream %p failed to reinitiate websocket "
+ "transaction (%08x).\n",
+ this, static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+ return true;
+ }
+
+ LOG3(("Http2Session::AddStream session=%p trans=%p websocket", this,
+ aHttpTransaction));
+ CreateWebsocketStream(aHttpTransaction, aCallbacks);
+ return true;
+ }
+
+ if (aUseTunnel) {
+ LOG3(("Http2Session::AddStream session=%p trans=%p OnTunnel", this,
+ aHttpTransaction));
+ DispatchOnTunnel(aHttpTransaction, aCallbacks);
+ return true;
+ }
+
+ RefPtr<Http2Stream> refStream =
+ new Http2Stream(aHttpTransaction, this, aPriority,
+ mCurrentForegroundTabOuterContentWindowId);
+
+ LOG3(("Http2Session::AddStream session=%p stream=%p serial=%" PRIu64 " "
+ "NextID=0x%X (tentative)",
+ this, refStream.get(), mSerial, mNextStreamID));
+
+ RefPtr<Http2Stream> stream = refStream;
+ mStreamTransactionHash.Put(aHttpTransaction, std::move(refStream));
+
+ mReadyForWrite.Push(stream);
+ 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();
+ }
+
+ return true;
+}
+
+void Http2Session::QueueStream(Http2Stream* 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
+ int32_t qsize = mQueuedStreams.GetSize();
+ for (int32_t i = 0; i < qsize; i++) {
+ Http2Stream* qStream = mQueuedStreams.ObjectAt(i);
+ MOZ_ASSERT(qStream != stream);
+ MOZ_ASSERT(qStream->Queued());
+ }
+#endif
+
+ stream->SetQueued(true);
+ mQueuedStreams.Push(stream);
+}
+
+void Http2Session::ProcessPending() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ Http2Stream* stream;
+ while (RoomForMoreConcurrent() && (stream = mQueuedStreams.PopFront())) {
+ LOG3(("Http2Session::ProcessPending %p stream %p woken from queue.", this,
+ stream));
+ MOZ_ASSERT(!stream->CountAsActive());
+ MOZ_ASSERT(stream->Queued());
+ stream->SetQueued(false);
+ mReadyForWrite.Push(stream);
+ 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();
+ mCheckNetworkStallsWithTFO = false;
+ }
+ 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.GetSize();
+}
+
+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(Http2Stream* 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(Http2Stream* 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() ||
+ trans->QuerySpdyConnectTransaction()) {
+ 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(Http2Stream* 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)
+ Http2Stream* 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 (!gHttpHandler->AllowPush()) {
+ // 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 (gHttpHandler->UseH2Deps() &&
+ 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(Http2Stream* 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;
+
+ nsAHttpTransaction* trans = aStream->Transaction();
+
+ test++;
+ if (!trans) break;
+
+ test++;
+ if (mStreamTransactionHash.GetWeak(trans) != aStream) break;
+
+ if (aStream->StreamID()) {
+ Http2Stream* idStream = mStreamIDHash.Get(aStream->StreamID());
+
+ test++;
+ if (idStream != aStream) break;
+
+ if (aOptionalID) {
+ test++;
+ if (idStream->StreamID() != aOptionalID) 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
+}
+
+void Http2Session::CleanupStream(Http2Stream* 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 = aStream->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));
+ aStream->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 Http2Stream and drop the reference to
+ // its transaction
+ mStreamTransactionHash.Remove(aStream->Transaction());
+
+ 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");
+ Http2Stream* 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);
+}
+
+static void RemoveStreamFromQueue(Http2Stream* aStream,
+ nsDeque<Http2Stream>& queue) {
+ size_t size = queue.GetSize();
+ for (size_t count = 0; count < size; ++count) {
+ Http2Stream* stream = queue.PopFront();
+ if (stream != aStream) queue.Push(stream);
+ }
+}
+
+void Http2Session::RemoveStreamFromQueues(Http2Stream* aStream) {
+ RemoveStreamFromQueue(aStream, mReadyForWrite);
+ RemoveStreamFromQueue(aStream, mQueuedStreams);
+ RemoveStreamFromQueue(aStream, mPushesReadyForRead);
+ RemoveStreamFromQueue(aStream, mSlowConsumersReadyForRead);
+}
+
+void Http2Session::CloseStream(Http2Stream* aStream, nsresult aResult) {
+ 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;
+ }
+
+ RemoveStreamFromQueues(aStream);
+
+ if (aStream->IsTunnel()) {
+ UnRegisterTunnel(aStream);
+ }
+
+ // Send the stream the close() indication
+ aStream->Close(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
+ // Http2Stream::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->Transaction()->DisableSpdy();
+ CleanupStream(mInputFrameDataStream, NS_ERROR_NET_RESET, CANCEL_ERROR);
+ ResetDownstreamState();
+ return NS_OK;
+ } else 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 (auto iter = self->mStreamTransactionHash.Iter(); !iter.Done();
+ iter.Next()) {
+ iter.Data()->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);
+ }
+ } 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->ProcessWaitingWebsockets();
+ }
+
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvPushPromise(Http2Session* self) {
+ 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;
+
+ Http2Stream* 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 (!gHttpHandler->AllowPush()) {
+ // 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->mCurrentForegroundTabOuterContentWindowId));
+
+ 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;
+ } else 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<Http2Stream> pushedWeak = pushedStream.get();
+ self->mStreamTransactionHash.Put(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 = Http2Stream::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(Http2Stream::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(Http2Stream::RESERVED_BY_REMOTE);
+ static_assert(Http2Stream::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;
+ } else {
+ // reply with a ack'd ping
+ self->GeneratePing(true);
+ }
+
+ self->ResetDownstreamState();
+ return NS_OK;
+}
+
+nsresult Http2Session::RecvGoAway(Http2Session* self) {
+ 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->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 (auto iter = self->mStreamTransactionHash.Iter(); !iter.Done();
+ iter.Next()) {
+ // 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
+ auto stream = iter.UserData();
+ 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) {
+ Http2Stream* stream =
+ static_cast<Http2Stream*>(self->mGoAwayStreamsToRestart.PopFront());
+
+ if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
+ stream->Transaction()->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.
+ size = self->mQueuedStreams.GetSize();
+ for (size_t count = 0; count < size; ++count) {
+ Http2Stream* stream = self->mQueuedStreams.PopFront();
+ MOZ_ASSERT(stream->Queued());
+ stream->SetQueued(false);
+ if (self->mPeerGoAwayReason == HTTP_1_1_REQUIRED) {
+ stream->Transaction()->DisableSpdy();
+ }
+ self->CloseStream(stream, NS_ERROR_NET_RESET);
+ self->mStreamTransactionHash.Remove(stream->Transaction());
+ }
+
+ 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(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 resest 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 (auto iter = self->mStreamTransactionHash.Iter(); !iter.Done();
+ iter.Next()) {
+ MOZ_ASSERT(self->mServerSessionWindow > 0);
+
+ auto stream = iter.UserData();
+ if (!stream->BlockedOnRwin() || stream->ServerReceiveWindow() <= 0) {
+ continue;
+ }
+
+ self->mReadyForWrite.Push(stream);
+ 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, nsIInterfaceRequestor* callbacks)
+ : Runnable("net::UpdateAltSvcEvent"),
+ mHeader(header),
+ mOrigin(aOrigin),
+ mCI(aCI),
+ mCallbacks(callbacks) {}
+
+ 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->GetTopWindowOrigin(), mCI->GetPrivate(), mCI->GetIsolated(),
+ mCallbacks, mCI->ProxyInfo(), 0, mCI->GetOriginAttributes());
+ return NS_OK;
+ }
+
+ AltSvcMapping::ProcessHeader(
+ mHeader, originScheme, originHost, originPort, mCI->GetUsername(),
+ mCI->GetTopWindowOrigin(), mCI->GetPrivate(), mCI->GetIsolated(),
+ 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<nsISupports> securityInfo;
+ self->mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
+ nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
+ 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 (origin.EqualsIgnoreCase("https://", 8)) {
+ specifiedOriginHost.Assign(origin.get() + 8, origin.Length() - 8);
+ } else if (origin.EqualsIgnoreCase("http://", 7)) {
+ 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;
+ }
+ }
+
+ nsCOMPtr<nsISupports> callbacks;
+ self->mConnection->GetSecurityInfo(getter_AddRefs(callbacks));
+ nsCOMPtr<nsIInterfaceRequestor> irCallbacks = do_QueryInterface(callbacks);
+
+ RefPtr<UpdateAltSvcEvent> event =
+ new UpdateAltSvcEvent(altSvcFieldValue, origin, ci, irCallbacks);
+ 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(Http2Stream::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);
+ if (!self->mOriginFrame.Get(key)) {
+ self->mOriginFrame.Put(key, 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 Http2Stream::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 Http2Stream 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_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));
+
+ Http2Stream* stream = mReadyForWrite.PopFront();
+ 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;
+ }
+ 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 Http2Stream %p 0x%X",
+ this, stream, 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 Http2Stream %p 0x%X "
+ "block-input=%d block-output=%d\n",
+ this, stream, 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,
+ *countRead));
+ mReadyForWrite.Push(stream);
+ SetWriteCallbacks();
+ return rv;
+ }
+
+ if (stream->BlockedOnRwin()) {
+ LOG3(("Http2Session %p will stream %p 0x%X suspended for flow control\n",
+ this, stream, stream->StreamID()));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ LOG3(("Http2Session::ReadSegments %p stream=%p stream send complete", this,
+ stream));
+
+ // 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);
+ }
+
+ 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) return NS_ERROR_FAILURE;
+
+ 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.
+ Http2Stream* pushConnectedStream = mPushesReadyForRead.PopFront();
+ if (pushConnectedStream) {
+ return ProcessConnectedPush(pushConnectedStream, writer, count,
+ countWritten);
+ }
+
+ // feed gecko channels that previously stopped consuming data
+ // only take data from stored buffers
+ Http2Stream* slowConsumer = mSlowConsumersReadyForRead.PopFront();
+ 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 (auto iter = mStreamTransactionHash.Iter(); !iter.Done();
+ iter.Next()) {
+ auto stream = iter.UserData();
+ stream->Transaction()->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);
+ } else 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->Transaction()->ReuseConnectionOnRestartOK(true);
+ } else if (mDownstreamRstReason == HTTP_1_1_REQUIRED) {
+ streamCleanupCode = NS_ERROR_NET_RESET;
+ mInputFrameDataStream->Transaction()->ReuseConnectionOnRestartOK(true);
+ mInputFrameDataStream->Transaction()->DisableSpdy();
+ mInputFrameDataStream->Transaction()
+ ->MakeNonSticky(); // actully allow restart by unsticking
+ } else {
+ streamCleanupCode = mInputFrameDataStream->RecvdData()
+ ? NS_ERROR_NET_PARTIAL_TRANSFER
+ : NS_ERROR_NET_INTERRUPT;
+ }
+
+ if (mDownstreamRstReason == COMPRESSION_ERROR) {
+ mShouldGoAway = true;
+ }
+
+ // mInputFrameDataStream is reset by ChangeDownstreamState
+ Http2Stream* 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 */
+
+ 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) {
+ Http2Stream* streamToCleanup = nullptr;
+ if (mInputFrameFinal) {
+ streamToCleanup = mInputFrameDataStream;
+ }
+
+ bool discardedPadding =
+ (mDownstreamState == DISCARDING_DATA_FRAME_PADDING);
+ ResetDownstreamState();
+
+ if (streamToCleanup) {
+ if (discardedPadding && !(streamToCleanup->StreamID() & 1)) {
+ // Pushed streams are special on padding-only final data frames.
+ // See bug 1409570 comments 6-8 for details.
+ streamToCleanup->SetPushComplete();
+ Http2Stream* pushSink = streamToCleanup->GetConsumerStream();
+ if (pushSink) {
+ bool enqueueSink = true;
+ for (auto s : mPushesReadyForRead) {
+ if (s == pushSink) {
+ enqueueSink = false;
+ break;
+ }
+ }
+ if (enqueueSink) {
+ mPushesReadyForRead.Push(pushSink);
+ // 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;
+}
+
+void Http2Session::SetFastOpenStatus(uint8_t aStatus) {
+ LOG3(("Http2Session::SetFastOpenStatus %d [this=%p]", aStatus, this));
+
+ for (size_t i = 0; i < m0RTTStreams.Length(); ++i) {
+ if (m0RTTStreams[i]) {
+ m0RTTStreams[i]->Transaction()->SetFastOpenStatus(aStatus);
+ }
+ }
+}
+
+nsresult Http2Session::ProcessConnectedPush(Http2Stream* 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.
+ if (NS_SUCCEEDED(rv) && !*countWritten && pushConnectedStream->PushSource() &&
+ pushConnectedStream->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(Http2Stream* 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(Http2Stream* 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(Http2Stream* 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();
+
+ mStreamIDHash.Clear();
+ mStreamTransactionHash.Clear();
+
+ // If we have any websocket transactions waiting for settings frame,
+ // reinitiate them. This can happend if we close a h2 connection before the
+ // settings frame is received.
+ if (mWaitingWebsockets.Length()) {
+ MOZ_ASSERT(!mProcessedWaitingWebsockets);
+ MOZ_ASSERT(mWaitingWebsockets.Length() ==
+ mWaitingWebsocketCallbacks.Length());
+
+ mProcessedWaitingWebsockets = true;
+ for (size_t i = 0; i < mWaitingWebsockets.Length(); ++i) {
+ RefPtr<nsAHttpTransaction> httpTransaction = mWaitingWebsockets[i];
+ LOG3(("Http2Session::Close %p Re-queuing websocket.", this));
+ httpTransaction->SetConnection(nullptr);
+ nsHttpTransaction* trans = httpTransaction->QueryHttpTransaction();
+ if (trans) {
+ nsresult rv =
+ gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2Session::Close %p failed to reinitiate websocket "
+ "transaction (%08x).\n",
+ this, static_cast<uint32_t>(rv)));
+ }
+ } else {
+ LOG3(("Http2Session::Close %p missing transaction?!", this));
+ }
+ }
+ mWaitingWebsockets.Clear();
+ mWaitingWebsocketCallbacks.Clear();
+ }
+
+ 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<Http2Stream> 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(
+ count, mFlatHTTPResponseHeaders.Length() - mFlatHTTPResponseHeadersOut);
+ memcpy(buf, mFlatHTTPResponseHeaders.get() + mFlatHTTPResponseHeadersOut,
+ count);
+ mFlatHTTPResponseHeadersOut += count;
+ *countWritten = count;
+
+ if (mFlatHTTPResponseHeaders.Length() == mFlatHTTPResponseHeadersOut) {
+ if (!mInputFrameFinal) {
+ // 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(Http2Stream* stream) {
+ mPushesReadyForRead.Push(stream);
+ Unused << ForceRecv();
+}
+
+void Http2Session::ConnectSlowConsumer(Http2Stream* stream) {
+ LOG3(("Http2Session::ConnectSlowConsumer %p 0x%X\n", this,
+ stream->StreamID()));
+ mSlowConsumersReadyForRead.Push(stream);
+ Unused << ForceRecv();
+}
+
+uint32_t Http2Session::FindTunnelCount(nsHttpConnectionInfo* aConnInfo) {
+ return FindTunnelCount(aConnInfo->HashKey());
+}
+uint32_t Http2Session::FindTunnelCount(nsCString const& aHashKey) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ uint32_t rv = 0;
+ mTunnelHash.Get(aHashKey, &rv);
+ return rv;
+}
+
+void Http2Session::RegisterTunnel(Http2Stream* aTunnel) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsCString const& regKey = aTunnel->RegistrationKey();
+ uint32_t newcount = FindTunnelCount(regKey) + 1;
+ mTunnelHash.Remove(regKey);
+ mTunnelHash.Put(regKey, newcount);
+ LOG3(("Http2Stream::RegisterTunnel %p stream=%p tunnels=%d [%s]", this,
+ aTunnel, newcount, regKey.get()));
+}
+
+void Http2Session::UnRegisterTunnel(Http2Stream* aTunnel) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsCString const& regKey = aTunnel->RegistrationKey();
+ MOZ_ASSERT(FindTunnelCount(regKey));
+ uint32_t newcount = FindTunnelCount(regKey) - 1;
+ mTunnelHash.Remove(regKey);
+ if (newcount) {
+ mTunnelHash.Put(regKey, newcount);
+ }
+ LOG3(("Http2Session::UnRegisterTunnel %p stream=%p tunnels=%d [%s]", this,
+ aTunnel, newcount, regKey.get()));
+}
+
+void Http2Session::CreateTunnel(nsHttpTransaction* trans,
+ nsHttpConnectionInfo* ci,
+ nsIInterfaceRequestor* aCallbacks) {
+ LOG(("Http2Session::CreateTunnel %p %p make new tunnel\n", this, trans));
+ // The connect transaction will hold onto the underlying http
+ // transaction so that an auth created by the connect can be mappped
+ // to the correct security callbacks
+
+ RefPtr<nsHttpConnectionInfo> clone(ci->Clone());
+ RefPtr<SpdyConnectTransaction> connectTrans = new SpdyConnectTransaction(
+ clone, aCallbacks, trans->Caps(), trans, this, false);
+ DebugOnly<bool> rv =
+ AddStream(connectTrans, nsISupportsPriority::PRIORITY_NORMAL, false,
+ false, nullptr);
+ MOZ_ASSERT(rv);
+ RefPtr<Http2Stream> tunnel = mStreamTransactionHash.Get(connectTrans);
+ MOZ_ASSERT(tunnel);
+ RegisterTunnel(tunnel);
+}
+
+void Http2Session::DispatchOnTunnel(nsAHttpTransaction* aHttpTransaction,
+ nsIInterfaceRequestor* aCallbacks) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction();
+ nsHttpConnectionInfo* ci = aHttpTransaction->ConnectionInfo();
+ MOZ_ASSERT(trans);
+
+ LOG3(("Http2Session::DispatchOnTunnel %p trans=%p", this, trans));
+
+ aHttpTransaction->SetConnection(nullptr);
+
+ // this transaction has done its work of setting up a tunnel, let
+ // the connection manager queue it if necessary
+ trans->SetTunnelProvider(this);
+ trans->EnableKeepAlive();
+
+ if (FindTunnelCount(ci) < gHttpHandler->MaxConnectionsPerOrigin()) {
+ LOG3(("Http2Session::DispatchOnTunnel %p create on new tunnel %s", this,
+ ci->HashKey().get()));
+ CreateTunnel(trans, ci, aCallbacks);
+ } else {
+ // requeue it. The connection manager is responsible for actually putting
+ // this on the tunnel connection with the specific ci. If that can't
+ // happen the cmgr checks with us via MaybeReTunnel() to see if it should
+ // make a new tunnel or just wait longer.
+ LOG3(
+ ("Http2Session::DispatchOnTunnel %p trans=%p queue in connection "
+ "manager",
+ this, trans));
+ nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2Session::DispatchOnTunnel %p trans=%p failed to initiate "
+ "transaction (%08x)",
+ this, trans, static_cast<uint32_t>(rv)));
+ }
+ }
+}
+
+// From ASpdySession
+bool Http2Session::MaybeReTunnel(nsAHttpTransaction* aHttpTransaction) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction();
+ LOG(("Http2Session::MaybeReTunnel %p trans=%p\n", this, trans));
+ if (!trans || trans->TunnelProvider() != this) {
+ // this isn't really one of our transactions.
+ return false;
+ }
+
+ if (mClosed || mShouldGoAway) {
+ LOG(("Http2Session::MaybeReTunnel %p %p session closed - requeue\n", this,
+ trans));
+ trans->SetTunnelProvider(nullptr);
+ nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2Session::MaybeReTunnel %p trans=%p failed to initiate "
+ "transaction (%08x)",
+ this, trans, static_cast<uint32_t>(rv)));
+ }
+ return true;
+ }
+
+ nsHttpConnectionInfo* ci = aHttpTransaction->ConnectionInfo();
+ LOG(("Http2Session:MaybeReTunnel %p %p count=%d limit %d\n", this, trans,
+ FindTunnelCount(ci), gHttpHandler->MaxConnectionsPerOrigin()));
+ if (FindTunnelCount(ci) >= gHttpHandler->MaxConnectionsPerOrigin()) {
+ // patience - a tunnel will open up.
+ return false;
+ }
+
+ LOG(("Http2Session::MaybeReTunnel %p %p make new tunnel\n", this, trans));
+ CreateTunnel(trans, ci, trans->SecurityCallbacks());
+ return true;
+}
+
+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(nsISupports* securityInfo) {
+ nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
+ LOG3(("Http2Session::ALPNCallback sslsocketcontrol=%p\n", ssl.get()));
+ if (ssl) {
+ int16_t version = ssl->GetSSLVersionOffered();
+ LOG3(("Http2Session::ALPNCallback version=%x\n", version));
+
+ if (version == nsISSLSocketControl::TLS_VERSION_1_2 &&
+ !gHttpHandler->IsH2MandatorySuiteEnabled()) {
+ LOG3(("Http2Session::ALPNCallback Mandatory Cipher Suite Unavailable\n"));
+ return false;
+ }
+
+ if (version >= nsISSLSocketControl::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 (!gHttpHandler->EnforceHttp2TlsProfile()) {
+ LOG3(
+ ("Http2Session::ConfirmTLSProfile %p passed due to configuration "
+ "bypass\n",
+ this));
+ mTLSProfileConfirmed = true;
+ return NS_OK;
+ }
+
+ if (!mConnection) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISupports> securityInfo;
+ mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
+ nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo);
+ 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 < nsISSLSocketControl::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_dh && kea != ssl_kea_ecdh) {
+ 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);
+ } else 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 != nsISSLSocketControl::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<Http2Stream> 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) {
+ mReadyForWrite.Push(stream);
+ 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<Http2Stream> 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()));
+ ConnectSlowConsumer(stream);
+}
+
+void Http2Session::TransactionHasDataToWrite(Http2Stream* stream) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http2Session::TransactionHasDataToWrite %p stream=%p ID=0x%x", this,
+ stream, stream->StreamID()));
+
+ mReadyForWrite.Push(stream);
+ SetWriteCallbacks();
+ Unused << ForceSend();
+}
+
+bool Http2Session::IsPersistent() { return true; }
+
+nsresult Http2Session::TakeTransport(nsISocketTransport**,
+ nsIAsyncInputStream**,
+ nsIAsyncOutputStream**) {
+ MOZ_ASSERT(false, "TakeTransport of Http2Session");
+ return NS_ERROR_UNEXPECTED;
+}
+
+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;
+}
+
+//-----------------------------------------------------------------------------
+// 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::SetConnection(nsAHttpConnection*) {
+ // This is unexpected
+ MOZ_ASSERT(false, "Http2Session::SetConnection()");
+}
+
+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) {
+ // 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 mConnection->OnHeadersAvailable(transaction, requestHead, responseHead,
+ reset);
+}
+
+bool Http2Session::IsReused() { 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");
+
+ 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();
+ }
+ 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<nsISupports> securityInfo;
+ nsCOMPtr<nsISSLSocketControl> sslSocketControl;
+
+ mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
+ sslSocketControl = do_QueryInterface(securityInfo, &rv);
+ if (NS_FAILED(rv) || !sslSocketControl) {
+ return false;
+ }
+
+ // try all the coalescable versions we support.
+ const SpdyInformation* info = gHttpHandler->SpdyInfo();
+ static_assert(SpdyInformation::kCount == 1, "assume 1 alpn version");
+ bool joinedReturn = false;
+ if (info->ProtocolEnabled(0)) {
+ if (justKidding) {
+ rv = sslSocketControl->TestJoinConnection(info->VersionString[0],
+ hostname, port, &isJoined);
+ } else {
+ rv = sslSocketControl->JoinConnection(info->VersionString[0], 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.Put(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.Put(key2, joinedReturn);
+ }
+ }
+ return joinedReturn;
+}
+
+void Http2Session::TopLevelOuterContentWindowIdChanged(uint64_t windowId) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mCurrentForegroundTabOuterContentWindowId = windowId;
+
+ for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
+ iter.Data()->TopLevelOuterContentWindowIdChanged(windowId);
+ }
+}
+
+void Http2Session::SetCleanShutdown(bool aCleanShutdown) {
+ mCleanShutdown = aCleanShutdown;
+}
+
+void Http2Session::CreateWebsocketStream(
+ nsAHttpTransaction* aOriginalTransaction,
+ nsIInterfaceRequestor* aCallbacks) {
+ LOG(("Http2Session::CreateWebsocketStream %p %p\n", this,
+ aOriginalTransaction));
+
+ nsHttpTransaction* trans = aOriginalTransaction->QueryHttpTransaction();
+ MOZ_ASSERT(trans);
+
+ nsHttpConnectionInfo* ci = aOriginalTransaction->ConnectionInfo();
+ MOZ_ASSERT(ci);
+
+ RefPtr<nsHttpConnectionInfo> clone(ci->Clone());
+ RefPtr<SpdyConnectTransaction> connectTrans = new SpdyConnectTransaction(
+ clone, aCallbacks, trans->Caps(), trans, this, true);
+ DebugOnly<bool> rv =
+ AddStream(connectTrans, nsISupportsPriority::PRIORITY_NORMAL, false,
+ false, nullptr);
+ MOZ_ASSERT(rv);
+}
+
+void Http2Session::ProcessWaitingWebsockets() {
+ MOZ_ASSERT(!mProcessedWaitingWebsockets);
+ MOZ_ASSERT(mWaitingWebsockets.Length() ==
+ mWaitingWebsocketCallbacks.Length());
+
+ mProcessedWaitingWebsockets = true;
+
+ if (!mWaitingWebsockets.Length()) {
+ // Nothing to do here
+ LOG3(("Http2Session::ProcessWaitingWebsockets %p nothing to do", this));
+ return;
+ }
+
+ for (size_t i = 0; i < mWaitingWebsockets.Length(); ++i) {
+ RefPtr<nsAHttpTransaction> httpTransaction = mWaitingWebsockets[i];
+ nsCOMPtr<nsIInterfaceRequestor> callbacks = mWaitingWebsocketCallbacks[i];
+
+ if (mPeerAllowsWebsockets) {
+ LOG3(
+ ("Http2Session::ProcessWaitingWebsockets session=%p trans=%p "
+ "websocket",
+ this, httpTransaction.get()));
+ CreateWebsocketStream(httpTransaction, callbacks);
+ } else {
+ LOG3(
+ ("Http2Session::ProcessWaitingWebsockets %p Re-queuing websocket as "
+ "h1 due to mPeerAllowsWebsockets=false",
+ this));
+ httpTransaction->SetConnection(nullptr);
+ httpTransaction->DisableSpdy();
+ nsHttpTransaction* trans = httpTransaction->QueryHttpTransaction();
+ if (trans) {
+ nsresult rv =
+ gHttpHandler->InitiateTransaction(trans, trans->Priority());
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2Session::ProcessWaitingWebsockets %p failed to reinitiate "
+ "websocket transaction (%08x).\n",
+ this, static_cast<uint32_t>(rv)));
+ }
+ } else {
+ LOG3(("Http2Session::ProcessWaitingWebsockets %p missing transaction?!",
+ this));
+ }
+ }
+ }
+
+ mWaitingWebsockets.Clear();
+ mWaitingWebsocketCallbacks.Clear();
+}
+
+bool Http2Session::CanAcceptWebsocket() {
+ LOG3(("Http2Session::CanAcceptWebsocket %p enable=%d allow=%d processed=%d",
+ this, mEnableWebsockets, mPeerAllowsWebsockets,
+ mProcessedWaitingWebsockets));
+ if (mEnableWebsockets &&
+ (mPeerAllowsWebsockets || !mProcessedWaitingWebsockets)) {
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Session.h b/netwerk/protocol/http/Http2Session.h
new file mode 100644
index 0000000000..ddc52fc5b7
--- /dev/null
+++ b/netwerk/protocol/http/Http2Session.h
@@ -0,0 +1,609 @@
+/* -*- 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 "nsDataHashtable.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 Http2Stream;
+class nsHttpTransaction;
+
+class Http2Session final : public ASpdySession,
+ public nsAHttpConnection,
+ public nsAHttpSegmentReader,
+ public nsAHttpSegmentWriter {
+ ~Http2Session();
+
+ public:
+ 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, bool, bool,
+ 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(Http2Stream*, 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*, Http2Stream*, const char*, const char*,
+ uint32_t);
+
+ // overload of nsAHttpConnection
+ void TransactionHasDataToWrite(nsAHttpTransaction*) override;
+ void TransactionHasDataToRecv(nsAHttpTransaction*) override;
+
+ // a similar version for Http2Stream
+ void TransactionHasDataToWrite(Http2Stream*);
+
+ // an overload of nsAHttpSegementReader
+ [[nodiscard]] virtual nsresult CommitToSegmentSize(
+ uint32_t size, 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(Http2Stream* stream);
+ void ConnectPushedStream(Http2Stream* stream);
+ void ConnectSlowConsumer(Http2Stream* stream);
+
+ [[nodiscard]] nsresult ConfirmTLSProfile();
+ [[nodiscard]] static bool ALPNCallback(nsISupports* securityInfo);
+
+ 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;
+ [[nodiscard]] bool MaybeReTunnel(nsAHttpTransaction*) 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;
+ void SetFastOpenStatus(uint8_t aStatus) final;
+
+ // For use by an HTTP2Stream
+ void Received421(nsHttpConnectionInfo* ci);
+
+ void SendPriorityFrame(uint32_t streamID, uint32_t dependsOn, uint8_t weight);
+ void IncrementTrrCounter() { mTrrStreams++; }
+
+ bool CanAcceptWebsocket() override;
+
+ private:
+ Http2Session(nsISocketTransport*, enum SpdyVersion version,
+ bool attemptingEarlyData);
+
+ // 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];
+
+ [[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(Http2Stream*, nsresult, errorType);
+ void CleanupStream(uint32_t, nsresult, errorType);
+ void CloseStream(Http2Stream*, nsresult);
+ void SendHello();
+ void RemoveStreamFromQueues(Http2Stream*);
+ [[nodiscard]] nsresult ParsePadding(uint8_t&, uint16_t&);
+
+ void SetWriteCallbacks();
+ void RealignOutputQueue();
+
+ void ProcessPending();
+ [[nodiscard]] nsresult ProcessConnectedPush(Http2Stream*,
+ nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*);
+ [[nodiscard]] nsresult ProcessSlowConsumer(Http2Stream*,
+ 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(Http2Stream*, uint32_t);
+ void SetNeedsCleanup();
+
+ void UpdateLocalRwin(Http2Stream* stream, uint32_t bytes);
+ void UpdateLocalStreamWindow(Http2Stream* stream, uint32_t bytes);
+ void UpdateLocalSessionWindow(uint32_t bytes);
+
+ void MaybeDecrementConcurrent(Http2Stream* stream);
+ bool RoomForMoreConcurrent();
+ void IncrementConcurrent(Http2Stream* stream);
+ void QueueStream(Http2Stream* 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 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.
+ nsDataHashtable<nsUint32HashKey, Http2Stream*> mStreamIDHash;
+ nsRefPtrHashtable<nsPtrHashKey<nsAHttpTransaction>, Http2Stream>
+ mStreamTransactionHash;
+
+ nsDeque<Http2Stream> mReadyForWrite;
+ nsDeque<Http2Stream> mQueuedStreams;
+ nsDeque<Http2Stream> mPushesReadyForRead;
+ nsDeque<Http2Stream> 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.
+ Http2Stream* 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.
+ Http2Stream* 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<Http2Stream> 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<Http2Stream>> 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<Http2Stream>> mCannotDo0RTTStreams;
+
+ bool RealJoinConnection(const nsACString& hostname, int32_t port, bool jk);
+ bool TestOriginFrame(const nsACString& name, int32_t port);
+ bool mOriginFrameActivated;
+ nsDataHashtable<nsCStringHashKey, bool> mOriginFrame;
+
+ nsDataHashtable<nsCStringHashKey, bool> mJoinConnectionCache;
+
+ uint64_t mCurrentForegroundTabOuterContentWindowId;
+
+ uint32_t mCntActivated;
+
+ // A h2 session will be created before all socket events are trigered,
+ // e.g. NS_NET_STATUS_TLS_HANDSHAKE_ENDED and for TFO many others.
+ // We should propagate this events to the first nsHttpTransaction.
+ RefPtr<nsHttpTransaction> mFirstHttpTransaction;
+ bool mTlsHandshakeFinished;
+
+ bool mCheckNetworkStallsWithTFO;
+ PRIntervalTime mLastRequestBytesSentTime;
+
+ bool mPeerFailedHandshake;
+
+ private:
+ /// connect tunnels
+ void DispatchOnTunnel(nsAHttpTransaction*, nsIInterfaceRequestor*);
+ void CreateTunnel(nsHttpTransaction*, nsHttpConnectionInfo*,
+ nsIInterfaceRequestor*);
+ void RegisterTunnel(Http2Stream*);
+ void UnRegisterTunnel(Http2Stream*);
+ uint32_t FindTunnelCount(nsHttpConnectionInfo*);
+ uint32_t FindTunnelCount(nsCString const&);
+ nsDataHashtable<nsCStringHashKey, uint32_t> mTunnelHash;
+ uint32_t mTrrStreams;
+
+ // websockets
+ void CreateWebsocketStream(nsAHttpTransaction*, nsIInterfaceRequestor*);
+ void ProcessWaitingWebsockets();
+ 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
+ nsTArray<RefPtr<nsAHttpTransaction>>
+ mWaitingWebsockets; // Websocket transactions that may be waiting for the
+ // opening SETTINGS
+ nsCOMArray<nsIInterfaceRequestor> mWaitingWebsocketCallbacks;
+};
+
+} // 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..eed2accec5
--- /dev/null
+++ b/netwerk/protocol/http/Http2Stream.cpp
@@ -0,0 +1,1678 @@
+/* -*- 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 "Http2Stream.h"
+#include "Http2Push.h"
+#include "TunnelUtils.h"
+
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Telemetry.h"
+#include "nsAlgorithm.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsIClassOfService.h"
+#include "nsStandardURL.h"
+#include "prnetdb.h"
+
+namespace mozilla {
+namespace net {
+
+Http2Stream::Http2Stream(nsAHttpTransaction* httpTransaction,
+ Http2Session* session, int32_t priority,
+ uint64_t windowId)
+ : mStreamID(0),
+ mSession(session),
+ mSegmentReader(nullptr),
+ mSegmentWriter(nullptr),
+ mUpstreamState(GENERATING_HEADERS),
+ mState(IDLE),
+ mRequestHeadersDone(0),
+ mOpenGenerated(0),
+ mAllHeadersReceived(0),
+ mQueued(0),
+ mSocketTransport(session->SocketTransport()),
+ mCurrentForegroundTabOuterContentWindowId(windowId),
+ mTransactionTabId(0),
+ mTransaction(httpTransaction),
+ mChunkSize(session->SendingChunkSize()),
+ mRequestBlockedOnRead(0),
+ mRecvdFin(0),
+ mReceivedData(0),
+ mRecvdReset(0),
+ mSentReset(0),
+ mCountAsActive(0),
+ mSentFin(0),
+ mSentWaitingFor(0),
+ mSetTCPSocketBuffer(0),
+ mBypassInputBuffer(0),
+ mTxInlineFrameSize(Http2Session::kDefaultBufferSize),
+ mTxInlineFrameUsed(0),
+ mTxStreamFrameSize(0),
+ mRequestBodyLenRemaining(0),
+ mLocalUnacked(0),
+ mBlockedOnRwin(false),
+ mTotalSent(0),
+ mTotalRead(0),
+ mPushSource(nullptr),
+ mAttempting0RTT(false),
+ mIsTunnel(false),
+ mPlainTextTunnel(false),
+ mIsWebsocket(false) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ LOG1(("Http2Stream::Http2Stream %p trans=%p atrans=%p", this, trans,
+ httpTransaction));
+
+ 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));
+
+ if (trans) {
+ mTransactionTabId = trans->TopLevelOuterContentWindowId();
+ }
+}
+
+Http2Stream::~Http2Stream() {
+ ClearPushSource();
+ ClearTransactionsBlockedOnTunnel();
+ mStreamID = Http2Session::kDeadStreamID;
+
+ LOG3(("Http2Stream::~Http2Stream %p", this));
+}
+
+void Http2Stream::ClearPushSource() {
+ if (mPushSource) {
+ mPushSource->SetConsumerStream(nullptr);
+ mPushSource = nullptr;
+ }
+}
+
+// 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 Http2Stream::ReadSegments(nsAHttpSegmentReader* reader, uint32_t count,
+ uint32_t* countRead) {
+ LOG3(("Http2Stream %p ReadSegments reader=%p count=%d state=%x", this, reader,
+ count, mUpstreamState));
+
+ 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(
+ ("Http2Stream %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 = mTransaction->ReadSegments(this, count, countRead);
+ mSegmentReader = nullptr;
+
+ LOG3(("Http2Stream::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)
+ mSession->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(("Http2Stream %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(
+ ("Http2Stream %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)) {
+ MOZ_ASSERT(!mQueued);
+ MOZ_ASSERT(mRequestHeadersDone);
+ LOG3((
+ "Http2Stream::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);
+ mSession->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, "Http2Stream::ReadSegments unknown state");
+ break;
+ }
+
+ return rv;
+}
+
+uint64_t Http2Stream::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 Http2Stream::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 Http2Stream::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 Http2Stream::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count, uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mSegmentWriter, "segment writer in progress");
+
+ LOG3(("Http2Stream::WriteSegments %p count=%d state=%x", this, count,
+ mUpstreamState));
+
+ mSegmentWriter = writer;
+ nsresult rv = mTransaction->WriteSegments(this, 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.
+
+ // with tunnels you need to make sure that this is an underlying connction
+ // established that can be meaningfully giving this signal
+ bool doBuffer = true;
+ if (mIsTunnel) {
+ RefPtr<SpdyConnectTransaction> qiTrans(
+ mTransaction->QuerySpdyConnectTransaction());
+ if (qiTrans) {
+ doBuffer = qiTrans->ConnectedReadyForInput();
+ }
+ }
+ // stash this data
+ if (doBuffer) {
+ rv = BufferInput(count, countWritten);
+ LOG3(("Http2Stream::WriteSegments %p Buffered %" PRIX32 " %d\n", this,
+ static_cast<uint32_t>(rv), *countWritten));
+ }
+ }
+ mSegmentWriter = nullptr;
+ return rv;
+}
+
+nsresult Http2Stream::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 Http2Stream::MakeOriginURL(const nsACString& scheme,
+ const nsACString& origin,
+ nsCOMPtr<nsIURI>& url) {
+ return NS_MutateURI(new nsStandardURL::Mutator())
+ .Apply(NS_MutatorMethod(
+ &nsIStandardURLMutator::Init, nsIStandardURL::URLTYPE_AUTHORITY,
+ scheme.EqualsLiteral("http") ? NS_HTTP_DEFAULT_PORT
+ : NS_HTTPS_DEFAULT_PORT,
+ nsCString(origin), nullptr, nullptr, nullptr))
+ .Finalize(url);
+}
+
+void Http2Stream::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 = Http2Stream::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 Http2Stream::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(("Http2Stream::ParseHttpRequestHeaders %p avail=%d state=%x", this,
+ avail, mUpstreamState));
+
+ mFlatHttpRequestHeaders.Append(buf, avail);
+ nsHttpRequestHead* head = mTransaction->RequestHead();
+
+ // 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(
+ ("Http2Stream::ParseHttpRequestHeaders %p "
+ "Need more header bytes. Len = %d",
+ 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;
+
+ 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, mSession->Serial(),
+ requestURI, mOrigin, hashkey);
+
+ // check the push cache for GET
+ if (head->IsGet()) {
+ // 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())) {
+ if (pushedStream->mSession == mSession) {
+ 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, pushedStream->mSession->Serial(),
+ mSession->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",
+ mSession, 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
+ mSession->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()) {
+ mSession->IncrementTrrCounter();
+ }
+
+ return NS_OK;
+ }
+ }
+ return NS_OK;
+}
+
+// This is really a headers frame, but open is pretty clear from a workflow pov
+nsresult Http2Stream::GenerateOpen() {
+ // It is now OK to assign a streamID that we are assured will
+ // be monotonically increasing amongst new streams on this
+ // session
+ mStreamID = mSession->RegisterStreamID(this);
+ MOZ_ASSERT(mStreamID & 1, "Http2 Stream Channel ID must be odd");
+ MOZ_ASSERT(!mOpenGenerated);
+
+ mOpenGenerated = 1;
+
+ nsHttpRequestHead* head = mTransaction->RequestHead();
+ nsAutoCString requestURI;
+ head->RequestURI(requestURI);
+ LOG3(("Http2Stream %p Stream ID 0x%X [session=%p] for URI %s\n", this,
+ mStreamID, mSession, requestURI.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}
+
+ nsAutoCStringN<1025> compressedData;
+ nsAutoCString authorityHeader;
+ nsresult rv = head->GetHeader(nsHttp::Host, authorityHeader);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false);
+ return rv;
+ }
+
+ nsDependentCString scheme(head->IsHTTPS() ? "https" : "http");
+ if (head->IsConnect()) {
+ SpdyConnectTransaction* scTrans =
+ mTransaction->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(scTrans);
+ if (scTrans->IsWebsocket()) {
+ mIsWebsocket = true;
+ } else {
+ mIsTunnel = true;
+ }
+ mRequestBodyLenRemaining = 0x0fffffffffffffffULL;
+
+ if (mIsTunnel) {
+ // Our normal authority has an implicit port, best to use an
+ // explicit one with a tunnel
+ nsHttpConnectionInfo* ci = mTransaction->ConnectionInfo();
+ if (!ci) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ authorityHeader = ci->GetOrigin();
+ authorityHeader.Append(':');
+ authorityHeader.AppendInt(ci->OriginPort());
+ }
+ }
+
+ nsAutoCString method;
+ nsAutoCString path;
+ head->Method(method);
+ head->Path(path);
+ bool useSimpleConnect = head->IsConnect();
+ nsAutoCString protocol;
+ if (mIsWebsocket) {
+ useSimpleConnect = false;
+ protocol.AppendLiteral("websocket");
+ }
+ rv = mSession->Compressor()->EncodeHeaderBlock(
+ mFlatHttpRequestHeaders, method, path, authorityHeader, scheme, protocol,
+ useSimpleConnect, compressedData);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t clVal = mSession->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.
+ uint8_t firstFrameFlags = Http2Session::kFlag_PRIORITY;
+
+ if (head->IsGet() || head->IsHead()) {
+ // for GET and HEAD place the fin bit right on the
+ // header packet
+
+ SetSentFin(true);
+ 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
+ SetSentFin(true);
+ firstFrameFlags |= Http2Session::kFlag_END_STREAM;
+ }
+
+ // 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(
+ ("Http2Stream %p Generating %d bytes of HEADERS for stream 0x%X with "
+ "priority weight %u dep 0x%X frames %u uri=%s\n",
+ this, mTxInlineFrameUsed, mStreamID, mPriorityWeight,
+ mPriorityDependency, numFrames, requestURI.get()));
+
+ 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;
+
+ mSession->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());
+
+ // The size of the input headers is approximate
+ uint32_t ratio =
+ compressedData.Length() * 100 /
+ (11 + requestURI.Length() + mFlatHttpRequestHeaders.Length());
+
+ mFlatHttpRequestHeaders.Truncate();
+ Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
+ return NS_OK;
+}
+
+void Http2Stream::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.
+
+ // >0 even numbered IDs are pushed streams.
+ // odd numbered IDs are pulled streams.
+ // 0 is the sink for a pushed stream.
+ Http2Stream* stream = this;
+ if (!mStreamID) {
+ MOZ_ASSERT(mPushSource);
+ if (!mPushSource) return;
+ stream = mPushSource;
+ MOZ_ASSERT(stream->mStreamID);
+ MOZ_ASSERT(!(stream->mStreamID & 1)); // is a push stream
+
+ // If the pushed stream has recvd a FIN, there is no reason to update
+ // the window
+ if (stream->RecvdFin() || stream->RecvdReset()) return;
+ }
+
+ if (stream->mState == RESERVED_BY_REMOTE) {
+ // h2-14 prevents sending a window update in this state
+ 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;
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ if (trans && trans->InitialRwin()) {
+ bump = (trans->InitialRwin() > mClientReceiveWindow)
+ ? (trans->InitialRwin() - mClientReceiveWindow)
+ : 0;
+ } else {
+ MOZ_ASSERT(mSession->InitialRwin() >= mClientReceiveWindow);
+ bump = mSession->InitialRwin() - mClientReceiveWindow;
+ }
+
+ LOG3(("AdjustInitialwindow increased flow control window %p 0x%X %u\n", this,
+ stream->mStreamID, 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;
+
+ mSession->CreateFrameHeader(packet, 4, Http2Session::FRAME_TYPE_WINDOW_UPDATE,
+ 0, stream->mStreamID);
+
+ mClientReceiveWindow += bump;
+ bump = PR_htonl(bump);
+ memcpy(packet + Http2Session::kFrameHeaderBytes, &bump, 4);
+}
+
+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->mStreamID && !(mPushSource->mStreamID & 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;
+
+ mSession->CreateFrameHeader(packet, 5, Http2Session::FRAME_TYPE_PRIORITY, 0,
+ mPushSource->mStreamID);
+
+ 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->mStreamID, mPriorityDependency, mPriorityWeight));
+}
+
+void Http2Stream::UpdateTransportReadEvents(uint32_t count) {
+ mTotalRead += count;
+ if (!mSocketTransport) {
+ return;
+ }
+
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_RECEIVING_FROM, mTotalRead);
+}
+
+void Http2Stream::UpdateTransportSendEvents(uint32_t count) {
+ mTotalSent += count;
+
+ // 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 ((mTotalSent > bufferSize) && !mSetTCPSocketBuffer) {
+ mSetTCPSocketBuffer = 1;
+ mSocketTransport->SetSendBufferSize(bufferSize);
+ }
+
+ if (mUpstreamState != SENDING_FIN_STREAM)
+ mTransaction->OnTransportStatus(mSocketTransport, NS_NET_STATUS_SENDING_TO,
+ mTotalSent);
+
+ if (!mSentWaitingFor && !mRequestBodyLenRemaining) {
+ mSentWaitingFor = 1;
+ mTransaction->OnTransportStatus(mSocketTransport, NS_NET_STATUS_WAITING_FOR,
+ 0);
+ }
+}
+
+nsresult Http2Stream::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(("Http2Stream::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;
+
+ // 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");
+ mSession->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 = mSession->BufferOutput(reinterpret_cast<char*>(mTxInlineFrame.get()),
+ mTxInlineFrameUsed, &transmittedCount);
+ LOG3(
+ ("Http2Stream::TransmitFrame for inline BufferOutput session=%p "
+ "stream=%p result %" PRIx32 " len=%d",
+ mSession, 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(mSession, 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 (mSession->AmountOfOutputBuffered()) {
+ rv = mSession->BufferOutput(buf, mTxStreamFrameSize, &transmittedCount);
+ } else {
+ rv = mSession->OnReadSegment(buf, mTxStreamFrameSize, &transmittedCount);
+ }
+
+ LOG3(
+ ("Http2Stream::TransmitFrame for regular session=%p "
+ "stream=%p result %" PRIx32 " len=%d",
+ mSession, 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(mSession, this, "Writing from Transaction Buffer", buf,
+ transmittedCount);
+
+ *countUsed += mTxStreamFrameSize;
+ }
+
+ if (!mAttempting0RTT) {
+ mSession->FlushOutputQueue();
+ }
+
+ // calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
+ UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
+
+ mTxInlineFrameUsed = 0;
+ mTxStreamFrameSize = 0;
+
+ return NS_OK;
+}
+
+void Http2Stream::ChangeState(enum upstreamStateType newState) {
+ LOG3(("Http2Stream::ChangeState() %p from %X to %X", this, mUpstreamState,
+ newState));
+ mUpstreamState = newState;
+}
+
+void Http2Stream::GenerateDataFrameHeader(uint32_t dataLength, bool lastFrame) {
+ LOG3(("Http2Stream::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);
+ }
+
+ mSession->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 Http2Stream::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(("Http2Stream::ConvertResponseHeaders %p decode Error\n", this));
+ return rv;
+ }
+
+ nsAutoCString statusString;
+ decompressor->GetStatus(statusString);
+ if (statusString.IsEmpty()) {
+ LOG3(("Http2Stream::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(("Http2Stream::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(("Http2Stream::ConvertResponseHeaders %p response code %d\n", this,
+ httpResponseCode));
+ if (mIsTunnel) {
+ LOG3(("Http2Stream %p Tunnel Response code %d", this, httpResponseCode));
+ // 1xx response is simply skipeed and a final response is expected.
+ // 2xx response needs to be encrypted.
+ if ((httpResponseCode / 100) > 2) {
+ MapStreamToPlainText();
+ }
+ if (MapStreamToHttpConnection(aHeadersOut, httpResponseCode)) {
+ // Process transactions only if we have a final response, i.e., response
+ // code >= 200.
+ ClearTransactionsBlockedOnTunnel();
+ }
+ } else if (mIsWebsocket) {
+ LOG3(("Http2Stream %p websocket response code %d", this, httpResponseCode));
+ if (httpResponseCode == 200) {
+ MapStreamToHttpConnection(aHeadersOut);
+ }
+ }
+
+ if (httpResponseCode == 421) {
+ // Origin Frame requires 421 to remove this origin from the origin set
+ mSession->Received421(mTransaction->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()));
+ if (mIsTunnel && !mPlainTextTunnel) {
+ aHeadersOut.Truncate();
+ LOG(("Http2Stream::ConvertHeaders %p 0x%X headers removed for tunnel\n",
+ this, mStreamID));
+ }
+ return NS_OK;
+}
+
+// ConvertPushHeaders is used to convert the pushed request headers
+// into HTTP/1 format and report some telemetry
+nsresult Http2Stream::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(("Http2Stream::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(
+ ("Http2Stream::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((
+ "Http2Stream::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 Http2Stream::ConvertResponseTrailers(Http2Decompressor* decompressor,
+ nsACString& aTrailersIn) {
+ LOG3(("Http2Stream::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(("Http2Stream::ConvertResponseTrailers %p decode Error", this));
+ return rv;
+ }
+
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ if (trans) {
+ trans->SetHttpTrailers(flatTrailers);
+ } else {
+ LOG3(("Http2Stream::ConvertResponseTrailers %p no trans", this));
+ }
+
+ return NS_OK;
+}
+
+void Http2Stream::Close(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);
+}
+
+void Http2Stream::SetResponseIsComplete() {
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ if (trans) {
+ trans->SetResponseIsComplete();
+ }
+}
+
+void Http2Stream::SetAllHeadersReceived() {
+ if (mAllHeadersReceived) {
+ return;
+ }
+
+ if (mState == RESERVED_BY_REMOTE) {
+ // pushed streams needs to wait until headers have
+ // arrived to open up their window
+ LOG3(("Http2Stream::SetAllHeadersReceived %p state OPEN from reserved\n",
+ this));
+ mState = OPEN;
+ AdjustInitialWindow();
+ }
+
+ mAllHeadersReceived = 1;
+}
+
+bool Http2Stream::AllowFlowControlledWrite() {
+ return (mSession->ServerSessionWindow() > 0) && (mServerReceiveWindow > 0);
+}
+
+void Http2Stream::UpdateServerReceiveWindow(int32_t delta) {
+ mServerReceiveWindow += delta;
+
+ if (mBlockedOnRwin && AllowFlowControlledWrite()) {
+ LOG3(
+ ("Http2Stream::UpdateServerReceived UnPause %p 0x%X "
+ "Open stream window\n",
+ this, mStreamID));
+ mSession->TransactionHasDataToWrite(this);
+ }
+}
+
+void Http2Stream::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 Http2Stream::SetPriorityDependency(uint32_t newPriority,
+ uint32_t newDependency) {
+ SetPriority(newPriority);
+ mPriorityDependency = newDependency;
+}
+
+static uint32_t GetPriorityDependencyFromTransaction(nsHttpTransaction* trans) {
+ MOZ_ASSERT(trans);
+
+ uint32_t classFlags = trans->ClassOfService();
+
+ 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 Http2Stream::UpdatePriorityDependency() {
+ if (!mSession->UseH2Deps()) {
+ return;
+ }
+
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ 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() &&
+ mTransactionTabId != mCurrentForegroundTabOuterContentWindowId &&
+ mPriorityDependency != Http2Session::kUrgentStartGroupID) {
+ LOG3(
+ ("Http2Stream::UpdatePriorityDependency %p "
+ " depends on background group for trans %p\n",
+ this, trans));
+ mPriorityDependency = Http2Session::kBackgroundGroupID;
+
+ nsHttp::NotifyActiveTabLoadOptimization();
+ }
+
+ LOG1(
+ ("Http2Stream::UpdatePriorityDependency %p "
+ "depends on stream 0x%X\n",
+ this, mPriorityDependency));
+}
+
+void Http2Stream::TopLevelOuterContentWindowIdChanged(uint64_t windowId) {
+ 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;
+ }
+
+ TopLevelOuterContentWindowIdChangedInternal(windowId);
+}
+
+void Http2Stream::TopLevelOuterContentWindowIdChangedInternal(
+ uint64_t windowId) {
+ MOZ_ASSERT(gHttpHandler->ActiveTabPriority());
+
+ LOG3(
+ ("Http2Stream::TopLevelOuterContentWindowIdChanged "
+ "%p windowId=%" PRIx64 "\n",
+ this, windowId));
+
+ mCurrentForegroundTabOuterContentWindowId = windowId;
+
+ if (!mSession->UseH2Deps()) {
+ return;
+ }
+
+ // Urgent start takes an absolute precedence, so don't
+ // change mPriorityDependency here.
+ if (mPriorityDependency == Http2Session::kUrgentStartGroupID) {
+ return;
+ }
+
+ if (mTransactionTabId != mCurrentForegroundTabOuterContentWindowId) {
+ mPriorityDependency = Http2Session::kBackgroundGroupID;
+ LOG3(
+ ("Http2Stream::TopLevelOuterContentWindowIdChanged %p "
+ "move into background group.\n",
+ this));
+
+ nsHttp::NotifyActiveTabLoadOptimization();
+ } else {
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ if (!trans) {
+ return;
+ }
+
+ mPriorityDependency = GetPriorityDependencyFromTransaction(trans);
+ LOG3(
+ ("Http2Stream::TopLevelOuterContentWindowIdChanged %p "
+ "depends on stream 0x%X\n",
+ this, mPriorityDependency));
+ }
+
+ uint32_t modifyStreamID = mStreamID;
+ if (!modifyStreamID && mPushSource) {
+ modifyStreamID = mPushSource->StreamID();
+ }
+ if (modifyStreamID) {
+ mSession->SendPriorityFrame(modifyStreamID, mPriorityDependency,
+ mPriorityWeight);
+ }
+}
+
+void Http2Stream::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 Http2Stream::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 Http2Stream::SetRecvdReset(bool aStatus) {
+ mRecvdReset = aStatus ? 1 : 0;
+ if (!aStatus) return;
+ mState = CLOSED;
+}
+
+void Http2Stream::SetSentReset(bool aStatus) {
+ mSentReset = aStatus ? 1 : 0;
+ if (!aStatus) return;
+ mState = CLOSED;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentReader
+//-----------------------------------------------------------------------------
+
+nsresult Http2Stream::OnReadSegment(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ LOG3(("Http2Stream::OnReadSegment %p count=%d state=%x", this, count,
+ mUpstreamState));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mSegmentReader, "OnReadSegment with null mSegmentReader");
+
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ uint32_t dataLength;
+
+ 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 (!mSession->TryToActivate(this)) {
+ LOG3(("Http2Stream::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(
+ ("Http2Stream this=%p, id 0x%X request body suspended because "
+ "remote window is stream=%" PRId64 " session=%" PRId64 ".\n",
+ this, mStreamID, mServerReceiveWindow,
+ mSession->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 > mSession->ServerSessionWindow())
+ dataLength = static_cast<uint32_t>(mSession->ServerSessionWindow());
+
+ if (dataLength > mServerReceiveWindow)
+ dataLength = static_cast<uint32_t>(mServerReceiveWindow);
+
+ LOG3(
+ ("Http2Stream 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,
+ mSession->ServerSessionWindow(), Http2Session::kMaxFrameData,
+ dataLength));
+
+ mSession->DecrementServerSessionWindow(dataLength);
+ mServerReceiveWindow -= dataLength;
+
+ LOG3(("Http2Stream %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(mPushSource);
+ rv = TransmitFrame(nullptr, nullptr, true);
+ break;
+
+ default:
+ MOZ_ASSERT(false, "Http2Stream::OnReadSegment non-write state");
+ break;
+ }
+
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// nsAHttpSegmentWriter
+//-----------------------------------------------------------------------------
+
+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;
+
+ mSession->ConnectPushedStream(this);
+ return NS_OK;
+ }
+
+ // 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(
+ ("Http2Stream::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);
+}
+
+/// connect tunnels
+
+nsCString& Http2Stream::RegistrationKey() {
+ if (mRegistrationKey.IsEmpty()) {
+ MOZ_ASSERT(Transaction());
+ MOZ_ASSERT(Transaction()->ConnectionInfo());
+
+ mRegistrationKey = Transaction()->ConnectionInfo()->HashKey();
+ }
+
+ return mRegistrationKey;
+}
+
+void Http2Stream::ClearTransactionsBlockedOnTunnel() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (!mIsTunnel) {
+ return;
+ }
+ nsresult rv =
+ gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo());
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http2Stream::ClearTransactionsBlockedOnTunnel %p\n"
+ " ProcessPendingQ failed: %08x\n",
+ this, static_cast<uint32_t>(rv)));
+ }
+}
+
+void Http2Stream::MapStreamToPlainText() {
+ RefPtr<SpdyConnectTransaction> qiTrans(
+ mTransaction->QuerySpdyConnectTransaction());
+ MOZ_ASSERT(qiTrans);
+ mPlainTextTunnel = true;
+ qiTrans->ForcePlainText();
+}
+
+bool Http2Stream::MapStreamToHttpConnection(const nsACString& aFlat407Headers,
+ int32_t aHttpResponseCode) {
+ RefPtr<SpdyConnectTransaction> qiTrans(
+ mTransaction->QuerySpdyConnectTransaction());
+ MOZ_ASSERT(qiTrans);
+
+ return qiTrans->MapStreamToHttpConnection(
+ mSocketTransport, mTransaction->ConnectionInfo(), aFlat407Headers,
+ mIsTunnel ? aHttpResponseCode : -1);
+}
+
+// -----------------------------------------------------------------------------
+// mirror nsAHttpTransaction
+// -----------------------------------------------------------------------------
+
+bool Http2Stream::Do0RTT() {
+ MOZ_ASSERT(mTransaction);
+ mAttempting0RTT = mTransaction->Do0RTT();
+ return mAttempting0RTT;
+}
+
+nsresult Http2Stream::Finish0RTT(bool aRestart, bool aAlpnChanged) {
+ MOZ_ASSERT(mTransaction);
+ 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 = mTransaction->Finish0RTT(aAlpnChanged, aAlpnChanged);
+ if (aRestart) {
+ nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
+ if (trans) {
+ trans->Refused0RTT();
+ }
+ }
+ return rv;
+}
+
+nsresult Http2Stream::GetOriginAttributes(mozilla::OriginAttributes* oa) {
+ if (!mSocketTransport) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return mSocketTransport->GetOriginAttributes(oa);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http2Stream.h b/netwerk/protocol/http/Http2Stream.h
new file mode 100644
index 0000000000..197ccfce6a
--- /dev/null
+++ b/netwerk/protocol/http/Http2Stream.h
@@ -0,0 +1,395 @@
+/* -*- 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_Http2Stream_h
+#define mozilla_net_Http2Stream_h
+
+// HTTP/2 - RFC7540
+// https://www.rfc-editor.org/rfc/rfc7540.txt
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsAHttpTransaction.h"
+#include "nsISupportsPriority.h"
+#include "SimpleBuffer.h"
+#include "nsISupportsImpl.h"
+
+class nsIInputStream;
+class nsIOutputStream;
+
+namespace mozilla {
+class OriginAttributes;
+}
+
+namespace mozilla {
+namespace net {
+
+class nsStandardURL;
+class Http2Session;
+class Http2Decompressor;
+
+class Http2Stream : public nsAHttpSegmentReader,
+ public nsAHttpSegmentWriter,
+ public SupportsWeakPtr {
+ public:
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Http2Stream, override)
+
+ 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;
+
+ Http2Stream(nsAHttpTransaction*, Http2Session*, int32_t, uint64_t);
+
+ uint32_t StreamID() { return mStreamID; }
+ Http2PushedStream* PushSource() { return mPushSource; }
+ void ClearPushSource();
+
+ 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);
+
+ // The consumer stream is the synthetic pull stream hooked up to this stream
+ // http2PushedStream overrides it
+ virtual Http2Stream* GetConsumerStream() { return nullptr; };
+
+ 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; }
+
+ nsAHttpTransaction* Transaction() { return mTransaction; }
+ virtual nsIRequestContext* RequestContext() {
+ return mTransaction ? mTransaction->RequestContext() : nullptr;
+ }
+
+ void Close(nsresult reason);
+ 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 ConvertPushHeaders(Http2Decompressor*, nsACString&,
+ nsACString&);
+ [[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 TransactionTabId() { return mTransactionTabId; }
+
+ // 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; }
+
+ // This is a no-op on pull streams. Pushed streams override this.
+ virtual void SetPushComplete(){};
+
+ Http2Session* Session() { return mSession; }
+
+ [[nodiscard]] static nsresult MakeOriginURL(const nsACString& origin,
+ nsCOMPtr<nsIURI>& url);
+
+ [[nodiscard]] static nsresult MakeOriginURL(const nsACString& scheme,
+ const nsACString& origin,
+ nsCOMPtr<nsIURI>& url);
+
+ // Mirrors nsAHttpTransaction
+ bool Do0RTT();
+ nsresult Finish0RTT(bool aRestart, bool aAlpnIgnored);
+
+ nsresult GetOriginAttributes(mozilla::OriginAttributes* oa);
+
+ virtual void TopLevelOuterContentWindowIdChanged(uint64_t windowId);
+ void TopLevelOuterContentWindowIdChangedInternal(
+ uint64_t windowId); // For use by pushed streams only
+
+ protected:
+ virtual ~Http2Stream();
+ static void CreatePushHashKey(
+ const nsCString& scheme, const nsCString& hostHeader,
+ const mozilla::OriginAttributes& originAttributes, uint64_t serial,
+ const nsACString& pathInfo, nsCString& outOrigin, nsCString& outKey);
+
+ // These internal states track request generation
+ enum upstreamStateType {
+ GENERATING_HEADERS,
+ GENERATING_BODY,
+ SENDING_BODY,
+ SENDING_FIN_STREAM,
+ UPSTREAM_COMPLETE
+ };
+
+ uint32_t mStreamID;
+
+ // The session that this stream is a subset of
+ Http2Session* 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.
+ nsAHttpSegmentReader* mSegmentReader;
+ nsAHttpSegmentWriter* mSegmentWriter;
+
+ 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;
+
+ // The HTTP/2 state for the stream from section 5.1
+ enum stateType mState;
+
+ // 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
+ nsISocketTransport* mSocketTransport;
+
+ uint8_t mPriorityWeight; // h2 weight
+ uint32_t mPriorityDependency; // h2 stream id this one depends on
+ uint64_t mCurrentForegroundTabOuterContentWindowId;
+ uint64_t mTransactionTabId;
+
+ private:
+ friend class mozilla::DefaultDelete<Http2Stream>;
+
+ [[nodiscard]] nsresult ParseHttpRequestHeaders(const char*, uint32_t,
+ uint32_t*);
+ [[nodiscard]] nsresult GenerateOpen();
+
+ void AdjustPushedPriority();
+ void GenerateDataFrameHeader(uint32_t, bool);
+
+ [[nodiscard]] nsresult BufferInput(uint32_t, uint32_t*);
+
+ // 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;
+
+ // 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;
+
+ // The InlineFrame and associated data is used for composing control
+ // frames and data frame headers.
+ UniquePtr<uint8_t[]> mTxInlineFrame;
+ uint32_t mTxInlineFrameSize;
+ uint32_t mTxInlineFrameUsed;
+
+ // 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;
+
+ // 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;
+
+ uint32_t mPriority; // geckoish weight
+
+ // 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;
+
+ // True when sending is suspended becuase the server receive window is
+ // <= 0
+ bool mBlockedOnRwin;
+
+ // For Progress Events
+ uint64_t mTotalSent;
+ uint64_t mTotalRead;
+
+ // For Http2Push
+ Http2PushedStream* mPushSource;
+
+ // Used to store stream data when the transaction channel cannot keep up
+ // and flow control has not yet kicked in.
+ SimpleBuffer mSimpleBuffer;
+
+ bool mAttempting0RTT;
+
+ /// connect tunnels
+ public:
+ bool IsTunnel() { return mIsTunnel; }
+ // TODO - remove as part of bug 1564120 fix?
+ // This method saves the key the tunnel was registered under, so that when the
+ // associated transaction connection info hash key changes, we still find it
+ // and decrement the correct item in the session's tunnel hash table.
+ nsCString& RegistrationKey();
+
+ private:
+ void ClearTransactionsBlockedOnTunnel();
+ void MapStreamToPlainText();
+ bool MapStreamToHttpConnection(const nsACString& aFlat407Headers,
+ int32_t aHttpResponseCode = -1);
+
+ bool mIsTunnel;
+ bool mPlainTextTunnel;
+ nsCString mRegistrationKey;
+
+ /// websockets
+ public:
+ bool IsWebsocket() { return mIsWebsocket; }
+
+ private:
+ bool mIsWebsocket;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http2Stream_h
diff --git a/netwerk/protocol/http/Http3Session.cpp b/netwerk/protocol/http/Http3Session.cpp
new file mode 100644
index 0000000000..9674356808
--- /dev/null
+++ b/netwerk/protocol/http/Http3Session.cpp
@@ -0,0 +1,1715 @@
+/* -*- 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 "HttpLog.h"
+#include "Http3Session.h"
+#include "Http3Stream.h"
+#include "mozilla/net/DNS.h"
+#include "nsHttpHandler.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Telemetry.h"
+#include "ASpdySession.h" // because of SoftStreamError()
+#include "nsIOService.h"
+#include "nsISSLSocketControl.h"
+#include "ScopedNSSTypes.h"
+#include "nsQueryObject.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "QuicSocketControl.h"
+#include "SSLServerCertVerification.h"
+#include "SSLTokensCache.h"
+#include "HttpConnectionUDP.h"
+#include "sslerr.h"
+
+namespace mozilla {
+namespace 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;
+
+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()
+ : mState(INITIALIZING),
+ mAuthenticationStarted(false),
+ mCleanShutdown(false),
+ mGoawayReceived(false),
+ mShouldClose(false),
+ mIsClosedByNeqo(false),
+ mError(NS_OK),
+ mSocketError(NS_OK),
+ mBeforeConnectedError(false),
+ mTimerActive(false) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("Http3Session::Http3Session [this=%p]", this));
+
+ mCurrentForegroundTabOuterContentWindowId =
+ gHttpHandler->ConnMgr()->CurrentTopLevelOuterContentWindowId();
+}
+
+nsresult Http3Session::Init(const nsHttpConnectionInfo* aConnInfo,
+ nsISocketTransport* aSocketTransport,
+ HttpConnectionUDP* readerWriter) {
+ LOG3(("Http3Session::Init %p", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(aSocketTransport);
+ MOZ_ASSERT(readerWriter);
+
+ mConnInfo = aConnInfo->Clone();
+ mSocketTransport = aSocketTransport;
+
+ nsCOMPtr<nsISupports> info;
+ Unused << mSocketTransport->GetSecurityInfo(getter_AddRefs(info));
+ mSocketControl = do_QueryObject(info);
+
+ // Get the local and remote address neqo needs it.
+ NetAddr selfAddr;
+ if (NS_FAILED(mSocketTransport->GetSelfAddr(&selfAddr))) {
+ LOG3(("Http3Session::Init GetSelfAddr failed [this=%p]", this));
+ return NS_ERROR_FAILURE;
+ }
+ char buf[kIPv6CStrBufSize];
+ selfAddr.ToStringBuffer(buf, kIPv6CStrBufSize);
+
+ nsAutoCString selfAddrStr;
+ if (selfAddr.raw.family == AF_INET6) {
+ selfAddrStr.Append("[");
+ }
+ // Append terminating ']' and port.
+ selfAddrStr.Append(buf, strlen(buf));
+ if (selfAddr.raw.family == AF_INET6) {
+ selfAddrStr.Append("]:");
+ selfAddrStr.AppendInt(ntohs(selfAddr.inet6.port));
+ } else {
+ selfAddrStr.Append(":");
+ selfAddrStr.AppendInt(ntohs(selfAddr.inet.port));
+ }
+
+ NetAddr peerAddr;
+ if (NS_FAILED(mSocketTransport->GetPeerAddr(&peerAddr))) {
+ LOG3(("Http3Session::Init GetPeerAddr failed [this=%p]", this));
+ return NS_ERROR_FAILURE;
+ }
+ peerAddr.ToStringBuffer(buf, kIPv6CStrBufSize);
+
+ nsAutoCString peerAddrStr;
+ if (peerAddr.raw.family == AF_INET6) {
+ peerAddrStr.Append("[");
+ }
+ peerAddrStr.Append(buf, strlen(buf));
+ // Append terminating ']' and port.
+ if (peerAddr.raw.family == AF_INET6) {
+ peerAddrStr.Append("]:");
+ peerAddrStr.AppendInt(ntohs(peerAddr.inet6.port));
+ } else {
+ peerAddrStr.Append(':');
+ peerAddrStr.AppendInt(ntohs(peerAddr.inet.port));
+ }
+
+ LOG3(
+ ("Http3Session::Init origin=%s, alpn=%s, selfAddr=%s, peerAddr=%s,"
+ " qpack table size=%u, max blocked streams=%u [this=%p]",
+ PromiseFlatCString(mConnInfo->GetOrigin()).get(),
+ PromiseFlatCString(mConnInfo->GetNPNToken()).get(), selfAddrStr.get(),
+ peerAddrStr.get(), gHttpHandler->DefaultQpackTableSize(),
+ gHttpHandler->DefaultHttp3MaxBlockedStreams(), this));
+
+ nsresult rv = NeqoHttp3Conn::Init(
+ mConnInfo->GetOrigin(), mConnInfo->GetNPNToken(), selfAddrStr,
+ peerAddrStr, gHttpHandler->DefaultQpackTableSize(),
+ gHttpHandler->DefaultHttp3MaxBlockedStreams(),
+ gHttpHandler->Http3QlogDir(), getter_AddRefs(mHttp3Connection));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString peerId;
+ mSocketControl->GetPeerId(peerId);
+ nsTArray<uint8_t> token;
+ if (NS_SUCCEEDED(SSLTokensCache::Get(peerId, token))) {
+ LOG(("Found a resumption token in the cache."));
+ mHttp3Connection->SetResumptionToken(token);
+ if (mHttp3Connection->IsZeroRtt()) {
+ LOG(("Can send ZeroRtt data"));
+ RefPtr<Http3Session> self(this);
+ mState = ZERORTT;
+ // 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");
+ }
+ }
+
+ // 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.
+ mSegmentReaderWriter = readerWriter;
+ return NS_OK;
+}
+
+// Shutdown the http3session and close all transactions.
+void Http3Session::Shutdown() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if ((mBeforeConnectedError ||
+ (mError == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR)) &&
+ (mError !=
+ mozilla::psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERT_DOMAIN))) {
+ gHttpHandler->ExcludeHttp3(mConnInfo);
+ }
+
+ for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<Http3Stream> stream = iter.Data();
+
+ 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));
+ 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->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 {
+ stream->Close(NS_ERROR_ABORT);
+ }
+ RemoveStreamFromQueues(stream);
+ if (stream->HasStreamId()) {
+ mStreamIdHash.Remove(stream->StreamId());
+ }
+ }
+
+ mStreamTransactionHash.Clear();
+}
+
+Http3Session::~Http3Session() {
+ LOG3(("Http3Session::~Http3Session %p", this));
+
+ 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);
+
+ 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::OnInputStreamReady ->
+// HttpConnectionUDP::OnSocketReadable ->
+// Http3Session::WriteSegmentsAgain
+nsresult Http3Session::ProcessInput(uint32_t* aCountRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mSegmentReaderWriter);
+
+ LOG(("Http3Session::ProcessInput writer=%p [this=%p state=%d]",
+ mSegmentReaderWriter.get(), this, mState));
+
+ uint8_t packet[UDP_MAX_PACKET_SIZE];
+ nsresult rv = NS_OK;
+ // Read from socket until NS_BASE_STREAM_WOULD_BLOCK or another error.
+ do {
+ uint32_t read = 0;
+ rv = mSegmentReaderWriter->OnWriteSegment((char*)packet,
+ UDP_MAX_PACKET_SIZE, &read);
+ if (NS_SUCCEEDED(rv)) {
+ mHttp3Connection->ProcessInput(packet, read);
+ *aCountRead += read;
+ }
+ } while (NS_SUCCEEDED(rv));
+ // NS_BASE_STREAM_WOULD_BLOCK means that there is no more date to read.
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ return NS_OK;
+ }
+
+ LOG(("Http3Session::ProcessInput error=%" PRIx32 " [this=%p]",
+ static_cast<uint32_t>(rv), this));
+ if (NS_SUCCEEDED(mSocketError)) {
+ mSocketError = rv;
+ }
+ return rv;
+}
+
+nsresult Http3Session::ProcessTransactionRead(uint64_t stream_id,
+ uint32_t count,
+ uint32_t* countWritten) {
+ RefPtr<Http3Stream> 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, count, countWritten);
+}
+
+nsresult Http3Session::ProcessTransactionRead(Http3Stream* stream,
+ uint32_t count,
+ uint32_t* countWritten) {
+ nsresult rv = stream->WriteSegments(stream, count, countWritten);
+
+ 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(uint32_t count) {
+ 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<Http3Stream> stream = mStreamIdHash.Get(id);
+ if (!stream) {
+ LOG(
+ ("Http3Session::ProcessEvents - HeaderReady - stream not found "
+ "stream_id=0x%" PRIx64 " [this=%p].",
+ id, this));
+ continue;
+ }
+
+ stream->SetResponseHeaders(data, event.header_ready.fin);
+
+ uint32_t read = 0;
+ rv = ProcessTransactionRead(stream, count, &read);
+
+ if (NS_FAILED(rv)) {
+ LOG(("Http3Session::ProcessEvents [this=%p] rv=%" PRIx32, this,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ break;
+ }
+ case Http3Event::Tag::DataReadable: {
+ MOZ_ASSERT(mState == CONNECTED);
+ LOG(("Http3Session::ProcessEvents - DataReadable"));
+ uint64_t id = event.data_readable.stream_id;
+
+ uint32_t read = 0;
+ nsresult rv = ProcessTransactionRead(id, count, &read);
+
+ 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(CanSandData());
+ LOG(("Http3Session::ProcessEvents - DataWritable"));
+
+ RefPtr<Http3Stream> stream =
+ mStreamIdHash.Get(event.data_writable.stream_id);
+ if (stream) {
+ StreamReadyToWrite(stream);
+ }
+ } break;
+ case Http3Event::Tag::Reset:
+ LOG(("Http3Session::ProcessEvents - Reset"));
+ ResetRecvd(event.reset.stream_id, event.reset.error);
+ break;
+ case Http3Event::Tag::StopSending:
+ LOG(("Http3Session::ProcessEvents - StopSeniding with error 0x%" PRIx64,
+ event.stop_sending.error));
+ if (event.stop_sending.error == HTTP3_APP_ERROR_NO_ERROR) {
+ RefPtr<Http3Stream> stream =
+ mStreamIdHash.Get(event.data_writable.stream_id);
+ if (stream) {
+ stream->StopSending();
+ }
+ } else {
+ ResetRecvd(event.reset.stream_id, event.reset.error);
+ }
+ 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();
+ }
+ break;
+ case Http3Event::Tag::ZeroRttRejected:
+ LOG(("Http3Session::ProcessEvents - ZeroRttRejected"));
+ if (mState == ZERORTT) {
+ mState = INITIALIZING;
+ Finish0Rtt(true);
+ }
+ break;
+ case Http3Event::Tag::ResumptionToken: {
+ LOG(("Http3Session::ProcessEvents - ResumptionToken"));
+ if (!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);
+ }
+
+ 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();
+ } break;
+ case Http3Event::Tag::GoawayReceived:
+ LOG(("Http3Session::ProcessEvents - GoawayReceived"));
+ 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);
+ }
+ return mError;
+ break;
+ case Http3Event::Tag::ConnectionClosed:
+ LOG(("Http3Session::ProcessEvents - ConnectionClosed"));
+ if (NS_SUCCEEDED(mError)) {
+ mError = NS_ERROR_NET_TIMEOUT;
+ CloseConnectionTelemetry(event.connection_closed.error, false);
+ }
+ mIsClosedByNeqo = true;
+ 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;
+ 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::OnOutputStreamReady ->
+// HttpConnectionUDP::OnSocketWritable ->
+// Http3Session::ReadSegmentsAgain
+nsresult Http3Session::ProcessOutput() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mSegmentReaderWriter);
+
+ LOG(("Http3Session::ProcessOutput reader=%p, [this=%p]",
+ mSegmentReaderWriter.get(), this));
+
+ // Process neqo.
+ uint64_t timeout = mHttp3Connection->ProcessOutput();
+
+ // Check if we have a packet that could not have been sent in a previous
+ // iteration or maybe get new packets to send.
+ while (mPacketToSend.Length() ||
+ NS_SUCCEEDED(mHttp3Connection->GetDataToSend(mPacketToSend))) {
+ MOZ_ASSERT(mPacketToSend.Length());
+ LOG(("Http3Session::ProcessOutput sending packet with %u bytes [this=%p].",
+ (uint32_t)mPacketToSend.Length(), this));
+ uint32_t written = 0;
+ nsresult rv = mSegmentReaderWriter->OnReadSegment(
+ (const char*)mPacketToSend.Elements(), mPacketToSend.Length(),
+ &written);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ // The socket is blocked, keep the packet and we will send it when the
+ // socket is ready to send data again.
+ if (mConnection) {
+ Unused << mConnection->ResumeSend();
+ }
+ SetupTimer(timeout);
+ return NS_OK;
+ }
+ if (NS_FAILED(rv)) {
+ mSocketError = rv;
+ // Ok the socket is blocked or there is an error, return from here,
+ // we do not need to set a timer if error is not
+ // NS_BASE_STREAM_WOULD_BLOCK, i.e. we are closing the connection.
+ return rv;
+ }
+ MOZ_ASSERT(written == mPacketToSend.Length());
+ mPacketToSend.TruncateLength(0);
+ }
+
+ SetupTimer(timeout);
+ return NS_OK;
+}
+
+// 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() {
+ 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 = ProcessOutput();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return ProcessEvents(nsIOService::gDefaultSegmentSize);
+}
+
+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;
+ }
+
+ LOG(("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;
+ }
+
+ if (!mTimer) {
+ mTimer = NS_NewTimer();
+ }
+
+ mTimerActive = true;
+
+ if (!mTimer ||
+ NS_FAILED(mTimer->InitWithNamedFuncCallback(
+ &HttpConnectionUDP::OnQuicTimeout, mSegmentReaderWriter, aTimeout,
+ nsITimer::TYPE_ONE_SHOT, "net::HttpConnectionUDP::OnQuicTimeout"))) {
+ NS_DispatchToCurrentThread(NewRunnableMethod(
+ "net::HttpConnectionUDP::OnQuicTimeoutExpired", mSegmentReaderWriter,
+ &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();
+
+ LOG3(("Http3Session::AddStream %p atrans=%p.\n", this, aHttpTransaction));
+ Http3Stream* stream = new Http3Stream(aHttpTransaction, this);
+ mStreamTransactionHash.Put(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);
+ }
+ return true;
+ } else {
+ m0RTTStreams.AppendElement(stream);
+ }
+ }
+
+ if (!mFirstHttpTransaction && !IsConnected()) {
+ mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction();
+ LOG3(("Http3Session::AddStream first session=%p trans=%p ", this,
+ mFirstHttpTransaction.get()));
+ }
+
+ StreamReadyToWrite(stream);
+
+ return true;
+}
+
+bool Http3Session::CanReuse() {
+ return CanSandData() && !(mGoawayReceived || mShouldClose);
+}
+
+void Http3Session::QueueStream(Http3Stream* 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");
+
+ Http3Stream* stream;
+ while ((stream = mQueuedStreams.PopFront())) {
+ LOG3(("Http3Session::ProcessPending %p stream %p woken from queue.", this,
+ stream));
+ MOZ_ASSERT(stream->Queued());
+ stream->SetQueued(false);
+ mReadyForWrite.Push(stream);
+ }
+ MaybeResumeSend();
+}
+
+static void RemoveStreamFromQueue(Http3Stream* aStream,
+ nsDeque<Http3Stream>& queue) {
+ size_t size = queue.GetSize();
+ for (size_t count = 0; count < size; ++count) {
+ Http3Stream* stream = queue.PopFront();
+ if (stream != aStream) {
+ queue.Push(stream);
+ }
+ }
+}
+
+void Http3Session::RemoveStreamFromQueues(Http3Stream* 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, Http3Stream* 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 = mHttp3Connection->Fetch(aMethod, aScheme, 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()) {
+ // TODO: investigate why this is failing MOZ_ASSERT(mConnectionIdleStart);
+ MOZ_ASSERT(mFirstStreamIdReuseIdleConnection.isNothing());
+
+ mConnectionIdleEnd = TimeStamp::Now();
+ mFirstStreamIdReuseIdleConnection = Some(*aStreamId);
+ }
+ mStreamIdHash.Put(*aStreamId, RefPtr{aStream});
+ mTransactionCount++;
+
+ 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::ResetRecvd(uint64_t aStreamId, uint64_t aError) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ RefPtr<Http3Stream> stream = mStreamIdHash.Get(aStreamId);
+ if (!stream) {
+ return;
+ }
+
+ stream->SetRecvdReset();
+
+ // 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.
+ stream->Transaction()->DisableHttp3();
+ stream->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.
+ stream->Transaction()->DoNotRemoveAltSvc();
+ CloseStream(stream, NS_ERROR_NET_RESET);
+ } else {
+ if (stream->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) {
+ bool again = false;
+ return ReadSegmentsAgain(reader, count, countRead, &again);
+}
+
+nsresult Http3Session::ReadSegmentsAgain(nsAHttpSegmentReader* reader,
+ uint32_t count, uint32_t* countRead,
+ bool* again) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("Http3Session::ReadSegmentsAgain [this=%p]", this));
+ *again = false;
+
+ *countRead = 0;
+
+ // 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).
+
+ nsresult rv = NS_OK;
+ Http3Stream* stream = nullptr;
+
+ // Step 1)
+ while (CanSandData() && (stream = mReadyForWrite.PopFront())) {
+ LOG(
+ ("Http3Session::ReadSegmentsAgain call ReadSegments from stream=%p "
+ "[this=%p]",
+ stream, this));
+
+ rv = stream->ReadSegments(this);
+
+ // on stream error we return earlier to let the error be handled.
+ if (NS_FAILED(rv)) {
+ LOG3(("Http3Session::ReadSegmentsAgain %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::ReadSegments %p soft error override\n", this));
+ rv = NS_OK;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ // Step 2:
+ // Call actuall network write.
+ rv = ProcessOutput();
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ if (mConnection) {
+ Unused << mConnection->ResumeRecv();
+ }
+
+ // Step 3:
+ MaybeResumeSend();
+ }
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = NS_OK;
+ }
+
+ return rv;
+}
+
+void Http3Session::StreamReadyToWrite(Http3Stream* aStream) {
+ MOZ_ASSERT(aStream);
+ mReadyForWrite.Push(aStream);
+ if (CanSandData() && mConnection) {
+ Unused << mConnection->ResumeSend();
+ }
+}
+
+void Http3Session::MaybeResumeSend() {
+ if ((mReadyForWrite.GetSize() > 0) && CanSandData() && mConnection) {
+ Unused << mConnection->ResumeSend();
+ }
+}
+
+nsresult Http3Session::ProcessSlowConsumers() {
+ if (mSlowConsumersReadyForRead.IsEmpty()) {
+ return NS_OK;
+ }
+
+ RefPtr<Http3Stream> slowConsumer = mSlowConsumersReadyForRead.ElementAt(0);
+ mSlowConsumersReadyForRead.RemoveElementAt(0);
+
+ uint32_t countRead = 0;
+ nsresult rv = ProcessTransactionRead(
+ slowConsumer, nsIOService::gDefaultSegmentSize, &countRead);
+
+ return rv;
+}
+
+nsresult Http3Session::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count, uint32_t* countWritten) {
+ bool again = false;
+ return WriteSegmentsAgain(writer, count, countWritten, &again);
+}
+
+nsresult Http3Session::WriteSegmentsAgain(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten, bool* again) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ *again = false;
+
+ // 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;
+ }
+
+ rv = ProcessInput(countWritten);
+ if (NS_FAILED(rv)) {
+ LOG3(("Http3Session %p processInput returns 0x%" PRIx32 "\n", this,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ rv = ProcessEvents(count);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (mConnection) {
+ Unused << mConnection->ResumeRecv();
+ }
+
+ // Update timeout and check for new packets to be written.
+ // We call ResumeSend to trigger writes if needed.
+ uint64_t timeout = mHttp3Connection->ProcessOutput();
+
+ if (mHttp3Connection->HasDataToSend() && mConnection) {
+ Unused << mConnection->ResumeSend();
+ }
+ SetupTimer(timeout);
+
+ return NS_OK;
+}
+
+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 "closing" key and 37 in the
+ // graph.
+ Telemetry::Accumulate(Telemetry::HTTP3_CONNECTTION_CLOSE_CODE, "closing"_ns,
+ 37);
+ 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();
+ }
+ mConnection = nullptr;
+ mSocketTransport = nullptr;
+ mSegmentReaderWriter = 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;
+ }
+ 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<Http3Stream> 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(Http3Stream* aStream, nsresult aResult) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ if (!aStream->RecvdFin() && !aStream->RecvdReset() &&
+ (aStream->HasStreamId())) {
+ mHttp3Connection->ResetStream(aStream->StreamId(),
+ HTTP3_APP_ERROR_REQUEST_CANCELLED);
+ }
+ aStream->Close(aResult);
+ if (aStream->HasStreamId()) {
+ // We know the transaction reusing an idle connection has succeeded or
+ // failed.
+ if (mFirstStreamIdReuseIdleConnection.isSome() &&
+ aStream->StreamId() == *mFirstStreamIdReuseIdleConnection) {
+ // TODO: investigate why this is failing 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);
+ mStreamTransactionHash.Remove(aStream->Transaction());
+ if ((mShouldClose || mGoawayReceived) && !mStreamTransactionHash.Count()) {
+ MOZ_ASSERT(!IsClosing());
+ Close(NS_OK);
+ }
+}
+
+nsresult Http3Session::TakeTransport(nsISocketTransport**,
+ nsIAsyncInputStream**,
+ nsIAsyncOutputStream**) {
+ MOZ_ASSERT(false, "TakeTransport of Http3Session");
+ return NS_ERROR_UNEXPECTED;
+}
+
+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::TopLevelOuterContentWindowIdChanged(uint64_t windowId) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mCurrentForegroundTabOuterContentWindowId = windowId;
+
+ for (auto iter = mStreamTransactionHash.Iter(); !iter.Done(); iter.Next()) {
+ iter.Data()->TopLevelOuterContentWindowIdChanged(windowId);
+ }
+}
+
+nsresult Http3Session::OnReadSegment(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Session::OnReadSegment"));
+ *countRead = 0;
+ return NS_OK;
+}
+
+nsresult Http3Session::OnWriteSegment(char* buf, uint32_t count,
+ uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Session::OnWriteSegment"));
+ *countWritten = 0;
+ return NS_OK;
+}
+
+// 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<Http3Stream> 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()));
+
+ if (!IsClosing()) {
+ StreamReadyToWrite(stream);
+ } 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<Http3Stream> 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(Http3Stream* 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 || !CanSandData() || 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<nsISupports> securityInfo;
+ nsCOMPtr<nsISSLSocketControl> sslSocketControl;
+
+ mConnection->GetSecurityInfo(getter_AddRefs(securityInfo));
+ sslSocketControl = do_QueryInterface(securityInfo, &rv);
+ if (NS_FAILED(rv) || !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.Put(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.Put(key2, joinedReturn);
+ }
+ }
+ return joinedReturn;
+}
+
+void Http3Session::CallCertVerification() {
+ 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));
+ }
+
+ mSocketControl->SetAuthenticationCallback(this);
+ uint32_t providerFlags;
+ // the return value is always NS_OK, just ignore it.
+ Unused << mSocketControl->GetProviderFlags(&providerFlags);
+
+ SECStatus rv = AuthCertificateHookWithInfo(
+ mSocketControl, 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", mSegmentReaderWriter,
+ &HttpConnectionUDP::OnQuicTimeoutExpired));
+ }
+}
+
+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->SetInfo(secInfo.cipher, secInfo.version, secInfo.group,
+ secInfo.signature_scheme);
+ }
+
+ if (!mSocketControl->HasServerCert() &&
+ StaticPrefs::network_ssl_tokens_cache_enabled()) {
+ mSocketControl->RebuildCertificateInfoFromSSLTokenCache();
+ }
+}
+
+void Http3Session::CloseConnectionTelemetry(CloseError& aError, bool aClosing) {
+ uint64_t value = 0;
+
+ switch (aError.tag) {
+ case CloseError::Tag::QuicTransportError:
+ // Transport error have values from 0 to 12.
+ // (https://tools.ietf.org/html/draft-ietf-quic-transport-24#section-20)
+ // We will map this error to 0-12.
+ // 13 wil capture error codes between and including 13 and 0x0ff. This
+ // error codes are not define by the spec but who know peer may sent them.
+ // CryptoAlerts have value 0x100 + alert code. For now we will map them
+ // to 14. (https://tools.ietf.org/html/draft-ietf-quic-tls-24#section-4.9)
+ // (telemetry does not allow more than 100 bucket and to easily map alerts
+ // we need 256. If we find problem with too many alerts we could map
+ // them.)
+ if (aError.quic_transport_error._0 <= 12) {
+ value = aError.quic_transport_error._0;
+ } else if (aError.quic_transport_error._0 < 0x100) {
+ value = 13;
+ } else {
+ value = 14;
+ }
+ break;
+ case CloseError::Tag::Http3AppError:
+ if (aError.http3_app_error._0 <= 0x110) {
+ // Http3 error codes are 0x100-0x110.
+ // (https://tools.ietf.org/html/draft-ietf-quic-http-24#section-8.1)
+ // The will be mapped to 15-31.
+ value = (aError.http3_app_error._0 - 0x100) + 15;
+ } else if (aError.http3_app_error._0 < 0x200) {
+ // Error codes between 0x111 and 0x1ff are not defined and will be
+ // mapped int 32
+ value = 32; // 0x11 + 15
+ } else if (aError.http3_app_error._0 < 0x203) {
+ // Error codes between 0x200 and 0x202 are related to qpack.
+ // (https://tools.ietf.org/html/draft-ietf-quic-qpack-11#section-6)
+ // They will be mapped to 33-35
+ value = aError.http3_app_error._0 - 0x200 + 33;
+ } else {
+ value = 36;
+ }
+ }
+
+ // A connection may be closed in two ways: client side closes connection or
+ // server side. In former case http3 state will first change to closing state
+ // in the later case itt will change to closed. In tthis way we can
+ // distinguish which side is closing connecttion first. If necko closes
+ // connection, this will map to "closing" key and 37 in the graph.
+ Telemetry::Accumulate(Telemetry::HTTP3_CONNECTTION_CLOSE_CODE,
+ aClosing ? "closing"_ns : "closed"_ns, 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 (CanSandData() && !mHttp3ConnectionReported) {
+ mHttp3ConnectionReported = true;
+ gHttpHandler->ConnMgr()->ReportHttp3Connection(mSegmentReaderWriter);
+ MaybeResumeSend();
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http3Session.h b/netwerk/protocol/http/Http3Session.h
new file mode 100644
index 0000000000..6fe980cea0
--- /dev/null
+++ b/netwerk/protocol/http/Http3Session.h
@@ -0,0 +1,218 @@
+/* -*- 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 "nsISupportsImpl.h"
+#include "nsITimer.h"
+#include "mozilla/net/NeqoHttp3Conn.h"
+#include "nsAHttpConnection.h"
+#include "nsRefPtrHashtable.h"
+#include "nsWeakReference.h"
+#include "HttpTrafficAnalyzer.h"
+#include "mozilla/UniquePtr.h"
+#include "nsDeque.h"
+
+namespace mozilla {
+namespace net {
+
+class HttpConnectionUDP;
+class Http3Stream;
+class QuicSocketControl;
+
+// IID for the Http3Session interface
+#define NS_HTTP3SESSION_IID \
+ { \
+ 0x8fc82aaf, 0xc4ef, 0x46ed, { \
+ 0x89, 0x41, 0x93, 0x95, 0x8f, 0xac, 0x4f, 0x21 \
+ } \
+ }
+
+class Http3Session final : public nsAHttpTransaction,
+ public nsAHttpConnection,
+ public nsAHttpSegmentReader,
+ public nsAHttpSegmentWriter {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTP3SESSION_IID)
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_NSAHTTPCONNECTION(mConnection)
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+
+ Http3Session();
+ nsresult Init(const nsHttpConnectionInfo* aConnInfo,
+ nsISocketTransport* aSocketTransport,
+ HttpConnectionUDP* readerWriter);
+
+ bool IsConnected() const { return mState == CONNECTED; }
+ bool CanSandData() 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();
+
+ // overload of nsAHttpTransaction
+ [[nodiscard]] nsresult ReadSegmentsAgain(nsAHttpSegmentReader*, uint32_t,
+ uint32_t*, bool*) final;
+ [[nodiscard]] nsresult WriteSegmentsAgain(nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*, bool*) final;
+
+ // The folowing functions are used by Http3Stream:
+ nsresult TryActivating(const nsACString& aMethod, const nsACString& aScheme,
+ const nsACString& aHost, const nsACString& aPath,
+ const nsACString& aHeaders, uint64_t* aStreamId,
+ Http3Stream* aStream);
+ 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);
+
+ void CloseStream(Http3Stream* 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;
+
+ nsISocketTransport* SocketTransport() { return mSocketTransport; }
+
+ // This function will be called by QuicSocketControl when the certificate
+ // verification is done.
+ void Authenticated(int32_t aError);
+
+ nsresult ProcessOutputAndEvents();
+
+ const nsCString& GetAlpnToken() { return mAlpnToken; }
+
+ void ReportHttp3Connection();
+
+ private:
+ ~Http3Session();
+
+ void CloseInternal(bool aCallNeqoClose);
+ void Shutdown();
+
+ bool RealJoinConnection(const nsACString& hostname, int32_t port,
+ bool justKidding);
+
+ nsresult ProcessOutput();
+ nsresult ProcessInput(uint32_t* aCountRead);
+ nsresult ProcessEvents(uint32_t count);
+
+ nsresult ProcessTransactionRead(uint64_t stream_id, uint32_t count,
+ uint32_t* countWritten);
+ nsresult ProcessTransactionRead(Http3Stream* stream, uint32_t count,
+ uint32_t* countWritten);
+ nsresult ProcessSlowConsumers();
+ void ConnectSlowConsumer(Http3Stream* stream);
+
+ void SetupTimer(uint64_t aTimeout);
+
+ void ResetRecvd(uint64_t aStreamId, uint64_t aError);
+
+ void QueueStream(Http3Stream* stream);
+ void RemoveStreamFromQueues(Http3Stream*);
+ void ProcessPending();
+
+ void CallCertVerification();
+ void SetSecInfo();
+
+ void StreamReadyToWrite(Http3Stream* aStream);
+ void MaybeResumeSend();
+
+ void CloseConnectionTelemetry(CloseError& aError, bool aClosing);
+ void Finish0Rtt(bool aRestart);
+
+ RefPtr<NeqoHttp3Conn> mHttp3Connection;
+ RefPtr<nsAHttpConnection> mConnection;
+ nsRefPtrHashtable<nsUint64HashKey, Http3Stream> mStreamIdHash;
+ nsRefPtrHashtable<nsPtrHashKey<nsAHttpTransaction>, Http3Stream>
+ mStreamTransactionHash;
+
+ nsDeque<Http3Stream> mReadyForWrite;
+ nsTArray<RefPtr<Http3Stream>> mSlowConsumersReadyForRead;
+ nsDeque<Http3Stream> mQueuedStreams;
+
+ enum State { INITIALIZING, ZERORTT, CONNECTED, CLOSING, CLOSED } mState;
+
+ bool mAuthenticationStarted;
+ bool mCleanShutdown;
+ bool mGoawayReceived;
+ bool mShouldClose;
+ bool mIsClosedByNeqo;
+ bool mHttp3ConnectionReported = false;
+ // mError is neqo error (a protocol error) and that may mean that we will
+ // send some packets after that.
+ nsresult mError;
+ // This is a socket error, there is no poioint in sending anything on that
+ // socket.
+ nsresult mSocketError;
+ bool mBeforeConnectedError;
+ uint64_t mCurrentForegroundTabOuterContentWindowId;
+
+ // True if the mTimer is inited and waiting for firing.
+ bool mTimerActive;
+
+ nsTArray<uint8_t> mPacketToSend;
+
+ RefPtr<HttpConnectionUDP> mSegmentReaderWriter;
+
+ // The underlying socket transport object is needed to propogate some events
+ RefPtr<nsISocketTransport> mSocketTransport;
+
+ nsCOMPtr<nsITimer> mTimer;
+
+ nsDataHashtable<nsCStringHashKey, bool> mJoinConnectionCache;
+
+ RefPtr<QuicSocketControl> mSocketControl;
+ nsCString mAlpnToken;
+
+ uint64_t mTransactionCount = 0;
+
+ // The stream(s) that we are getting 0RTT data from.
+ nsTArray<WeakPtr<Http3Stream>> 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<Http3Stream>> mCannotDo0RTTStreams;
+
+ // The following variables are needed for telemetry.
+ TimeStamp mConnectionIdleStart;
+ TimeStamp mConnectionIdleEnd;
+ Maybe<uint64_t> mFirstStreamIdReuseIdleConnection;
+ TimeStamp mTimerShouldTrigger;
+ 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;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(Http3Session, NS_HTTP3SESSION_IID);
+
+} // namespace net
+} // namespace mozilla
+
+#endif // Http3Session_H__
diff --git a/netwerk/protocol/http/Http3Stream.cpp b/netwerk/protocol/http/Http3Stream.cpp
new file mode 100644
index 0000000000..5451eb7c3b
--- /dev/null
+++ b/netwerk/protocol/http/Http3Stream.cpp
@@ -0,0 +1,489 @@
+/* -*- 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 "nsISocketTransport.h"
+#include "nsSocketTransportService2.h"
+
+#include <stdio.h>
+
+namespace mozilla {
+namespace net {
+
+Http3Stream::Http3Stream(nsAHttpTransaction* httpTransaction,
+ Http3Session* session)
+ : mSendState(PREPARING_HEADERS),
+ mRecvState(BEFORE_HEADERS),
+ mStreamId(UINT64_MAX),
+ mSession(session),
+ mTransaction(httpTransaction),
+ mQueued(false),
+ mDataReceived(false),
+ mResetRecv(false),
+ mRequestBodyLenRemaining(0),
+ mSocketTransport(session->SocketTransport()),
+ mTotalSent(0),
+ mTotalRead(0),
+ mFin(false),
+ mSendingBlockedByFlowControlCount(0) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG3(("Http3Stream::Http3Stream [this=%p]", this));
+}
+
+void Http3Stream::Close(nsresult aResult) {
+ mRecvState = RECV_DONE;
+ mTransaction->Close(aResult);
+}
+
+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 = %u",
+ this, mFlatHttpRequestHeaders.Length()));
+ *countUsed = avail;
+ return false;
+ }
+
+ uint32_t oldLen = mFlatHttpRequestHeaders.Length();
+ mFlatHttpRequestHeaders.SetLength(endHeader + 2);
+ *countUsed = avail - (oldLen - endHeader) + 4;
+
+ FindRequestContentLength();
+ return true;
+}
+
+void Http3Stream::FindRequestContentLength() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ // Look for Content-Length header to find out if we have request body and
+ // how long it is.
+ int32_t contentLengthStart = mFlatHttpRequestHeaders.Find("Content-Length:");
+ if (contentLengthStart == -1) {
+ // There is no content-Length.
+ return;
+ }
+
+ // We have Content-Length header, find the end of it.
+ int32_t crlfIndex =
+ mFlatHttpRequestHeaders.Find("\r\n", false, contentLengthStart);
+ if (crlfIndex == -1) {
+ MOZ_ASSERT(false, "We must have \\r\\n at the end of the headers string.");
+ return;
+ }
+
+ // Find the beginning.
+ int32_t valueIndex =
+ mFlatHttpRequestHeaders.Find(":", false, contentLengthStart) + 1;
+ if (valueIndex > crlfIndex) {
+ // Content-Length headers is empty.
+ MOZ_ASSERT(false, "Content-Length must have a value.");
+ return;
+ }
+
+ const char* beginBuffer = mFlatHttpRequestHeaders.BeginReading();
+ while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ') {
+ ++valueIndex;
+ }
+
+ nsDependentCSubstring value =
+ Substring(beginBuffer + valueIndex, beginBuffer + crlfIndex);
+
+ int64_t len;
+ nsCString tmp(value);
+ if (nsHttp::ParseInt64(tmp.get(), nullptr, &len)) {
+ mRequestBodyLenRemaining = len;
+ }
+}
+
+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);
+
+ return mSession->TryActivating(method, scheme, authorityHeader, path,
+ mFlatHttpRequestHeaders, &mStreamId, this);
+}
+
+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(mSocketTransport,
+ NS_NET_STATUS_SENDING_TO, mTotalSent);
+
+ if (mRequestBodyLenRemaining) {
+ mSendState = SENDING_BODY;
+ } else {
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_WAITING_FOR, 0);
+ mSession->CloseSendingSide(mStreamId);
+ mSendState = SEND_DONE;
+ }
+ break;
+ case SENDING_BODY: {
+ rv = mSession->SendRequestBody(mStreamId, buf, count, countRead);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSendingBlockedByFlowControlCount++;
+ }
+ MOZ_ASSERT(mRequestBodyLenRemaining >= *countRead,
+ "We cannot send more that than we promised.");
+ if (mRequestBodyLenRemaining < *countRead) {
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ if (NS_FAILED(rv)) {
+ LOG3(
+ ("Http3Stream::OnReadSegment %p sending body returns "
+ "error=0x%" PRIx32 ".",
+ this, static_cast<uint32_t>(rv)));
+ break;
+ }
+
+ mRequestBodyLenRemaining -= *countRead;
+ if (!mRequestBodyLenRemaining) {
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_WAITING_FOR, 0);
+ mSession->CloseSendingSide(mStreamId);
+ mSendState = SEND_DONE;
+ Telemetry::Accumulate(
+ Telemetry::HTTP3_SENDING_BLOCKED_BY_FLOW_CONTROL_PER_TRANS,
+ mSendingBlockedByFlowControlCount);
+ }
+ } break;
+ case EARLY_RESPONSE:
+ // We do not need to send the rest of the request, so just ignore it.
+ *countRead = count;
+ mRequestBodyLenRemaining -= count;
+ if (!mRequestBodyLenRemaining) {
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_WAITING_FOR, 0);
+ mSendState = SEND_DONE;
+ }
+ break;
+ default:
+ MOZ_ASSERT(false, "We are done sending this request!");
+ break;
+ }
+
+ mSocketOutCondition = rv;
+
+ return mSocketOutCondition;
+}
+
+void Http3Stream::SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders,
+ bool aFin) {
+ MOZ_ASSERT(mRecvState == BEFORE_HEADERS);
+ MOZ_ASSERT(mFlatResponseHeaders.IsEmpty(),
+ "Cannot set response headers more than once");
+ mFlatResponseHeaders = std::move(aResponseHeaders);
+ mRecvState = 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: {
+ // 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) {
+ mRecvState = mFin ? RECEIVED_FIN : READING_DATA;
+ }
+
+ if (*countWritten == 0) {
+ rv = NS_BASE_STREAM_WOULD_BLOCK;
+ } else {
+ mTotalRead += *countWritten;
+ mTransaction->OnTransportStatus(
+ mSocketTransport, 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(
+ mSocketTransport, 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(nsAHttpSegmentReader* reader) {
+ 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) {
+ 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(nsAHttpSegmentWriter* writer,
+ uint32_t count, uint32_t* countWritten) {
+ 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;
+ rv = mTransaction->WriteSegmentsAgain(this, count, &countWrittenSingle,
+ &again);
+ *countWritten += countWrittenSingle;
+ 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);
+ mRequestBodyLenRemaining = 0;
+ mTotalSent = 0;
+ mTotalRead = 0;
+ mFin = false;
+ mSendingBlockedByFlowControlCount = 0;
+ mSocketInCondition = NS_ERROR_NOT_INITIALIZED;
+ mSocketOutCondition = NS_ERROR_NOT_INITIALIZED;
+ }
+
+ return rv;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/Http3Stream.h b/netwerk/protocol/http/Http3Stream.h
new file mode 100644
index 0000000000..8463a3f860
--- /dev/null
+++ b/netwerk/protocol/http/Http3Stream.h
@@ -0,0 +1,158 @@
+/* -*- 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 "mozilla/WeakPtr.h"
+
+namespace mozilla {
+namespace net {
+
+class Http3Session;
+
+class Http3Stream final : public nsAHttpSegmentReader,
+ public nsAHttpSegmentWriter,
+ public SupportsWeakPtr,
+ public ARefBase {
+ public:
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+ // for RefPtr
+ NS_INLINE_DECL_REFCOUNTING(Http3Stream, override)
+
+ Http3Stream(nsAHttpTransaction* httpTransaction, Http3Session* session);
+
+ bool HasStreamId() const { return mStreamId != UINT64_MAX; }
+ uint64_t StreamId() const { return mStreamId; }
+
+ nsresult TryActivating();
+
+ // TODO priorities
+ void TopLevelOuterContentWindowIdChanged(uint64_t windowId){};
+
+ [[nodiscard]] nsresult ReadSegments(nsAHttpSegmentReader*);
+ [[nodiscard]] nsresult WriteSegments(nsAHttpSegmentWriter*, uint32_t,
+ uint32_t*);
+
+ void SetQueued(bool aStatus) { mQueued = aStatus; }
+ bool Queued() const { return mQueued; }
+
+ bool Done() const { return mRecvState == RECV_DONE; }
+
+ void Close(nsresult aResult);
+ bool RecvdData() const { return mDataReceived; }
+
+ nsAHttpTransaction* Transaction() { return mTransaction; }
+ bool RecvdFin() const { return mFin; }
+ bool RecvdReset() const { return mResetRecv; }
+ void SetRecvdReset() { mResetRecv = true; }
+
+ void StopSending();
+
+ void SetResponseHeaders(nsTArray<uint8_t>& aResponseHeaders, bool fin);
+
+ // Mirrors nsAHttpTransaction
+ bool Do0RTT();
+ nsresult Finish0RTT(bool aRestart);
+
+ private:
+ ~Http3Stream() = default;
+
+ bool GetHeadersString(const char* buf, uint32_t avail, uint32_t* countUsed);
+ nsresult StartRequest();
+ void FindRequestContentLength();
+
+ /**
+ * 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;
+
+ /**
+ * RecvStreamState:
+ * - BEFORE_HEADERS:
+ * The stream has not received headers yet.
+ * - READING_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 stream has been closed by the server after sending headers the
+ * stream will transit into RECEIVED_FIN state, otherwise it transits to
+ * READING_DATA state.
+ * - 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_DATA,
+ RECEIVED_FIN,
+ RECV_DONE
+ } mRecvState;
+
+ uint64_t mStreamId;
+ Http3Session* mSession;
+ RefPtr<nsAHttpTransaction> mTransaction;
+ nsCString mFlatHttpRequestHeaders;
+ bool mQueued;
+ bool mDataReceived;
+ bool mResetRecv;
+ nsTArray<uint8_t> mFlatResponseHeaders;
+ uint32_t mRequestBodyLenRemaining;
+
+ // The underlying socket transport object is needed to propogate some events
+ RefPtr<nsISocketTransport> mSocketTransport;
+
+ // For Progress Events
+ uint64_t mTotalSent;
+ uint64_t mTotalRead;
+
+ bool mFin;
+
+ bool mAttempting0RTT = false;
+
+ uint32_t mSendingBlockedByFlowControlCount = 0;
+
+ nsresult mSocketInCondition = NS_ERROR_NOT_INITIALIZED;
+ nsresult mSocketOutCondition = NS_ERROR_NOT_INITIALIZED;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_Http3Stream_h
diff --git a/netwerk/protocol/http/HttpAuthUtils.cpp b/netwerk/protocol/http/HttpAuthUtils.cpp
new file mode 100644
index 0000000000..d48d4aa0fe
--- /dev/null
+++ b/netwerk/protocol/http/HttpAuthUtils.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/net/HttpAuthUtils.h"
+#include "mozilla/Tokenizer.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsUnicharUtils.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..4c7b6cddd8
--- /dev/null
+++ b/netwerk/protocol/http/HttpBackgroundChannelChild.cpp
@@ -0,0 +1,504 @@
+/* -*- 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/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->mCreateBackgroundChannelFailed = true;
+#endif
+ mChannelChild = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ mFirstODASource = ODA_PENDING;
+ mOnStopRequestCalled = false;
+ return NS_OK;
+}
+
+void HttpBackgroundChannelChild::CreateDataBridge() {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (!mChannelChild) {
+ return;
+ }
+
+ PBackgroundChild* actorChild =
+ BackgroundChild::GetOrCreateSocketActorForCurrentThread();
+ if (NS_WARN_IF(!actorChild)) {
+ return;
+ }
+
+ RefPtr<BackgroundDataBridgeChild> dataBridgeChild =
+ new BackgroundDataBridgeChild(this);
+ Unused << actorChild->SendPBackgroundDataBridgeConstructor(
+ dataBridgeChild, mChannelChild->ChannelId());
+}
+
+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) {
+ 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);
+ // 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 nsDependentCSubstring& aData, const bool& aDataFromSocketProcess) {
+ RefPtr<HttpBackgroundChannelChild> self = this;
+ nsCString data(aData);
+ std::function<void()> callProcessOnTransportAndData =
+ [self, aChannelStatus, aTransportStatus, aOffset, aCount, data,
+ aDataFromSocketProcess]() {
+ 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);
+ };
+
+ // 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) {
+ 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]() mutable {
+ self->RecvOnStopRequest(aChannelStatus, aTiming, aLastActiveTabOptHit,
+ aResponseTrailers, std::move(consoleReports),
+ aFromSocketProcess);
+ });
+
+ 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);
+ }
+ 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);
+ 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::RecvNotifyFlashPluginStateChanged(
+ const nsIHttpChannel::FlashPluginState& aState) {
+ LOG(
+ ("HttpBackgroundChannelChild::RecvNotifyFlashPluginStateChanged "
+ "[this=%p]\n",
+ this));
+ MOZ_ASSERT(OnSocketThread());
+
+ if (NS_WARN_IF(!mChannelChild)) {
+ return IPC_OK();
+ }
+
+ // NotifyFlashPluginStateChanged has no order dependency to OnStartRequest.
+ // It this be handled as soon as possible
+ mChannelChild->ProcessNotifyFlashPluginStateChanged(aState);
+
+ 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();
+}
+
+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..af9fba57f8
--- /dev/null
+++ b/netwerk/protocol/http/HttpBackgroundChannelChild.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_HttpBackgroundChannelChild_h
+#define mozilla_net_HttpBackgroundChannelChild_h
+
+#include "mozilla/net/PHttpBackgroundChannelChild.h"
+#include "nsIRunnable.h"
+#include "nsTArray.h"
+
+using mozilla::ipc::IPCResult;
+
+namespace mozilla {
+namespace net {
+
+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);
+
+ IPCResult RecvOnTransportAndData(const nsresult& aChannelStatus,
+ const nsresult& aTransportStatus,
+ const uint64_t& aOffset,
+ const uint32_t& aCount,
+ const nsDependentCSubstring& aData,
+ const bool& aDataFromSocketProcess);
+
+ IPCResult RecvOnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const TimeStamp& aLastActiveTabOptHit,
+ const nsHttpHeaderArray& aResponseTrailers,
+ nsTArray<ConsoleReportCollected>&& aConsoleReports,
+ const bool& aFromSocketProcess);
+
+ 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 RecvNotifyFlashPluginStateChanged(
+ const nsIHttpChannel::FlashPluginState& aState);
+
+ IPCResult RecvSetClassifierMatchedInfo(const ClassifierInfo& info);
+
+ IPCResult RecvSetClassifierMatchedTrackingInfo(const ClassifierInfo& info);
+
+ IPCResult RecvAttachStreamFilter(
+ Endpoint<extensions::PStreamFilterParent>&& aEndpoint);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ void CreateDataBridge();
+
+ 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;
+
+ // 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..3c25e8454e
--- /dev/null
+++ b/netwerk/protocol/http/HttpBackgroundChannelParent.cpp
@@ -0,0 +1,519 @@
+/* -*- 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 "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) {
+ 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>(
+ "net::HttpBackgroundChannelParent::OnStartRequest", this,
+ &HttpBackgroundChannelParent::OnStartRequest, aResponseHead,
+ aUseResponseHead, aRequestHeaders, aArgs),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ return SendOnStartRequest(aResponseHead, aUseResponseHead, aRequestHeaders,
+ aArgs);
+}
+
+bool HttpBackgroundChannelParent::OnTransportAndData(
+ const nsresult& aChannelStatus, const nsresult& aTransportStatus,
+ const uint64_t& aOffset, const uint32_t& aCount, const nsCString& aData) {
+ 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>(
+ "net::HttpBackgroundChannelParent::OnTransportAndData", this,
+ &HttpBackgroundChannelParent::OnTransportAndData, aChannelStatus,
+ aTransportStatus, aOffset, aCount, aData),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ nsHttp::SendFunc<nsDependentCSubstring> sendFunc =
+ [self = UnsafePtr<HttpBackgroundChannelParent>(this), aChannelStatus,
+ aTransportStatus](const nsDependentCSubstring& aData, uint64_t aOffset,
+ uint32_t aCount) {
+ return self->SendOnTransportAndData(aChannelStatus, aTransportStatus,
+ aOffset, aCount, aData, false);
+ };
+
+ return nsHttp::SendDataInChunks(aData, aOffset, aCount, sendFunc);
+}
+
+bool HttpBackgroundChannelParent::OnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers,
+ const nsTArray<ConsoleReportCollected>& aConsoleReports) {
+ 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>>(
+ "net::HttpBackgroundChannelParent::OnStopRequest", this,
+ &HttpBackgroundChannelParent::OnStopRequest, aChannelStatus,
+ aTiming, aResponseTrailers, aConsoleReports),
+ 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);
+}
+
+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::OnNotifyFlashPluginStateChanged(
+ nsIHttpChannel::FlashPluginState aState) {
+ LOG(
+ ("HttpBackgroundChannelParent::OnNotifyFlashPluginStateChanged "
+ "[this=%p]\n",
+ this));
+ AssertIsInMainProcess();
+
+ if (NS_WARN_IF(!mIPCOpened)) {
+ return false;
+ }
+
+ if (!IsOnBackgroundThread()) {
+ MutexAutoLock lock(mBgThreadMutex);
+ RefPtr<HttpBackgroundChannelParent> self = this;
+ nsresult rv = mBackgroundThread->Dispatch(
+ NS_NewRunnableFunction(
+ "net::HttpBackgroundChannelParent::OnNotifyFlashPluginStateChanged",
+ [self, aState]() {
+ self->OnNotifyFlashPluginStateChanged(aState);
+ }),
+ NS_DISPATCH_NORMAL);
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ return NS_SUCCEEDED(rv);
+ }
+
+ return SendNotifyFlashPluginStateChanged(aState);
+}
+
+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__);
+}
+
+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..c2d25f1df6
--- /dev/null
+++ b/netwerk/protocol/http/HttpBackgroundChannelParent.h
@@ -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/. */
+
+#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);
+
+ // 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);
+
+ // To send OnStopRequest message over background channel.
+ bool OnStopRequest(const nsresult& aChannelStatus,
+ const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers,
+ const nsTArray<ConsoleReportCollected>& aConsoleReports);
+
+ // 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 NotifyFlashPluginStateChanged message over background channel.
+ bool OnNotifyFlashPluginStateChanged(nsIHttpChannel::FlashPluginState aState);
+
+ // 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);
+
+ protected:
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+ virtual ~HttpBackgroundChannelParent();
+
+ Atomic<bool> mIPCOpened;
+
+ // Used to ensure atomicity of mBackgroundThread
+ Mutex mBgThreadMutex;
+
+ 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..7193953ad1
--- /dev/null
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -0,0 +1,5330 @@
+/* -*- 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 "mozIThirdPartyUtil.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/NullPrincipal.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/net/PartiallySeekableInputStream.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "nsCRT.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentSecurityUtils.h"
+#include "nsContentUtils.h"
+#include "nsEscape.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsHttpChannel.h"
+#include "nsHttpHandler.h"
+#include "nsIApplicationCacheChannel.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 "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 "nsThreadUtils.h"
+#include "nsURLHelper.h"
+#include "mozilla/RemoteLazyInputStreamChild.h"
+#include "mozilla/RemoteLazyInputStreamUtils.h"
+#include "mozilla/net/SFVService.h"
+#include "mozilla/dom/ContentChild.h"
+
+namespace mozilla {
+namespace net {
+
+static bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader) {
+ // IMPORTANT: keep this list ASCII-code sorted
+ static nsHttpAtom const* blackList[] = {&nsHttp::Accept,
+ &nsHttp::Accept_Encoding,
+ &nsHttp::Accept_Language,
+ &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._val, aVal->_val);
+ }
+ };
+
+ 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)
+
+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),
+ mTopLevelOuterContentWindowId(0),
+ mAltDataLength(-1),
+ mChannelId(0),
+ mReqContentLength(0U),
+ mStatus(NS_OK),
+ mCanceled(false),
+ mFirstPartyClassificationFlags(0),
+ mThirdPartyClassificationFlags(0),
+ mFlashPluginState(nsIHttpChannel::FlashPluginUnknown),
+ mLoadFlags(LOAD_NORMAL),
+ mCaps(0),
+ mClassOfService(0),
+ mTlsFlags(0),
+ mSuspendCount(0),
+ mInitialRwin(0),
+ mProxyResolveFlags(0),
+ mContentDispositionHint(UINT32_MAX),
+ mCorsMode(nsIHttpChannelInternal::CORS_MODE_NO_CORS),
+ mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW),
+ mLastRedirectFlags(0),
+ mPriority(PRIORITY_NORMAL),
+ mRedirectionLimit(gHttpHandler->RedirectionLimit()),
+ mRedirectCount(0),
+ mInternalRedirectCount(0) {
+ StoreApplyConversion(true);
+ StoreAllowSTS(true);
+ StoreInheritApplicationCache(true);
+ StoreTracingEnabled(true);
+ StoreReportTiming(true);
+ StoreAllowSpdy(true);
+ StoreAllowHttp3(true);
+ StoreAllowAltSvc(true);
+ StoreResponseTimeoutEnabled(true);
+ StoreAllRedirectsSameOrigin(true);
+ StoreAllRedirectsPassTimingAllowCheck(true);
+ StoreUpgradableToSecure(true);
+
+ 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(mApplicationCache.forget());
+ arrayToRelease.AppendElement(mPrincipal.forget());
+ arrayToRelease.AppendElement(mListener.forget());
+ arrayToRelease.AppendElement(mCompressListener.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;
+ }
+}
+
+void HttpBaseChannel::SetFlashPluginState(
+ nsIHttpChannel::FlashPluginState aState) {
+ LOG(("HttpBaseChannel::SetFlashPluginState %p", this));
+ mFlashPluginState = aState;
+}
+
+nsresult HttpBaseChannel::Init(nsIURI* aURI, uint32_t aCaps,
+ nsProxyInfo* aProxyInfo,
+ uint32_t aProxyResolveFlags, nsIURI* aProxyURI,
+ uint64_t aChannelId,
+ ExtContentPolicyType aContentPolicyType) {
+ 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;
+
+ // Construct connection info object
+ nsAutoCString host;
+ int32_t port = -1;
+ bool isHTTPS = mURI->SchemeIs("https");
+
+ 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);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString type;
+ if (aProxyInfo && NS_SUCCEEDED(aProxyInfo->GetType(type)) &&
+ !type.EqualsLiteral("unknown"))
+ mProxyInfo = aProxyInfo;
+
+ mCurrentThread = GetCurrentEventTarget();
+ 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 = mLoadGroup;
+ NS_IF_ADDREF(*aLoadGroup);
+ 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) {
+ 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;
+ }
+
+ const nsString& customUserAgent = bc->GetUserAgentOverride();
+ 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 = mOwner;
+ NS_IF_ADDREF(*aOwner);
+ 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) {
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsDocument(bool* aIsDocument) {
+ return NS_GetIsDocumentChannel(this, aIsDocument);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
+ *aCallbacks = mCallbacks;
+ NS_IF_ADDREF(*aCallbacks);
+ 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()) {
+ 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.
+ if (mContentDispositionHint == nsIChannel::DISPOSITION_ATTACHMENT) {
+ *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) {
+ MOZ_ASSERT_UNREACHABLE("HttpBaseChannel::SetContentLength");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+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 = mUploadStream;
+ NS_IF_ADDREF(*stream);
+ 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 = stream;
+ return NS_OK;
+}
+
+namespace {
+
+void CopyComplete(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
+
+ auto channel = static_cast<HttpBaseChannel*>(aClosure);
+ channel->OnCopyComplete(aStatus);
+}
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+HttpBaseChannel::EnsureUploadStreamIsCloneable(nsIRunnable* aCallback) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+ NS_ENSURE_ARG_POINTER(aCallback);
+
+ // We could in theory allow multiple callers to use this method,
+ // but the complexity does not seem worth it yet. Just fail if
+ // this is called more than once simultaneously.
+ NS_ENSURE_FALSE(mUploadCloneableCallback, NS_ERROR_UNEXPECTED);
+
+ // We can immediately exec the callback if we don't have an upload stream.
+ if (!mUploadStream) {
+ aCallback->Run();
+ return NS_OK;
+ }
+
+ // Upload nsIInputStream must be cloneable and seekable in order to be
+ // processed by devtools network inspector.
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
+ if (seekable && NS_InputStreamIsCloneable(mUploadStream)) {
+ aCallback->Run();
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIStorageStream> storageStream;
+ nsresult rv =
+ NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storageStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> newUploadStream;
+ rv = storageStream->NewInputStream(0, getter_AddRefs(newUploadStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> sink;
+ rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> source;
+ if (NS_InputStreamIsBuffered(mUploadStream)) {
+ source = mUploadStream;
+ } else {
+ rv = NS_NewBufferedInputStream(getter_AddRefs(source),
+ mUploadStream.forget(), 4096);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+
+ mUploadCloneableCallback = aCallback;
+
+ rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS,
+ 4096, // copy segment size
+ CopyComplete, this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mUploadCloneableCallback = nullptr;
+ return rv;
+ }
+
+ // Since we're consuming the old stream, replace it with the new
+ // stream immediately.
+ mUploadStream = newUploadStream;
+
+ // Explicity hold the stream alive until copying is complete. This will
+ // be released in EnsureUploadStreamIsCloneableComplete().
+ AddRef();
+
+ return NS_OK;
+}
+
+void HttpBaseChannel::OnCopyComplete(nsresult aStatus) {
+ // Assert in parent process because we don't have to label the runnable
+ // in parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<nsresult>(
+ "net::HttpBaseChannel::EnsureUploadStreamIsCloneableComplete", this,
+ &HttpBaseChannel::EnsureUploadStreamIsCloneableComplete, aStatus);
+ NS_DispatchToMainThread(runnable.forget());
+}
+
+void HttpBaseChannel::EnsureUploadStreamIsCloneableComplete(nsresult aStatus) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+ MOZ_ASSERT(mUploadCloneableCallback);
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ mUploadCloneableCallback->Run();
+ mUploadCloneableCallback = nullptr;
+
+ // Release the reference we grabbed in EnsureUploadStreamIsCloneable() now
+ // that the copying is complete.
+ Release();
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::CloneUploadStream(int64_t* aContentLength,
+ nsIInputStream** aClonedStream) {
+ NS_ENSURE_ARG_POINTER(aContentLength);
+ NS_ENSURE_ARG_POINTER(aClonedStream);
+ *aClonedStream = nullptr;
+
+ 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);
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aStream);
+ if (!seekable) {
+ nsCOMPtr<nsIInputStream> stream = aStream;
+ seekable = new PartiallySeekableInputStream(stream.forget());
+ }
+
+ mUploadStream = do_QueryInterface(seekable);
+
+ if (aContentLength >= 0) {
+ ExplicitSetUploadStreamLength(aContentLength, aStreamHasHeaders);
+ return NS_OK;
+ }
+
+ // Sync access to the stream length.
+ int64_t length;
+ if (InputStreamLengthHelper::GetSyncLength(aStream, &length)) {
+ ExplicitSetUploadStreamLength(length >= 0 ? length : 0, aStreamHasHeaders);
+ return NS_OK;
+ }
+
+ // Let's resolve the size of the stream.
+ RefPtr<HttpBaseChannel> self = this;
+ InputStreamLengthHelper::GetAsyncLength(
+ aStream, [self, aStreamHasHeaders](int64_t aLength) {
+ self->StorePendingInputStreamLengthOperation(false);
+ self->ExplicitSetUploadStreamLength(aLength >= 0 ? aLength : 0,
+ aStreamHasHeaders);
+ self->MaybeResumeAsyncOpen();
+ });
+ StorePendingInputStreamLengthOperation(true);
+ return NS_OK;
+}
+
+void HttpBaseChannel::ExplicitSetUploadStreamLength(uint64_t aContentLength,
+ bool aStreamHasHeaders) {
+ // We already have the content length. We don't need to determinate it.
+ mReqContentLength = aContentLength;
+
+ if (aStreamHasHeaders) {
+ 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::MaybeWaitForUploadStreamLength(
+ nsIStreamListener* aListener, nsISupports* aContext) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!LoadAsyncOpenWaitingForStreamLength(),
+ "AsyncOpen() called twice?");
+
+ if (!LoadPendingInputStreamLengthOperation()) {
+ return false;
+ }
+
+ mListener = aListener;
+ StoreAsyncOpenWaitingForStreamLength(true);
+ return true;
+}
+
+void HttpBaseChannel::MaybeResumeAsyncOpen() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!LoadPendingInputStreamLengthOperation());
+
+ if (!LoadAsyncOpenWaitingForStreamLength()) {
+ return;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ listener.swap(mListener);
+
+ StoreAsyncOpenWaitingForStreamLength(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, mURI->SchemeIs("https"))) {
+ nsCOMPtr<nsIStreamConverterService> serv;
+ rv = gHttpHandler->GetStreamConverterService(getter_AddRefs(serv));
+
+ // we won't fail to load the page just because we couldn't load the
+ // stream converter service.. carry on..
+ if (NS_FAILED(rv)) {
+ if (val) LOG(("Unknown content encoding '%s', ignoring\n", val));
+ continue;
+ }
+
+ nsCOMPtr<nsIStreamListener> converter;
+ nsAutoCString from(val);
+ ToLowerCase(from);
+ rv = serv->AsyncConvertData(from.get(), "uncompressed", nextListener,
+ aCtxt, getter_AddRefs(converter));
+ 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 = nextListener;
+ NS_IF_ADDREF(*aNewNextListener);
+ 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::SetTopLevelOuterContentWindowId(
+ uint64_t aWindowId) {
+ mTopLevelOuterContentWindowId = aWindowId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetTopLevelOuterContentWindowId(
+ uint64_t* aWindowId) {
+ EnsureTopLevelOuterContentWindowId();
+ *aWindowId = mTopLevelOuterContentWindowId;
+ 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);
+ 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::GetFlashPluginState(nsIHttpChannel::FlashPluginState* aState) {
+ uint32_t flashPluginState = mFlashPluginState;
+ *aState = (nsIHttpChannel::FlashPluginState)flashPluginState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTransferSize(uint64_t* aTransferSize) {
+ *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) {
+ *aEncodedBodySize = mEncodedBodySize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetSupportsHTTP3(bool* aSupportsHTTP3) {
+ *aSupportsHTTP3 = mSupportsHTTP3;
+ 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 (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;
+ }
+
+ 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;
+ }
+
+ 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::GetAllowPipelining(bool* value) {
+ NS_ENSURE_ARG_POINTER(value);
+ *value = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowPipelining(bool value) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ // nop
+ return NS_OK;
+}
+
+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();
+
+ if (!value) {
+ // The only channels that are allowSTS == false are OCSPRequest
+ // 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_FIRST.
+ // We do this to prevent a TRR service channel's OCSP validation from
+ // blocking DNS resolution completely.
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ uint32_t trrMode = 0;
+ if (dns && NS_SUCCEEDED(dns->GetCurrentTrrMode(&trrMode)) && trrMode == 3) {
+ SetTRRMode(nsIRequest::TRR_FIRST_MODE);
+ }
+ }
+
+ StoreAllowSTS(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(nsISupports* 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;
+ 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) {
+ nsresult rv;
+ nsCOMPtr<nsITransportSecurityInfo> info =
+ do_QueryInterface(mSecurityInfo, &rv);
+ nsAutoCString protocol;
+ if (NS_SUCCEEDED(rv) && info &&
+ NS_SUCCEEDED(info->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 = services::GetThirdPartyUtil();
+ 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
+ }
+ }
+ NS_IF_ADDREF(*aTopWindowURI = mTopWindowURI);
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDocumentURI(nsIURI** aDocumentURI) {
+ NS_ENSURE_ARG_POINTER(aDocumentURI);
+ *aDocumentURI = mDocumentURI;
+ NS_IF_ADDREF(*aDocumentURI);
+ 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 (mLoadGroup && mLoadGroup->GetIsBrowsingContextDiscarded()) {
+ return true;
+ }
+
+ return false;
+}
+
+// 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;
+ rv = GetResponseEmbedderPolicy(&resultPolicy);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ // https://mikewest.github.io/corpp/#abstract-opdef-process-navigation-response
+ if (mLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SUBDOCUMENT &&
+ mLoadInfo->GetLoadingEmbedderPolicy() !=
+ nsILoadInfo::EMBEDDER_POLICY_NULL &&
+ resultPolicy != nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) {
+ return NS_ERROR_BLOCKED_BY_POLICY;
+ }
+
+ return NS_OK;
+}
+
+// https://mikewest.github.io/corpp/#corp-check
+nsresult HttpBaseChannel::ProcessCrossOriginResourcePolicyHeader() {
+ // Fetch 4.5.9
+ uint32_t corsMode;
+ MOZ_ALWAYS_SUCCEEDS(GetCorsMode(&corsMode));
+ if (corsMode != nsIHttpChannelInternal::CORS_MODE_NO_CORS) {
+ return NS_OK;
+ }
+
+ // We only apply this for resources.
+ if (mLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT ||
+ mLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_WEBSOCKET) {
+ return NS_OK;
+ }
+
+ if (mLoadInfo->GetExternalContentPolicyType() ==
+ 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;
+ }
+
+ nsAutoCString content;
+ Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Resource_Policy,
+ content);
+
+ if (StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ // 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.
+ if (content.IsEmpty() && mLoadInfo->GetLoadingEmbedderPolicy() ==
+ nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) {
+ 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() {
+ 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;
+ }
+
+ // 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;
+
+ // 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 &&
+ GetHasNonEmptySandboxingFlag()) {
+ LOG((
+ "HttpBaseChannel::ComputeCrossOriginOpenerPolicyMismatch network error "
+ "for non empty sandboxing and non null COOP"));
+ return NS_ERROR_BLOCKED_BY_POLICY;
+ }
+
+ // In xpcshell-tests we don't always have a current window global
+ if (!ctx->Canonical()->GetCurrentWindowGlobal()) {
+ return NS_OK;
+ }
+
+ // We use the top window principal as the documentOrigin
+ nsCOMPtr<nsIPrincipal> documentOrigin =
+ ctx->Canonical()->GetCurrentWindowGlobal()->DocumentPrincipal();
+ nsCOMPtr<nsIPrincipal> resultOrigin;
+
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(resultOrigin));
+
+ 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 (!ctx->Canonical()->GetCurrentWindowGlobal()->IsInitialDocument()) {
+ StoreHasCrossOriginOpenerPolicyMismatch(true);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+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_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;
+ }
+
+ bool isPrivateWin = aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
+ bool isSameOrigin = false;
+ aLoadInfo->GetLoadingPrincipal()->IsSameOrigin(aURI, isPrivateWin,
+ &isSameOrigin);
+ if (isSameOrigin) {
+ // 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"), 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)) {
+ bool isPrivateWin =
+ aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
+ bool isSameOrigin = false;
+ aLoadInfo->GetLoadingPrincipal()->IsSameOrigin(
+ corsOriginURI, isPrivateWin, &isSameOrigin);
+ if (isSameOrigin) {
+ 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) {
+ 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;
+}
+
+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) {
+ mRedirectedCachekeys = 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 (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;
+}
+
+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::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);
+ NS_IF_ADDREF(*aResult = mAPIRedirectToURI);
+ 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::GetCorsMode(uint32_t* aMode) {
+ *aMode = mCorsMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCorsMode(uint32_t aMode) {
+ mCorsMode = 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)) {
+ *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();
+}
+
+nsIPrincipal* HttpBaseChannel::GetURIPrincipal() {
+ if (mPrincipal) {
+ return mPrincipal;
+ }
+
+ nsIScriptSecurityManager* securityManager =
+ nsContentUtils::GetSecurityManager();
+
+ if (!securityManager) {
+ LOG(("HttpBaseChannel::GetURIPrincipal: No security manager [this=%p]",
+ this));
+ return nullptr;
+ }
+
+ securityManager->GetChannelURIPrincipal(this, getter_AddRefs(mPrincipal));
+ if (!mPrincipal) {
+ LOG(("HttpBaseChannel::GetURIPrincipal: No channel principal [this=%p]",
+ this));
+ return nullptr;
+ }
+
+ return mPrincipal;
+}
+
+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;
+
+ // 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) {
+ nsCOMPtr<nsIPrincipal> nullPrincipalToInherit =
+ NullPrincipal::CreateWithoutOriginAttributes();
+ 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));
+
+ nsCString remoteAddress;
+ Unused << GetRemoteAddress(remoteAddress);
+ nsCOMPtr<nsIURI> referrer;
+ if (mReferrerInfo) {
+ referrer = mReferrerInfo->GetComputedReferrer();
+ }
+
+ nsCOMPtr<nsIRedirectHistoryEntry> entry =
+ new nsRedirectHistoryEntry(GetURIPrincipal(), referrer, remoteAddress);
+
+ newLoadInfo->AppendRedirectHistoryEntry(entry, 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;
+}
+
+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(nsDependentCString(nsHttp::Cookie), 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);
+ }
+
+ if (referrerPolicy != dom::ReferrerPolicy::_empty) {
+ // We may reuse computed referrer in redirect, so if referrerPolicy
+ // changes, we must not use the old computed value, and have to compute
+ // again.
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ dom::ReferrerInfo::CreateFromOtherAndPolicyOverride(mReferrerInfo,
+ referrerPolicy);
+ config.referrerInfo = referrerInfo;
+ } else {
+ config.referrerInfo = mReferrerInfo;
+ }
+ }
+ }
+
+ nsCOMPtr<nsITimedChannel> oldTimedChannel(
+ do_QueryInterface(static_cast<nsIHttpChannel*>(this)));
+ if (oldTimedChannel) {
+ config.timedChannel = Some(dom::TimedChannelInfo());
+ config.timedChannel->timingEnabled() = LoadTimingEnabled();
+ config.timedChannel->redirectCount() = mRedirectCount;
+ config.timedChannel->internalRedirectCount() = mInternalRedirectCount;
+ config.timedChannel->asyncOpen() = mAsyncOpenTime;
+ config.timedChannel->channelCreation() = mChannelCreationTimestamp;
+ config.timedChannel->redirectStart() = mRedirectStartTimeStamp;
+ config.timedChannel->redirectEnd() = mRedirectEndTimeStamp;
+ config.timedChannel->initiatorType() = mInitiatorType;
+ config.timedChannel->allRedirectsSameOrigin() =
+ LoadAllRedirectsSameOrigin();
+ config.timedChannel->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.timedChannel->timingAllowCheckForPrincipal() =
+ Some(oldTimedChannel->TimingAllowCheck(principal));
+ }
+
+ config.timedChannel->allRedirectsPassTimingAllowCheck() =
+ LoadAllRedirectsPassTimingAllowCheck();
+ config.timedChannel->launchServiceWorkerStart() = mLaunchServiceWorkerStart;
+ config.timedChannel->launchServiceWorkerEnd() = mLaunchServiceWorkerEnd;
+ config.timedChannel->dispatchFetchEventStart() = mDispatchFetchEventStart;
+ config.timedChannel->dispatchFetchEventEnd() = mDispatchFetchEventEnd;
+ config.timedChannel->handleFetchEventStart() = mHandleFetchEventStart;
+ config.timedChannel->handleFetchEventEnd() = mHandleFetchEventEnd;
+ config.timedChannel->responseStart() = mTransactionTimings.responseStart;
+ config.timedChannel->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->SetClassFlags(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.timedChannel && newTimedChannel) {
+ newTimedChannel->SetTimingEnabled(config.timedChannel->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.timedChannel->redirectCount());
+ int8_t newCount = config.timedChannel->internalRedirectCount() + 1;
+ newTimedChannel->SetInternalRedirectCount(
+ std::max(newCount, config.timedChannel->internalRedirectCount()));
+ } else {
+ int8_t newCount = config.timedChannel->redirectCount() + 1;
+ newTimedChannel->SetRedirectCount(
+ std::max(newCount, config.timedChannel->redirectCount()));
+ newTimedChannel->SetInternalRedirectCount(
+ config.timedChannel->internalRedirectCount());
+ }
+
+ if (shouldHideTiming) {
+ if (!config.timedChannel->channelCreation().IsNull()) {
+ newTimedChannel->SetChannelCreation(
+ config.timedChannel->channelCreation());
+ }
+
+ if (!config.timedChannel->asyncOpen().IsNull()) {
+ newTimedChannel->SetAsyncOpen(config.timedChannel->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.timedChannel->redirectStart().IsNull()) {
+ // Only do this for real redirects. Internal redirects should be hidden.
+ if (!shouldHideTiming) {
+ newTimedChannel->SetRedirectStart(config.timedChannel->asyncOpen());
+ }
+ } else {
+ newTimedChannel->SetRedirectStart(config.timedChannel->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.timedChannel->redirectEnd();
+ } else {
+ newRedirectEnd = config.timedChannel->responseEnd();
+ }
+ newTimedChannel->SetRedirectEnd(newRedirectEnd);
+
+ newTimedChannel->SetInitiatorType(config.timedChannel->initiatorType());
+
+ nsCOMPtr<nsILoadInfo> loadInfo = newChannel->LoadInfo();
+ MOZ_ASSERT(loadInfo);
+
+ newTimedChannel->SetAllRedirectsSameOrigin(
+ config.timedChannel->allRedirectsSameOrigin());
+
+ if (config.timedChannel->timingAllowCheckForPrincipal()) {
+ newTimedChannel->SetAllRedirectsPassTimingAllowCheck(
+ config.timedChannel->allRedirectsPassTimingAllowCheck() &&
+ *config.timedChannel->timingAllowCheckForPrincipal());
+ }
+
+ // Propagate service worker measurements across redirects. The
+ // PeformanceResourceTiming.workerStart API expects to see the
+ // worker start time after a redirect.
+ newTimedChannel->SetLaunchServiceWorkerStart(
+ config.timedChannel->launchServiceWorkerStart());
+ newTimedChannel->SetLaunchServiceWorkerEnd(
+ config.timedChannel->launchServiceWorkerEnd());
+ newTimedChannel->SetDispatchFetchEventStart(
+ config.timedChannel->dispatchFetchEventStart());
+ newTimedChannel->SetDispatchFetchEventEnd(
+ config.timedChannel->dispatchFetchEventEnd());
+ newTimedChannel->SetHandleFetchEventStart(
+ config.timedChannel->handleFetchEventStart());
+ newTimedChannel->SetHandleFetchEventEnd(
+ config.timedChannel->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();
+ timedChannel = aInit.timedChannel();
+ if (RemoteLazyInputStreamChild* actor =
+ static_cast<RemoteLazyInputStreamChild*>(aInit.uploadStreamChild())) {
+ uploadStreamLength = actor->Size();
+ uploadStream = actor->CreateStream();
+ // actor can be deleted by CreateStream, so don't touch it
+ // after this.
+ } else {
+ uploadStreamLength = 0;
+ }
+ uploadStreamHasHeaders = aInit.uploadStreamHasHeaders();
+ contentType = aInit.contentType();
+ contentLength = aInit.contentLength();
+}
+
+dom::ReplacementChannelConfigInit
+HttpBaseChannel::ReplacementChannelConfig::Serialize(
+ dom::ContentParent* aParent) {
+ dom::ReplacementChannelConfigInit config;
+ config.redirectFlags() = redirectFlags;
+ config.classOfService() = classOfService;
+ config.privateBrowsing() = privateBrowsing;
+ config.method() = method;
+ config.referrerInfo() = referrerInfo;
+ config.timedChannel() = timedChannel;
+ if (uploadStream) {
+ RemoteLazyStream ipdlStream;
+ RemoteLazyInputStreamUtils::SerializeInputStream(
+ uploadStream, uploadStreamLength, ipdlStream, aParent);
+ config.uploadStreamParent() = ipdlStream;
+ }
+ 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;
+ }
+
+ // Do not pass along LOAD_CHECK_OFFLINE_CACHE
+ loadFlags &= ~nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE;
+ 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));
+ if (config.timedChannel && newTimedChannel) {
+ newTimedChannel->SetAllRedirectsSameOrigin(
+ config.timedChannel->allRedirectsSameOrigin() &&
+ SameOriginWithOriginalUri(newURI));
+ }
+
+ newChannel->SetLoadGroup(mLoadGroup);
+ newChannel->SetNotificationCallbacks(mCallbacks);
+
+ 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 && mCorsMode == CORS_MODE_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));
+ }
+ }
+
+ // 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->SetTopLevelOuterContentWindowId(
+ mTopLevelOuterContentWindowId);
+ 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(newURI));
+ }
+
+ // 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)
+ if (mRedirectedCachekeys) {
+ LOG(
+ ("HttpBaseChannel::SetupReplacementChannel "
+ "[this=%p] transferring chain of redirect cache-keys",
+ this));
+ rv = httpInternal->SetCacheKeysRedirectChain(
+ mRedirectedCachekeys.release());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // Preserve CORS mode flag.
+ rv = httpInternal->SetCorsMode(mCorsMode);
+ 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 application cache information
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(newChannel);
+ if (appCacheChannel) {
+ appCacheChannel->SetApplicationCache(mApplicationCache);
+ appCacheChannel->SetInheritApplicationCache(LoadInheritApplicationCache());
+ // We purposely avoid transfering ChooseApplicationCache.
+ }
+
+ // transfer any properties
+ nsCOMPtr<nsIWritablePropertyBag> bag(do_QueryInterface(newChannel));
+ if (bag) {
+ for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) {
+ bag->SetProperty(iter.Key(), iter.UserData());
+ }
+ }
+
+ // 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 (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));
+ }
+
+ // This channel has been redirected. Don't report timing info.
+ StoreTimingEnabled(false);
+ return NS_OK;
+}
+
+bool HttpBaseChannel::ShouldTaintReplacementChannelOrigin(nsIURI* aNewURI) {
+ if (LoadTaintedOriginFlag()) {
+ return true;
+ }
+
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ if (!ssm) {
+ return true;
+ }
+ bool isPrivateWin = mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
+ nsresult rv = ssm->CheckSameOriginURI(aNewURI, mURI, false, isPrivateWin);
+ if (NS_SUCCEEDED(rv)) {
+ return false;
+ }
+ // If aNewURI <-> mURI are not same-origin we need to taint unless
+ // mURI <-> mOriginalURI/LoadingPrincipal are same origin.
+
+ if (mLoadInfo->GetLoadingPrincipal()) {
+ bool sameOrigin = false;
+ rv = mLoadInfo->GetLoadingPrincipal()->IsSameOrigin(mURI, isPrivateWin,
+ &sameOrigin);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ return !sameOrigin;
+ }
+ if (!mOriginalURI) {
+ return true;
+ }
+
+ rv = ssm->CheckSameOriginURI(mOriginalURI, mURI, false, isPrivateWin);
+ 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;
+}
+
+// http://www.w3.org/TR/resource-timing/#timing-allow-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);
+ if (NS_SUCCEEDED(rv) && sameOrigin) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ nsAutoCString headerValue;
+ rv = GetResponseHeader("Timing-Allow-Origin"_ns, headerValue);
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ nsAutoCString origin;
+ aOrigin->GetAsciiOrigin(origin);
+
+ 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 == origin || 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::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)
+
+#undef IMPL_TIMING_ATTR
+
+mozilla::dom::PerformanceStorage* HttpBaseChannel::GetPerformanceStorage() {
+ // If performance timing is disabled, there is no need for the Performance
+ // object anymore.
+ if (!LoadTimingEnabled()) {
+ return nullptr;
+ }
+
+ // 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 nullptr;
+ }
+ return mLoadInfo->GetPerformanceStorage();
+}
+
+void HttpBaseChannel::MaybeReportTimingData() {
+ if (XRE_IsE10sParentProcess()) {
+ return;
+ }
+
+ mozilla::dom::PerformanceStorage* documentPerformance =
+ 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;
+ }
+ child->SendReportFrameTimingData(mLoadInfo->GetInnerWindowID(), 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));
+ if (!mRequestContext) {
+ return false;
+ }
+
+ return true;
+}
+
+void HttpBaseChannel::EnsureTopLevelOuterContentWindowId() {
+ if (mTopLevelOuterContentWindowId) {
+ return;
+ }
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ GetCallback(loadContext);
+ if (!loadContext) {
+ return;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> topWindow;
+ loadContext->GetTopWindow(getter_AddRefs(topWindow));
+ if (!topWindow) {
+ return;
+ }
+
+ mTopLevelOuterContentWindowId =
+ nsPIDOMWindowOuter::From(topWindow)->WindowID();
+}
+
+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) {
+ // 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;
+ }
+
+ 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);
+
+ nsAutoCString newType;
+ NS_SniffContent(NS_CONTENT_SNIFFER_CATEGORY, 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 (mURI->SchemeIs("https")) {
+ ParseServerTimingHeader(mResponseHead, aServerTiming);
+ ParseServerTimingHeader(mResponseTrailers, aServerTiming);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::CancelByURLClassifier(nsresult aErrorCode) {
+ MOZ_ASSERT(
+ UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
+ return Cancel(aErrorCode);
+}
+
+void HttpBaseChannel::SetIPv4Disabled() { mCaps |= NS_HTTP_DISABLE_IPV4; }
+
+void HttpBaseChannel::SetIPv6Disabled() { mCaps |= NS_HTTP_DISABLE_IPV6; }
+
+NS_IMETHODIMP HttpBaseChannel::GetResponseEmbedderPolicy(
+ 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);
+ 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;
+ if (NS_SUCCEEDED(GetResponseEmbedderPolicy(&coep)) &&
+ coep == nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) {
+ 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() {
+ // 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_WAIT_HTTPSSVC_RESULT;
+ 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..f4e0896056
--- /dev/null
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -0,0 +1,1045 @@
+/* -*- 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 "mozilla/AtomicBitfields.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/net/DNS.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 "nsIURI.h"
+#include "nsIUploadChannel2.h"
+#include "nsStringEnumerator.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 PreferredAlternativeDataTypeParams;
+
+enum CacheDisposition : uint8_t {
+ kCacheUnresolved = 0,
+ kCacheHit = 1,
+ kCacheHitViaReval = 2,
+ kCacheMissedViaReval = 3,
+ kCacheMissed = 4,
+ kCacheUnknown = 5
+};
+
+/*
+ * 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);
+
+ // 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;
+
+ // 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 GetAllowPipelining(bool* value) override; // deprecated
+ NS_IMETHOD SetAllowPipelining(bool value) override; // deprecated
+ 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 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 GetTopLevelOuterContentWindowId(uint64_t* aWindowId) override;
+ NS_IMETHOD SetTopLevelOuterContentWindowId(uint64_t aWindowId) override;
+
+ NS_IMETHOD GetFlashPluginState(
+ nsIHttpChannel::FlashPluginState* aState) 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 GetIsTRRServiceChannel(bool* aTRR) override;
+ NS_IMETHOD SetIsTRRServiceChannel(bool aTRR) override;
+ NS_IMETHOD GetIsResolvedByTRR(bool* aResolvedByTRR) 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 GetCorsMode(uint32_t* aCorsMode) override;
+ NS_IMETHOD SetCorsMode(uint32_t aCorsMode) 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;
+ virtual void SetIPv4Disabled(void) override;
+ virtual void 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(
+ nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) override;
+ virtual bool GetHasNonEmptySandboxingFlag() override {
+ return LoadHasNonEmptySandboxingFlag();
+ }
+ virtual void SetHasNonEmptySandboxingFlag(
+ bool aHasNonEmptySandboxingFlag) override {
+ StoreHasNonEmptySandboxingFlag(aHasNonEmptySandboxingFlag);
+ }
+
+ inline void CleanRedirectCacheChainIfNecessary() {
+ mRedirectedCachekeys = nullptr;
+ }
+ NS_IMETHOD HTTPUpgrade(const nsACString& aProtocolName,
+ nsIHttpUpgradeListener* aListener) override;
+ void DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() override;
+
+ NS_IMETHOD SetWaitForHTTPSSVCRecord() 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;
+ 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();
+ }
+
+ const NetAddr& GetSelfAddr() { return mSelfAddr; }
+ const NetAddr& GetPeerAddr() { return mPeerAddr; }
+
+ [[nodiscard]] nsresult OverrideSecurityInfo(nsISupports* aSecurityInfo);
+
+ 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);
+
+ // Callback on STS thread called by CopyComplete when NS_AsyncCopy()
+ // is finished. This function works as a proxy function to dispatch
+ // |EnsureUploadStreamIsCloneableComplete| to main thread.
+ virtual void OnCopyComplete(nsresult aStatus);
+
+ void AddClassificationFlags(uint32_t aFlags, bool aIsThirdParty);
+
+ void SetFlashPluginState(nsIHttpChannel::FlashPluginState aState);
+
+ const uint64_t& ChannelId() const { return mChannelId; }
+
+ void InternalSetUploadStream(nsIInputStream* uploadStream) {
+ mUploadStream = uploadStream;
+ }
+
+ void InternalSetUploadStreamLength(uint64_t aLength) {
+ mReqContentLength = aLength;
+ }
+
+ 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;
+ uint32_t classOfService = 0;
+ Maybe<bool> privateBrowsing = Nothing();
+ Maybe<nsCString> method;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ Maybe<dom::TimedChannelInfo> timedChannel;
+ nsCOMPtr<nsIInputStream> uploadStream;
+ uint64_t uploadStreamLength;
+ bool uploadStreamHasHeaders;
+ Maybe<nsCString> contentType;
+ Maybe<nsCString> contentLength;
+
+ dom::ReplacementChannelConfigInit Serialize(dom::ContentParent* aParent);
+ };
+
+ 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(); }
+
+ 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);
+
+ mozilla::dom::PerformanceStorage* GetPerformanceStorage();
+ void MaybeReportTimingData();
+ nsIURI* GetReferringPage();
+ nsPIDOMWindowInner* GetInnerDOMWindow();
+
+ void AddCookiesToRequest();
+ [[nodiscard]] virtual nsresult SetupReplacementChannel(
+ nsIURI*, nsIChannel*, bool preserveMethod, uint32_t redirectFlags);
+
+ // 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);
+
+ // GetPrincipal Returns the channel's URI principal.
+ nsIPrincipal* GetURIPrincipal();
+
+ [[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);
+
+ // Callback on main thread when NS_AsyncCopy() is finished populating
+ // the new mUploadStream.
+ void EnsureUploadStreamIsCloneableComplete(nsresult aStatus);
+
+#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 MaybeWaitForUploadStreamLength(nsIStreamListener* aListener,
+ nsISupports* aContext);
+
+ void MaybeFlushConsoleReports();
+
+ bool IsBrowsingContextDiscarded() const;
+
+ nsresult ProcessCrossOriginEmbedderPolicyHeader();
+
+ nsresult ProcessCrossOriginResourcePolicyHeader();
+
+ nsresult ComputeCrossOriginOpenerPolicyMismatch();
+
+ nsresult ValidateMIMEType();
+
+ friend class PrivateBrowsingChannel<HttpBaseChannel>;
+ friend class InterceptFailedOnStop;
+
+ 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<nsIApplicationCache> mApplicationCache;
+ 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;
+
+ private:
+ // WHATWG Fetch Standard 4.4. HTTP-redirect fetch, step 10
+ bool ShouldTaintReplacementChannelOrigin(nsIURI* aNewURI);
+
+ // Proxy release all members above on main thread.
+ void ReleaseMainThreadOnlyReferences();
+
+ void ExplicitSetUploadStreamLength(uint64_t aContentLength,
+ bool aStreamHasHeaders);
+
+ 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;
+ nsCOMPtr<nsIRunnable> mUploadCloneableCallback;
+ UniquePtr<nsHttpResponseHead> mResponseHead;
+ UniquePtr<nsHttpHeaderArray> mResponseTrailers;
+ RefPtr<nsHttpConnectionInfo> mConnectionInfo;
+ nsCOMPtr<nsIProxyInfo> mProxyInfo;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+ nsCOMPtr<nsIHttpUpgradeListener> mUpgradeProtocolCallback;
+ UniquePtr<nsString> mContentDispositionFilename;
+ nsCOMPtr<nsIConsoleReportCollector> mReportCollector;
+
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+ UniquePtr<nsTArray<nsCString>> 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;
+ // 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 mTopLevelOuterContentWindowId;
+ 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;
+ Atomic<uint32_t, ReleaseAcquire> mFlashPluginState;
+
+ UniquePtr<ProfileChunkedBuffer> mSource;
+
+ uint32_t mLoadFlags;
+ uint32_t mCaps;
+ uint32_t mClassOfService;
+
+ // 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, InheritApplicationCache, 1),
+ (uint32_t, ChooseApplicationCache, 1),
+ (uint32_t, LoadedFromApplicationCache, 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 prefer the LOAD_FROM_CACHE flag over LOAD_BYPASS_CACHE or
+ // LOAD_BYPASS_LOCAL_CACHE.
+ (uint32_t, PreferCacheLoadOverBypass, 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 stream length is still unknown.
+ // AsyncOpen() will be retriggered when InputStreamLengthHelper execs the
+ // callback, passing the stream length value.
+ (uint32_t, AsyncOpenWaitingForStreamLength, 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),
+
+ // True if the docshell's sandboxing flag set is not empty.
+ (uint32_t, HasNonEmptySandboxingFlag, 1),
+
+ // Tainted origin flag of a request, specified by
+ // WHATWG Fetch Standard 2.2.5.
+ (uint32_t, TaintedOriginFlag, 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;
+
+ uint32_t mCorsMode;
+ 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;
+
+ // 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 the
+ // InputStreamLengthHelper::GetAsyncLength callback.
+ (bool, PendingInputStreamLengthOperation, 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)
+ ))
+ // 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 EnsureTopLevelOuterContentWindowId();
+};
+
+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..fe757d6072
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -0,0 +1,3156 @@
+/* -*- 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 "nsHttp.h"
+#include "nsICacheEntry.h"
+#include "mozilla/BasePrincipal.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/extensions/StreamFilterParent.h"
+#include "mozilla/ipc/FileDescriptorSetChild.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 "nsCOMPtr.h"
+#include "nsContentPolicyUtils.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsGlobalWindow.h"
+#include "nsStringStream.h"
+#include "nsHttpChannel.h"
+#include "nsHttpHandler.h"
+#include "nsNetUtil.h"
+#include "nsSerializationHelper.h"
+#include "mozilla/Attributes.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 "InterceptedChannel.h"
+#include "nsContentSecurityManager.h"
+#include "nsICompressConvStats.h"
+#include "nsIDeprecationWarner.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 "nsApplicationCache.h"
+#include "ClassifierDummyChannel.h"
+#include "nsIOService.h"
+
+#ifdef MOZ_TASK_TRACER
+# include "GeckoTaskTracer.h"
+#endif
+
+#include <functional>
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild
+//-----------------------------------------------------------------------------
+
+HttpChannelChild::HttpChannelChild()
+ : HttpAsyncAborter<HttpChannelChild>(this),
+ NeckoTargetHolder(nullptr),
+ mBgChildMutex("HttpChannelChild::BgChildMutex"),
+ mEventTargetMutex("HttpChannelChild::EventTargetMutex"),
+ mCacheEntryId(0),
+ mCacheKey(0),
+ mCacheFetchCount(0),
+ mCacheExpirationTime(nsICacheEntry::NO_EXPIRATION_TIME),
+ mDeletingChannelSent(false),
+ mIsFromCache(false),
+ mIsRacing(false),
+ mCacheNeedToReportBytesReadInitialized(false),
+ mNeedToReportBytesRead(true),
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ mBackgroundChildQueueFinalState(BCKCHILD_UNKNOWN),
+#endif
+ mCacheEntryAvailable(false),
+ mAltDataCacheEntryAvailable(false),
+ mSendResumeAt(false),
+ mKeptAlive(false),
+ mIPCActorDeleted(false),
+ mSuspendSent(false),
+ mIsLastPartOfMultiPart(false),
+ mSuspendForWaitCompleteRedirectSetup(false),
+ mRecvOnStartRequestSentCalled(false),
+ mSuspendedByWaitingForPermissionCookie(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));
+
+#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
+
+ 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) {
+ NS_LOG_RELEASE(this, 0, "HttpChannelChild");
+ delete this;
+ return 0;
+ }
+
+ // 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);
+
+ // This runnable will create a strong reference to |this|.
+ NS_DispatchToMainThread(
+ NewRunnableMethod("~HttpChannelChild>DoNotifyListener", channel,
+ &HttpChannelChild::DoNotifyListener));
+
+ // 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.
+
+ // We should have already done any special handling for the refcount = 1
+ // case when the refcount first went from 2 to 1. We don't want it to happen
+ // when |channel| is destroyed.
+ MOZ_ASSERT(!mKeptAlive || !CanSend());
+
+ // XXX If std::move(channel) is allowed, then we don't have to have extra
+ // checks for the refcount going from 2 to 1. See bug 1680217.
+
+ // This will release the stabilization refcount, which is necessary to avoid
+ // a leak.
+ channel = nullptr;
+
+ 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_CONDITIONAL(nsIApplicationCacheContainer,
+ !mMultiPartID.isSome())
+ NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
+ 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);
+ }
+}
+
+void HttpChannelChild::AssociateApplicationCache(const nsCString& aGroupID,
+ const nsCString& aClientID) {
+ LOG(("HttpChannelChild::AssociateApplicationCache [this=%p]\n", this));
+ mApplicationCache = new nsApplicationCache();
+
+ StoreLoadedFromApplicationCache(true);
+ mApplicationCache->InitAsHandle(aGroupID, aClientID);
+}
+
+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) {
+ LOG(("HttpChannelChild::ProcessOnStartRequest [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aResponseHead,
+ aUseResponseHead, aRequestHeaders, aArgs]() {
+ 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();
+}
+
+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);
+
+ if (!aArgs.securityInfoSerialization().IsEmpty()) {
+ [[maybe_unused]] nsresult rv = NS_DeserializeObject(
+ aArgs.securityInfoSerialization(), getter_AddRefs(mSecurityInfo));
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv),
+ "Deserializing security info should not fail");
+ }
+
+ ipc::MergeParentLoadInfoForwarder(aArgs.loadInfoForwarder(), mLoadInfo);
+
+ mIsFromCache = aArgs.isFromCache();
+ mIsRacing = aArgs.isRacing();
+ mCacheEntryAvailable = aArgs.cacheEntryAvailable();
+ mCacheEntryId = aArgs.cacheEntryId();
+ mCacheFetchCount = aArgs.cacheFetchCount();
+ mCacheExpirationTime = aArgs.cacheExpirationTime();
+ mSelfAddr = aArgs.selfAddr();
+ mPeerAddr = aArgs.peerAddr();
+
+ mRedirectCount = aArgs.redirectCount();
+ mAvailableCachedAltDataType = aArgs.altDataType();
+ StoreDeliveringAltData(aArgs.deliveringAltData());
+ mAltDataLength = aArgs.altDataLength();
+ StoreResolvedByTRR(aArgs.isResolvedByTRR());
+
+ SetApplyConversion(aArgs.applyConversion());
+
+ StoreAfterOnStartRequestBegun(true);
+
+ AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
+ mCacheKey = aArgs.cacheKey();
+
+ // 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);
+
+ StoreAllRedirectsSameOrigin(aArgs.allRedirectsSameOrigin());
+
+ mMultiPartID = aArgs.multiPartID();
+ mIsLastPartOfMultiPart = aArgs.isLastPartOfMultiPart();
+
+ if (!aArgs.appCacheGroupId().IsEmpty() &&
+ !aArgs.appCacheClientId().IsEmpty()) {
+ AssociateApplicationCache(aArgs.appCacheGroupId(),
+ aArgs.appCacheClientId());
+ }
+
+ 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, nullptr);
+ }));
+ return;
+ }
+
+ // Remember whether HTTP3 is supported
+ if (mResponseHead && (mResponseHead->Version() == HttpVersion::v2_0) &&
+ (mResponseHead->Status() < 500) && (mResponseHead->Status() != 421)) {
+ nsAutoCString altSvc;
+ Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, altSvc);
+ if (!altSvc.IsEmpty() || nsHttp::IsReasonableHeaderValue(altSvc)) {
+ for (uint32_t i = 0; i < kHttp3VersionCount; i++) {
+ if (PL_strstr(altSvc.get(), kHttp3Versions[i].get())) {
+ mSupportsHTTP3 = true;
+ break;
+ }
+ }
+ }
+ }
+
+ DoOnStartRequest(this, nullptr);
+}
+
+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,
+ nsISupports* aContext) {
+ 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)) {
+ Cancel(rv);
+ return;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ rv = DoApplyContentConversions(mListener, getter_AddRefs(listener), nullptr);
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ } 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 nsCString& aData) {
+ 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]() {
+ 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 nsCString& aData) {
+ LOG(("HttpChannelChild::OnTransportAndData [this=%p]\n", this));
+
+ if (!mCanceled && NS_SUCCEEDED(mStatus)) {
+ mStatus = aChannelStatus;
+ }
+
+ if (mCanceled || NS_FAILED(mStatus)) {
+ return;
+ }
+
+ // 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)) {
+ Cancel(rv);
+ return;
+ }
+
+ DoOnDataAvailable(this, nullptr, 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);
+ }
+ }
+}
+
+void HttpChannelChild::DoOnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ 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);
+ }
+ }
+}
+
+void HttpChannelChild::ProcessOnStopRequest(
+ const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers,
+ nsTArray<ConsoleReportCollected>&& aConsoleReports,
+ bool aFromSocketProcess) {
+ LOG(
+ ("HttpChannelChild::ProcessOnStopRequest [this=%p, "
+ "aFromSocketProcess=%d]\n",
+ this, aFromSocketProcess));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aChannelStatus, aTiming,
+ aResponseTrailers,
+ consoleReports = CopyableTArray{aConsoleReports.Clone()},
+ aFromSocketProcess]() mutable {
+ 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::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 = aTiming.transferSize();
+ mEncodedBodySize = aTiming.encodedBodySize();
+ mProtocolVersion = aTiming.protocolVersion();
+
+ mCacheReadStart = aTiming.cacheReadStart();
+ mCacheReadEnd = aTiming.cacheReadEnd();
+
+#ifdef MOZ_GECKO_PROFILER
+ if (profiler_can_accept_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, TimeStamp::Now(), mTransferSize, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(), &mTransactionTimings, nullptr,
+ std::move(mSource), Some(nsDependentCString(contentType.get())));
+ }
+#endif
+
+ 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, nullptr);
+ // 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;
+ }
+
+ 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, mOMTResult);
+}
+
+void HttpChannelChild::DoOnStopRequest(nsIRequest* aRequest,
+ nsresult aChannelStatus,
+ nsISupports* aContext) {
+ 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();
+
+ // 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() {
+ 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;
+ StoreOnStartRequestCalled(
+ true); // avoid reentrancy bugs by setting this now
+ listener->OnStartRequest(this);
+ }
+ StoreOnStartRequestCalled(true);
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this)] {
+ self->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 nsString& messageTag, const nsString& messageCategory) {
+ DebugOnly<nsresult> rv = AddSecurityMessage(messageTag, messageCategory);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvRedirect1Begin(
+ const uint32_t& aRegistrarId, const URIParams& aNewUri,
+ const uint32_t& aNewLoadFlags, const uint32_t& aRedirectFlags,
+ const ParentLoadInfoForwarderArgs& aLoadInfoForwarder,
+ const nsHttpResponseHead& aResponseHead,
+ const nsCString& aSecurityInfoSerialization, 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, aNewUri,
+ aNewLoadFlags, aRedirectFlags, aLoadInfoForwarder, aResponseHead,
+ aSecurityInfoSerialization, aChannelId, aTiming]() {
+ self->Redirect1Begin(aRegistrarId, aNewUri, aNewLoadFlags,
+ aRedirectFlags, aLoadInfoForwarder, aResponseHead,
+ aSecurityInfoSerialization, 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, const URIParams& newOriginalURI,
+ const uint32_t& newLoadFlags, const uint32_t& redirectFlags,
+ const ParentLoadInfoForwarderArgs& loadInfoForwarder,
+ const nsHttpResponseHead& responseHead,
+ const nsACString& securityInfoSerialization, const uint64_t& channelId,
+ const ResourceTimingStructArgs& timing) {
+ nsresult rv;
+
+ LOG(("HttpChannelChild::Redirect1Begin [this=%p]\n", this));
+
+ ipc::MergeParentLoadInfoForwarder(loadInfoForwarder, mLoadInfo);
+
+ nsCOMPtr<nsIURI> uri = DeserializeURI(newOriginalURI);
+
+ ResourceTimingStructArgsToTimingsStruct(timing, mTransactionTimings);
+
+#ifdef MOZ_GECKO_PROFILER
+ if (profiler_can_accept_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(), &mTransactionTimings,
+ uri, std::move(mSource), Some(nsDependentCString(contentType.get())));
+ }
+#endif
+
+ if (!securityInfoSerialization.IsEmpty()) {
+ rv = NS_DeserializeObject(securityInfoSerialization,
+ getter_AddRefs(mSecurityInfo));
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv),
+ "Deserializing security info should not fail");
+ }
+
+ nsCOMPtr<nsIChannel> newChannel;
+ rv = SetupRedirect(uri, &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->Cancel(rv);
+
+ // 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();
+}
+
+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::ProcessNotifyFlashPluginStateChanged(
+ nsIHttpChannel::FlashPluginState aState) {
+ LOG(("HttpChannelChild::ProcessNotifyFlashPluginStateChanged [this=%p]\n",
+ this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpChannelChild>(this), aState]() {
+ self->SetFlashPluginState(aState);
+ }));
+}
+
+void HttpChannelChild::ProcessSetClassifierMatchedInfo(
+ const nsCString& aList, const nsCString& aProvider,
+ const nsCString& aFullHash) {
+ LOG(("HttpChannelChild::ProcessSetClassifierMatchedInfo [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread());
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this,
+ [self = UnsafePtr<HttpChannelChild>(this), aList, aProvider,
+ aFullHash]() { self->SetMatchedInfo(aList, aProvider, aFullHash); }));
+}
+
+void HttpChannelChild::ProcessSetClassifierMatchedTrackingInfo(
+ const nsCString& aLists, const nsCString& 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(true);
+ }
+
+ // 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)) {
+ nsCString remoteAddress;
+ Unused << GetRemoteAddress(remoteAddress);
+ nsCOMPtr<nsIURI> referrer;
+ if (mReferrerInfo) {
+ referrer = mReferrerInfo->GetComputedReferrer();
+ }
+
+ nsCOMPtr<nsIRedirectHistoryEntry> entry =
+ new nsRedirectHistoryEntry(GetURIPrincipal(), referrer, remoteAddress);
+
+ mLoadInfo->AppendRedirectHistoryEntry(entry, 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();
+
+ 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();
+#ifdef MOZ_GECKO_PROFILER
+ if (profiler_can_accept_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
+ mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(), nullptr, nullptr);
+ }
+#endif
+ 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());
+ Maybe<URIParams> redirectURI;
+ nsresult rv;
+
+ 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 the redirect was canceled, bypass OMR and send an empty API
+ * redirect URI */
+ SerializeURI(nullptr, redirectURI);
+
+ 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) {
+ nsCOMPtr<nsIURI> apiRedirectURI;
+ rv = newHttpChannelInternal->GetApiRedirectToURI(
+ getter_AddRefs(apiRedirectURI));
+ if (NS_SUCCEEDED(rv) && apiRedirectURI) {
+ /* If there was an API redirect of this channel, we need to send it
+ * up here, since it can't be sent via SendAsyncOpen. */
+ SerializeURI(apiRedirectURI, redirectURI);
+ }
+ }
+
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mRedirectChannelChild);
+ if (request) {
+ request->GetLoadFlags(&loadFlags);
+ }
+ }
+
+ bool chooseAppcache = false;
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(newHttpChannel);
+ if (appCacheChannel) {
+ appCacheChannel->GetChooseApplicationCache(&chooseAppcache);
+ }
+
+ 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, chooseAppcache);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::Cancel(nsresult aStatus) {
+ LOG(("HttpChannelChild::Cancel [this=%p, status=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(aStatus)));
+ LogCallingScriptLocation(this);
+
+ 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());
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::Suspend() {
+ LOG(("HttpChannelChild::Suspend [this=%p, mSuspendCount=%" PRIu32 "\n", this,
+ mSuspendCount + 1));
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE(RemoteChannelExists(), NS_ERROR_NOT_AVAILABLE);
+
+ 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(RemoteChannelExists(), NS_ERROR_NOT_AVAILABLE);
+ 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 && mSuspendSent) {
+ if (RemoteChannelExists()) {
+ 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(nsISupports** aSecurityInfo) {
+ NS_ENSURE_ARG_POINTER(aSecurityInfo);
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+ 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 (MaybeWaitForUploadStreamLength(listener, nullptr)) {
+ return NS_OK;
+ }
+
+ if (!LoadAsyncOpenTimeOverriden()) {
+ mAsyncOpenTime = TimeStamp::Now();
+ }
+
+#ifdef MOZ_TASK_TRACER
+ if (tasktracer::IsStartLogging()) {
+ nsCOMPtr<nsIURI> uri;
+ GetURI(getter_AddRefs(uri));
+ nsAutoCString urispec;
+ uri->GetSpec(urispec);
+ tasktracer::AddLabel("HttpChannelChild::AsyncOpen %s", urispec.get());
+ }
+#endif
+
+ // 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();
+#ifdef MOZ_GECKO_PROFILER
+ if (profiler_can_accept_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
+ mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(), nullptr, nullptr);
+ }
+#endif
+ StoreIsPending(true);
+ StoreWasOpened(true);
+ mListener = listener;
+
+ // add ourselves to the load group.
+ if (mLoadGroup) 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().
+ 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() {
+ nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo();
+
+ nsCOMPtr<nsISerialEventTarget> target =
+ nsContentUtils::GetEventTargetByLoadInfo(loadInfo, TaskCategory::Network);
+
+ if (!target) {
+ return;
+ }
+
+ gNeckoChild->SetEventTargetForActor(this, target);
+
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ mNeckoTarget = target;
+ }
+}
+
+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 = GetMainThreadEventTarget();
+ }
+ return target.forget();
+}
+
+nsresult HttpChannelChild::ContinueAsyncOpen() {
+ nsresult rv;
+ nsCString appCacheClientId;
+ if (LoadInheritApplicationCache()) {
+ // Pick up an application cache from the notification
+ // callbacks if available
+ nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
+ GetCallback(appCacheContainer);
+
+ if (appCacheContainer) {
+ nsCOMPtr<nsIApplicationCache> appCache;
+ nsresult rv =
+ appCacheContainer->GetApplicationCache(getter_AddRefs(appCache));
+ if (NS_SUCCEEDED(rv) && appCache) {
+ appCache->GetClientID(appCacheClientId);
+ }
+ }
+ }
+
+ //
+ // 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();
+ }
+ mTopLevelOuterContentWindowId = document->OuterWindowID();
+ }
+ }
+ SetTopLevelContentWindowId(contentWindowId);
+
+ HttpChannelOpenArgs openArgs;
+ // No access to HttpChannelOpenArgs members, but they each have a
+ // function with the struct name that returns a ref.
+ SerializeURI(mURI, openArgs.uri());
+ SerializeURI(mOriginalURI, openArgs.original());
+ SerializeURI(mDocumentURI, openArgs.doc());
+ SerializeURI(mAPIRedirectToURI, openArgs.apiRedirectTo());
+ openArgs.loadFlags() = mLoadFlags;
+ openArgs.requestHeaders() = mClientSetRequestHeaders;
+ mRequestHead.Method(openArgs.requestMethod());
+ openArgs.preferredAlternativeTypes() = mPreferredCachedAltDataTypes.Clone();
+ openArgs.referrerInfo() = mReferrerInfo;
+
+ AutoIPCStream autoStream(openArgs.uploadStream());
+ if (mUploadStream) {
+ autoStream.Serialize(mUploadStream, ContentChild::GetSingleton());
+ autoStream.TakeOptionalValue();
+ }
+
+ 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));
+
+ SerializeURI(mTopWindowURI, openArgs.topWindowURI());
+
+ 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.chooseApplicationCache() = LoadChooseApplicationCache();
+ openArgs.appCacheClientID() = appCacheClientId;
+ openArgs.allowSpdy() = LoadAllowSpdy();
+ openArgs.allowHttp3() = LoadAllowHttp3();
+ openArgs.allowAltSvc() = LoadAllowAltSvc();
+ openArgs.beConservative() = LoadBeConservative();
+ 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.corsMode() = mCorsMode;
+ openArgs.redirectMode() = mRedirectMode;
+
+ openArgs.channelId() = mChannelId;
+
+ openArgs.integrityMetadata() = mIntegrityMetadata;
+
+ openArgs.contentWindowId() = contentWindowId;
+ openArgs.topLevelOuterContentWindowId() = mTopLevelOuterContentWindowId;
+
+ LOG(("HttpChannelChild::ContinueAsyncOpen this=%p gid=%" PRIu64
+ " topwinid=%" PRIx64,
+ this, mChannelId, mTopLevelOuterContentWindowId));
+
+ if (browserChild && !browserChild->IPCOpen()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ 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.hasNonEmptySandboxingFlag() = GetHasNonEmptySandboxingFlag();
+
+ // 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;
+
+ 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;
+
+ 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::SetupFallbackChannel(const char* aFallbackKey) {
+ DROP_DEAD();
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetIsAuthChannel(bool* aIsAuthChannel) { DROP_DEAD(); }
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsICacheInfoChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetCacheTokenFetchCount(int32_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::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,
+ bool 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();
+
+ gNeckoChild->SetEventTargetForActor(stream, neckoTarget);
+
+ if (!gNeckoChild->SendPAltDataOutputStreamConstructor(
+ stream, nsCString(aType), aPredictedSize, 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::GetAltDataInputStream(const nsACString& aType,
+ nsIInputStreamReceiver* aReceiver) {
+ if (aReceiver == nullptr) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mAltDataInputStreamReceiver = aReceiver;
+ Unused << SendOpenAltDataCacheInputStream(nsCString(aType));
+
+ 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();
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvAltDataCacheInputStreamAvailable(
+ const Maybe<IPCStream>& aStream) {
+ nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aStream);
+ nsCOMPtr<nsIInputStreamReceiver> receiver;
+ receiver.swap(mAltDataInputStreamReceiver);
+ 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 == inFlags) {
+ return NS_OK;
+ }
+
+ mClassOfService = inFlags;
+
+ LOG(("HttpChannelChild %p ClassOfService=%u", this, mClassOfService));
+
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::AddClassFlags(uint32_t inFlags) {
+ mClassOfService |= inFlags;
+
+ LOG(("HttpChannelChild %p ClassOfService=%u", this, mClassOfService));
+
+ if (RemoteChannelExists()) {
+ SendSetClassOfService(mClassOfService);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::ClearClassFlags(uint32_t inFlags) {
+ mClassOfService &= ~inFlags;
+
+ LOG(("HttpChannelChild %p ClassOfService=%u", this, mClassOfService));
+
+ 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::nsIApplicationCacheContainer
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetApplicationCache(nsIApplicationCache** aApplicationCache) {
+ NS_IF_ADDREF(*aApplicationCache = mApplicationCache);
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetApplicationCache(nsIApplicationCache* aApplicationCache) {
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED);
+
+ mApplicationCache = aApplicationCache;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIApplicationCacheChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::GetApplicationCacheForWrite(
+ nsIApplicationCache** aApplicationCache) {
+ *aApplicationCache = nullptr;
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetApplicationCacheForWrite(
+ nsIApplicationCache* aApplicationCache) {
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED);
+
+ // Child channels are not intended to be used for cache writes
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetLoadedFromApplicationCache(
+ bool* aLoadedFromApplicationCache) {
+ *aLoadedFromApplicationCache = LoadLoadedFromApplicationCache();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetInheritApplicationCache(bool* aInherit) {
+ *aInherit = LoadInheritApplicationCache();
+ return NS_OK;
+}
+NS_IMETHODIMP
+HttpChannelChild::SetInheritApplicationCache(bool aInherit) {
+ StoreInheritApplicationCache(aInherit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetChooseApplicationCache(bool* aChoose) {
+ *aChoose = LoadChooseApplicationCache();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::SetChooseApplicationCache(bool aChoose) {
+ StoreChooseApplicationCache(aChoose);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::MarkOfflineCacheEntryAsForeign() {
+ SendMarkOfflineCacheEntryAsForeign();
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// 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) {
+ URIParams uri;
+ SerializeURI(aURI, uri);
+ PrincipalInfo principalInfo;
+ 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(uri, 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::GetIsLastPart(bool* aIsLastPart) {
+ if (!mMultiPartID) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aIsLastPart = mIsLastPartOfMultiPart;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::RetargetDeliveryTo(nsIEventTarget* aNewTarget) {
+ LOG(("HttpChannelChild::RetargetDeliveryTo [this=%p, aNewTarget=%p]", this,
+ aNewTarget));
+
+ MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
+ MOZ_ASSERT(!mODATarget);
+ 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);
+ mODATarget = aNewTarget;
+ }
+
+ mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::success;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelChild::GetDeliveryTarget(nsIEventTarget** aEventTarget) {
+ MutexAutoLock lock(mEventTargetMutex);
+
+ nsCOMPtr<nsIEventTarget> target = mODATarget;
+ if (!mODATarget) {
+ target = GetCurrentEventTarget();
+ }
+ 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));
+}
+
+void HttpChannelChild::OnCopyComplete(nsresult aStatus) {
+ nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<nsresult>(
+ "net::HttpBaseChannel::EnsureUploadStreamIsCloneableComplete", this,
+ &HttpChannelChild::EnsureUploadStreamIsCloneableComplete, aStatus);
+ nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
+ MOZ_ASSERT(neckoTarget);
+
+ Unused << neckoTarget->Dispatch(runnable, NS_DISPATCH_NORMAL);
+}
+
+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) {
+ LOG(("HttpChannelChild::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<HttpChannelChild>(this), aRv]() {
+ self->Cancel(aRv);
+ }));
+ mEventQ->Resume();
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvIssueDeprecationWarning(
+ const uint32_t& warning, const bool& asError) {
+ nsCOMPtr<nsIDeprecationWarner> warner;
+ GetCallback(warner);
+ if (warner) {
+ warner->IssueWarning(warning, asError);
+ }
+ return IPC_OK();
+}
+
+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::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::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 nsString& aMessage, const nsCString& aCategory) {
+ Unused << LogBlockedCORSRequest(aMessage, aCategory);
+ return IPC_OK();
+}
+
+NS_IMETHODIMP
+HttpChannelChild::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory) {
+ uint64_t innerWindowID = mLoadInfo->GetInnerWindowID();
+ bool privateBrowsing = !!mLoadInfo->GetOriginAttributes().mPrivateBrowsingId;
+ bool fromChromeContext =
+ mLoadInfo->TriggeringPrincipal()->IsSystemPrincipal();
+ nsCORSListenerProxy::LogBlockedCORSRequest(
+ innerWindowID, privateBrowsing, fromChromeContext, aMessage, aCategory);
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult HttpChannelChild::RecvLogMimeTypeMismatch(
+ const nsCString& aMessageName, const bool& aWarning, const nsString& aURL,
+ const nsString& 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::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 = mBgChild;
+ SocketProcessBridgeChild::GetSocketProcessBridge()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [bgChild]() {
+ gSocketTransportService->Dispatch(
+ NewRunnableMethod("HttpBackgroundChannelChild::CreateDataBridge",
+ bgChild,
+ &HttpBackgroundChannelChild::CreateDataBridge),
+ NS_DISPATCH_NORMAL);
+ },
+ []() { NS_WARNING("Failed to create SocketProcessBridgeChild"); });
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpChannelChild.h b/netwerk/protocol/http/HttpChannelChild.h
new file mode 100644
index 0000000000..fd44fa4c6d
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -0,0 +1,451 @@
+/* -*- 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/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 "nsICacheInfoChannel.h"
+#include "nsIApplicationCache.h"
+#include "nsIApplicationCacheChannel.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 nsInputStreamPump;
+class nsISerialEventTarget;
+class nsIInterceptedBodyCallback;
+
+#define HTTP_CHANNEL_CHILD_IID \
+ { \
+ 0x321bd99e, 0x2242, 0x4dc6, { \
+ 0xbb, 0xec, 0xd5, 0x06, 0x29, 0x7c, 0x39, 0x83 \
+ } \
+ }
+
+namespace mozilla {
+namespace net {
+
+class HttpBackgroundChannelChild;
+
+class HttpChannelChild final : public PHttpChannelChild,
+ public HttpBaseChannel,
+ public HttpAsyncAborter<HttpChannelChild>,
+ public nsICacheInfoChannel,
+ public nsIProxiedChannel,
+ public nsIApplicationCacheChannel,
+ 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_NSIAPPLICATIONCACHECONTAINER
+ NS_DECL_NSIAPPLICATIONCACHECHANNEL
+ 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 Cancel(nsresult status) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+ // nsIChannel
+ NS_IMETHOD GetSecurityInfo(nsISupports** 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 SetupFallbackChannel(const char* aFallbackKey) override;
+ NS_IMETHOD GetIsAuthChannel(bool* aIsAuthChannel) 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;
+ // nsIResumableChannel
+ NS_IMETHOD ResumeAt(uint64_t startPos, const nsACString& entityID) override;
+
+ nsresult SetReferrerHeader(const nsACString& aReferrer,
+ bool aRespectBeforeConnect) override;
+
+ [[nodiscard]] bool IsSuspended();
+
+ void OnCopyComplete(nsresult aStatus) override;
+
+ // Callback while background channel is ready.
+ void OnBackgroundChildReady(HttpBackgroundChannelChild* aBgChild);
+ // Callback while background channel is destroyed.
+ void OnBackgroundChildDestroyed(HttpBackgroundChannelChild* aBgChild);
+
+ nsresult CrossProcessRedirectFinished(nsresult aStatus);
+
+ protected:
+ mozilla::ipc::IPCResult RecvOnStartRequestSent() override;
+ mozilla::ipc::IPCResult RecvFailedAsyncOpen(const nsresult& status) override;
+ mozilla::ipc::IPCResult RecvRedirect1Begin(
+ const uint32_t& registrarId, const URIParams& newURI,
+ const uint32_t& newLoadFlags, const uint32_t& redirectFlags,
+ const ParentLoadInfoForwarderArgs& loadInfoForwarder,
+ const nsHttpResponseHead& responseHead,
+ const nsCString& securityInfoSerialization, const uint64_t& channelId,
+ const NetAddr& oldPeerAddr,
+ const ResourceTimingStructArgs& aTiming) override;
+ mozilla::ipc::IPCResult RecvRedirect3Complete() override;
+ mozilla::ipc::IPCResult RecvDeleteSelf() override;
+
+ mozilla::ipc::IPCResult RecvReportSecurityMessage(
+ const nsString& messageTag, const nsString& messageCategory) override;
+
+ mozilla::ipc::IPCResult RecvIssueDeprecationWarning(
+ const uint32_t& warning, const bool& asError) override;
+
+ mozilla::ipc::IPCResult RecvSetPriority(const int16_t& aPriority) override;
+
+ mozilla::ipc::IPCResult RecvOriginalCacheInputStreamAvailable(
+ const Maybe<IPCStream>& aStream) override;
+
+ mozilla::ipc::IPCResult RecvAltDataCacheInputStreamAvailable(
+ 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 nsString& aMessage, const nsCString& aCategory) override;
+ NS_IMETHOD LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory) override;
+
+ virtual mozilla::ipc::IPCResult RecvLogMimeTypeMismatch(
+ const nsCString& aMessageName, const bool& aWarning, const nsString& aURL,
+ const nsString& 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);
+
+ // Callbacks while receiving OnTransportAndData/OnStopRequest/OnProgress/
+ // OnStatus/FlushedForDiversion/DivertMessages on background IPC channel.
+ void ProcessOnTransportAndData(const nsresult& aChannelStatus,
+ const nsresult& aStatus,
+ const uint64_t& aOffset,
+ const uint32_t& aCount,
+ const nsCString& aData);
+ void ProcessOnStopRequest(const nsresult& aChannelStatus,
+ const ResourceTimingStructArgs& aTiming,
+ const nsHttpHeaderArray& aResponseTrailers,
+ nsTArray<ConsoleReportCollected>&& aConsoleReports,
+ bool aFromSocketProcess);
+ void ProcessOnConsoleReport(
+ nsTArray<ConsoleReportCollected>&& aConsoleReports);
+
+ void ProcessNotifyClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty);
+ void ProcessNotifyFlashPluginStateChanged(
+ nsIHttpChannel::FlashPluginState aState);
+ void ProcessSetClassifierMatchedInfo(const nsCString& aList,
+ const nsCString& aProvider,
+ const nsCString& aFullHash);
+ void ProcessSetClassifierMatchedTrackingInfo(const nsCString& aLists,
+ const nsCString& 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);
+
+ // 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, nsISupports* aContext);
+ void DoOnStatus(nsIRequest* aRequest, nsresult status);
+ void DoOnProgress(nsIRequest* aRequest, int64_t progress,
+ int64_t progressMax);
+ void DoOnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
+ nsIInputStream* aStream, uint64_t offset,
+ uint32_t count);
+ void DoPreOnStopRequest(nsresult aStatus);
+ void DoOnStopRequest(nsIRequest* aRequest, nsresult aChannelStatus,
+ nsISupports* aContext);
+ 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);
+
+ 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<nsIInputStreamReceiver> mAltDataInputStreamReceiver;
+
+ // Used to ensure atomicity of mBgChild and mBgInitFailCallback
+ Mutex mBgChildMutex;
+
+ // Associated HTTP background channel
+ RefPtr<HttpBackgroundChannelChild> mBgChild;
+
+ // Error handling procedure if failed to establish PBackground IPC
+ nsCOMPtr<nsIRunnable> mBgInitFailCallback;
+
+ // Remove the association with background channel after OnStopRequest
+ // or AsyncAbort.
+ void CleanupBackgroundChannel();
+
+ // Target thread for delivering ODA.
+ nsCOMPtr<nsIEventTarget> mODATarget;
+ // Used to ensure atomicity of mNeckoTarget / mODATarget;
+ Mutex mEventTargetMutex;
+
+ TimeStamp mLastStatusReported;
+
+ uint64_t mCacheEntryId;
+
+ // The result of RetargetDeliveryTo for this channel.
+ // |notRequested| represents OMT is not requested by the channel owner.
+ LABELS_HTTP_CHILD_OMT_STATS mOMTResult =
+ LABELS_HTTP_CHILD_OMT_STATS::notRequested;
+
+ uint32_t mCacheKey;
+ int32_t mCacheFetchCount;
+ uint32_t mCacheExpirationTime;
+
+ // 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;
+
+ Atomic<bool, SequentiallyConsistent> mIsFromCache;
+ Atomic<bool, SequentiallyConsistent> mIsRacing;
+ // Set if we get the result and cache |mNeedToReportBytesRead|
+ Atomic<bool, SequentiallyConsistent> mCacheNeedToReportBytesReadInitialized;
+ // True if we need to tell the parent the size of unreported received data
+ Atomic<bool, SequentiallyConsistent> mNeedToReportBytesRead;
+
+#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;
+ 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 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;
+
+ 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 AssociateApplicationCache(const nsCString& groupID,
+ const nsCString& clientID);
+ 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 nsCString& 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, const URIParams& newUri,
+ const uint32_t& newLoadFlags,
+ const uint32_t& redirectFlags,
+ const ParentLoadInfoForwarderArgs& loadInfoForwarder,
+ const nsHttpResponseHead& responseHead,
+ const nsACString& securityInfoSerialization,
+ const uint64_t& channelId,
+ const ResourceTimingStructArgs& timing);
+ void Redirect3Complete();
+ void DeleteSelf();
+ void DoNotifyListener();
+ void ContinueDoNotifyListener();
+ void OnAfterLastPart(const nsresult& aStatus);
+ void MaybeConnectToSocketProcess();
+
+ // 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();
+
+ 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 net
+} // namespace mozilla
+
+#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..0fe36f42b9
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParams.ipdlh
@@ -0,0 +1,57 @@
+/* -*- 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 "mozilla/dom/ReferrerInfoUtils.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";
+using refcounted class nsIReferrerInfo from "nsIReferrerInfo.h";
+
+namespace mozilla {
+namespace net {
+
+struct HttpChannelOnStartRequestArgs
+{
+ nsresult channelStatus;
+ ParentLoadInfoForwarderArgs loadInfoForwarder;
+ bool isFromCache;
+ bool isRacing;
+ bool cacheEntryAvailable;
+ uint64_t cacheEntryId;
+ int32_t cacheFetchCount;
+ uint32_t cacheExpirationTime;
+ nsCString securityInfoSerialization;
+ NetAddr selfAddr;
+ NetAddr peerAddr;
+ uint8_t redirectCount;
+ uint32_t cacheKey;
+ nsCString altDataType;
+ int64_t altDataLength;
+ bool deliveringAltData;
+ bool applyConversion;
+ bool isResolvedByTRR;
+ ResourceTimingStructArgs timing;
+ bool allRedirectsSameOrigin;
+ uint32_t? multiPartID;
+ bool isLastPartOfMultiPart;
+ CrossOriginOpenerPolicy openerPolicy;
+ nsCString appCacheGroupId;
+ nsCString appCacheClientId;
+ nsIReferrerInfo overrideReferrerInfo;
+ bool shouldWaitForOnStartRequestSent;
+ nsCString cookie;
+ bool dataFromSocketProcess;
+};
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp
new file mode 100644
index 0000000000..bc0fbab539
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -0,0 +1,2145 @@
+/* -*- 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/ConsoleReportCollector.h"
+#include "mozilla/ipc/FileDescriptorSetParent.h"
+#include "mozilla/ipc/IPCStreamUtils.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/net/NeckoParent.h"
+#include "mozilla/InputStreamLengthHelper.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "HttpBackgroundChannelParent.h"
+#include "ParentChannelListener.h"
+#include "nsHttpHandler.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsISupportsPriority.h"
+#include "nsIAuthPromptProvider.h"
+#include "mozilla/net/BackgroundChannelRegistrar.h"
+#include "nsSerializationHelper.h"
+#include "nsISerializable.h"
+#include "nsIApplicationCacheService.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/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 mozilla::BasePrincipal;
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+HttpChannelParent::HttpChannelParent(dom::BrowserParent* iframeEmbedding,
+ nsILoadContext* aLoadContext,
+ PBOverrideStatus aOverrideStatus)
+ : mLoadContext(aLoadContext),
+ mIPCClosed(false),
+ mPBOverride(aOverrideStatus),
+ mStatus(NS_OK),
+ mIgnoreProgress(false),
+ mSentRedirect1BeginFailed(false),
+ mReceivedRedirect2Verify(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;
+ }
+}
+
+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.chooseApplicationCache(), a.appCacheClientID(), a.allowSpdy(),
+ a.allowHttp3(), a.allowAltSvc(), a.beConservative(), a.tlsFlags(),
+ a.loadInfo(), a.cacheKey(), a.requestContextID(), a.preflightArgs(),
+ a.initialRwin(), a.blockAuthPrompt(), a.allowStaleCacheContent(),
+ a.preferCacheLoadOverBypass(), a.contentTypeHint(), a.corsMode(),
+ a.redirectMode(), a.channelId(), a.integrityMetadata(),
+ a.contentWindowId(), a.preferredAlternativeTypes(),
+ a.topLevelOuterContentWindowId(), a.launchServiceWorkerStart(),
+ a.launchServiceWorkerEnd(), a.dispatchFetchEventStart(),
+ a.dispatchFetchEventEnd(), a.handleFetchEventStart(),
+ a.handleFetchEventEnd(), a.forceMainDocumentChannel(),
+ a.navigationStartTimeStamp(), a.hasNonEmptySandboxingFlag());
+ }
+ 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 IProtocol::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(nsIAuthPromptProvider)
+ NS_INTERFACE_MAP_ENTRY(nsIParentRedirectingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIDeprecationWarner)
+ 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) {
+ // Only support nsIAuthPromptProvider in Content process
+ if (XRE_IsParentProcess() && aIID.Equals(NS_GET_IID(nsIAuthPromptProvider))) {
+ *result = nullptr;
+ return NS_OK;
+ }
+
+ // 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);
+ }
+}
+
+bool HttpChannelParent::DoAsyncOpen(
+ const URIParams& aURI, const Maybe<URIParams>& aOriginalURI,
+ const Maybe<URIParams>& aDocURI, nsIReferrerInfo* aReferrerInfo,
+ const Maybe<URIParams>& aAPIRedirectToURI,
+ const Maybe<URIParams>& aTopWindowURI, const uint32_t& aLoadFlags,
+ const RequestHeaderTuples& requestHeaders, const nsCString& requestMethod,
+ const Maybe<IPCStream>& uploadStream, const bool& uploadStreamHasHeaders,
+ const int16_t& priority, const uint32_t& classOfService,
+ const uint8_t& redirectionLimit, const bool& allowSTS,
+ const uint32_t& thirdPartyFlags, const bool& doResumeAt,
+ const uint64_t& startPos, const nsCString& entityID,
+ const bool& chooseApplicationCache, const nsCString& appCacheClientID,
+ const bool& allowSpdy, const bool& allowHttp3, const bool& allowAltSvc,
+ const bool& beConservative, const uint32_t& tlsFlags,
+ const Maybe<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 uint32_t& aCorsMode,
+ const uint32_t& aRedirectMode, const uint64_t& aChannelId,
+ const nsString& aIntegrityMetadata, const uint64_t& aContentWindowId,
+ const nsTArray<PreferredAlternativeDataTypeParams>&
+ aPreferredAlternativeTypes,
+ const uint64_t& aTopLevelOuterContentWindowId,
+ 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 bool& aHasNonEmptySandboxingFlag) {
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+ if (!uri) {
+ // URIParams does MOZ_ASSERT if null, but we need to protect opt builds from
+ // null deref here.
+ return false;
+ }
+ nsCOMPtr<nsIURI> originalUri = DeserializeURI(aOriginalURI);
+ nsCOMPtr<nsIURI> docUri = DeserializeURI(aDocURI);
+ nsCOMPtr<nsIURI> apiRedirectToUri = DeserializeURI(aAPIRedirectToURI);
+ nsCOMPtr<nsIURI> topWindowUri = DeserializeURI(aTopWindowURI);
+
+ LOG(("HttpChannelParent RecvAsyncOpen [this=%p uri=%s, gid=%" PRIu64
+ " topwinid=%" PRIx64 "]\n",
+ this, uri->GetSpecOrDefault().get(), aChannelId,
+ aTopLevelOuterContentWindowId));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
+ if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv);
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ rv = mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfoArgs,
+ getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannelInternal(getter_AddRefs(channel), uri, 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->SetCorsMode(aCorsMode);
+ httpChannel->SetRedirectMode(aRedirectMode);
+
+ // Set the channelId allocated in child to the parent instance
+ httpChannel->SetChannelId(aChannelId);
+ httpChannel->SetTopLevelContentWindowId(aContentWindowId);
+ httpChannel->SetTopLevelOuterContentWindowId(aTopLevelOuterContentWindowId);
+
+ 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 ? true : false);
+ }
+
+ if (doResumeAt) httpChannel->ResumeAt(startPos, entityID);
+
+ if (originalUri) httpChannel->SetOriginalURI(originalUri);
+ if (docUri) httpChannel->SetDocumentURI(docUri);
+ 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));
+ }
+
+ if (apiRedirectToUri) httpChannel->RedirectTo(apiRedirectToUri);
+ if (topWindowUri) {
+ httpChannel->SetTopWindowURI(topWindowUri);
+ }
+
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL)
+ httpChannel->SetLoadFlags(aLoadFlags);
+
+ if (aForceMainDocumentChannel) {
+ httpChannel->SetIsMainDocumentChannel(true);
+ }
+
+ if (aHasNonEmptySandboxingFlag) {
+ httpChannel->SetHasNonEmptySandboxingFlag(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);
+ }
+ }
+
+ RefPtr<ParentChannelListener> parentListener = new ParentChannelListener(
+ this, mBrowserParent ? mBrowserParent->GetBrowsingContext() : nullptr,
+ mLoadContext && mLoadContext->UsePrivateBrowsing());
+
+ 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) {
+ int64_t length;
+ if (InputStreamLengthHelper::GetSyncLength(stream, &length)) {
+ httpChannel->InternalSetUploadStreamLength(length >= 0 ? length : 0);
+ } else {
+ // Wait for the nputStreamLengthHelper::GetAsyncLength callback.
+ ++mAsyncOpenBarrier;
+
+ // Let's resolve the size of the stream. The following operation is always
+ // async.
+ RefPtr<HttpChannelParent> self = this;
+ InputStreamLengthHelper::GetAsyncLength(stream, [self, httpChannel](
+ int64_t aLength) {
+ httpChannel->InternalSetUploadStreamLength(aLength >= 0 ? aLength : 0);
+ self->TryInvokeAsyncOpen(NS_OK);
+ });
+ }
+
+ httpChannel->InternalSetUploadStream(stream);
+ httpChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders);
+ }
+
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel =
+ do_QueryInterface(static_cast<nsIChannel*>(httpChannel.get()));
+ if (cacheChannel) {
+ cacheChannel->SetCacheKey(aCacheKey);
+ for (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) {
+ httpChannel->SetClassFlags(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);
+
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChan =
+ do_QueryObject(httpChannel);
+ nsCOMPtr<nsIApplicationCacheService> appCacheService =
+ do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
+
+ bool setChooseApplicationCache = chooseApplicationCache;
+ if (appCacheChan && appCacheService) {
+ // We might potentially want to drop this flag (that is TRUE by default)
+ // after we successfully associate the channel with an application cache
+ // reported by the channel child. Dropping it here may be too early.
+ appCacheChan->SetInheritApplicationCache(false);
+ if (!appCacheClientID.IsEmpty()) {
+ nsCOMPtr<nsIApplicationCache> appCache;
+ rv = appCacheService->GetApplicationCache(appCacheClientID,
+ getter_AddRefs(appCache));
+ if (NS_SUCCEEDED(rv)) {
+ appCacheChan->SetApplicationCache(appCache);
+ setChooseApplicationCache = false;
+ }
+ }
+
+ if (setChooseApplicationCache) {
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetOriginAttributes(
+ httpChannel, attrs, StoragePrincipalHelper::eRegularPrincipal);
+
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(uri, attrs);
+
+ bool chooseAppCache = false;
+ // This works because we've already called SetNotificationCallbacks and
+ // done mPBOverride logic by this point.
+ chooseAppCache = NS_ShouldCheckAppCache(principal);
+
+ appCacheChan->SetChooseApplicationCache(chooseAppCache);
+ }
+ }
+
+ 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()
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self]() {
+ self->mRequest.Complete();
+ self->TryInvokeAsyncOpen(NS_OK);
+ },
+ [self](nsresult aStatus) {
+ self->mRequest.Complete();
+ self->TryInvokeAsyncOpen(aStatus);
+ })
+ ->Track(mRequest);
+
+ // The stream, received from the child process, must be cloneable and seekable
+ // in order to allow devtools to inspect its content.
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction("HttpChannelParent::EnsureUploadStreamIsCloneable",
+ [self]() { self->TryInvokeAsyncOpen(NS_OK); });
+ ++mAsyncOpenBarrier;
+ mChannel->EnsureUploadStreamIsCloneable(r);
+ return true;
+}
+
+RefPtr<GenericNonExclusivePromise> HttpChannelParent::WaitForBgParent() {
+ LOG(("HttpChannelParent::WaitForBgParent [this=%p]\n", this));
+ MOZ_ASSERT(!mBgParent);
+
+ if (!mChannel) {
+ return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
+ __func__);
+ }
+
+ nsCOMPtr<nsIBackgroundChannelRegistrar> registrar =
+ BackgroundChannelRegistrar::GetOrCreate();
+ MOZ_ASSERT(registrar);
+ registrar->LinkHttpChannel(mChannel->ChannelId(), 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_ERROR("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 ? true : false);
+ }
+ }
+
+ MOZ_ASSERT(!mBgParent);
+ MOZ_ASSERT(mPromise.IsEmpty());
+ // Waiting for background channel
+ RefPtr<HttpChannelParent> self = this;
+ WaitForBgParent()
+ ->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 uint32_t& cos) {
+ if (mChannel) {
+ mChannel->SetClassFlags(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) {
+ LOG(("HttpChannelParent::RecvCancel [this=%p]\n", this));
+
+ // May receive cancel before channel has been constructed!
+ if (mChannel) {
+ mChannel->Cancel(status);
+
+ 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;
+ 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,
+ const Maybe<URIParams>& aAPIRedirectURI,
+ const Maybe<CorsPreflightArgs>& aCorsPreflightArgs,
+ const bool& aChooseAppcache) {
+ 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) {
+ nsCOMPtr<nsIURI> apiRedirectUri = DeserializeURI(aAPIRedirectURI);
+
+ if (apiRedirectUri) {
+ rv = newHttpChannel->RedirectTo(apiRedirectUri);
+ 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));
+ }
+ }
+
+ nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
+ do_QueryInterface(newHttpChannel);
+ if (appCacheChannel) {
+ bool setChooseAppCache = false;
+ if (aChooseAppcache) {
+ nsCOMPtr<nsIURI> uri;
+ // Using GetURI because this is what DoAsyncOpen uses.
+ newHttpChannel->GetURI(getter_AddRefs(uri));
+
+ OriginAttributes attrs;
+ StoragePrincipalHelper::GetOriginAttributes(
+ newHttpChannel, attrs, StoragePrincipalHelper::eRegularPrincipal);
+
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(uri, attrs);
+
+ setChooseAppCache = NS_ShouldCheckAppCache(principal);
+ }
+
+ appCacheChannel->SetChooseApplicationCache(setChooseAppCache);
+ }
+
+ 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));
+ MOZ_ASSERT(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;
+ WaitForBgParent()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [callback]() { callback->ReadyToVerify(NS_OK); },
+ [callback](const nsresult& aResult) {
+ NS_ERROR("failed to establish the background channel");
+ callback->ReadyToVerify(aResult);
+ });
+ 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) {
+ // This should according the logic never happen, log the situation.
+ if (mReceivedRedirect2Verify) {
+ LOG(("RecvRedirect2Verify[%p]: Duplicate fire", this));
+ }
+ if (mSentRedirect1BeginFailed) {
+ LOG(("RecvRedirect2Verify[%p]: Send to child failed", this));
+ }
+ if ((mRedirectChannelId > 0) && NS_FAILED(aResult)) {
+ LOG(("RecvRedirect2Verify[%p]: Redirect failed", this));
+ }
+ if ((mRedirectChannelId > 0) && NS_SUCCEEDED(aResult)) {
+ LOG(("RecvRedirect2Verify[%p]: Redirect succeeded", this));
+ }
+ if (!mRedirectChannel) {
+ LOG(("RecvRedirect2Verify[%p]: Missing redirect channel", this));
+ }
+
+ NS_ERROR(
+ "Unexpcted call to HttpChannelParent::RecvRedirect2Verify, "
+ "mRedirectCallback null");
+ }
+
+ mReceivedRedirect2Verify = true;
+
+ 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;
+ }
+}
+
+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::RecvMarkOfflineCacheEntryAsForeign() {
+ if (mOfflineForeignMarker) {
+ mOfflineForeignMarker->MarkAsForeign();
+ mOfflineForeignMarker = nullptr;
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvRemoveCorsPreflightCacheEntry(
+ const URIParams& uri,
+ const mozilla::ipc::PrincipalInfo& requestingPrincipal,
+ const OriginAttributes& originAttributes) {
+ nsCOMPtr<nsIURI> deserializedURI = DeserializeURI(uri);
+ if (!deserializedURI) {
+ 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(deserializedURI, principal,
+ originAttributes);
+ return IPC_OK();
+}
+
+//-----------------------------------------------------------------------------
+// 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.
+
+ nsCString protocolVersion;
+ aChannel->GetProtocolVersion(protocolVersion);
+ args.protocolVersion() = protocolVersion;
+
+ aChannel->GetCacheReadStart(&timeStamp);
+ args.cacheReadStart() = timeStamp;
+
+ aChannel->GetCacheReadEnd(&timeStamp);
+ args.cacheReadEnd() = 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 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->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.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());
+
+ 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;
+
+ bool loadedFromApplicationCache = false;
+ httpChannelImpl->GetLoadedFromApplicationCache(&loadedFromApplicationCache);
+ if (loadedFromApplicationCache) {
+ mOfflineForeignMarker.reset(
+ httpChannelImpl->GetOfflineCacheEntryAsForeignMarker());
+ nsCOMPtr<nsIApplicationCache> appCache;
+ httpChannelImpl->GetApplicationCache(getter_AddRefs(appCache));
+ nsCString appCacheGroupId;
+ nsCString appCacheClientId;
+ appCache->GetGroupID(args.appCacheGroupId());
+ appCache->GetClientID(args.appCacheClientId());
+ }
+ }
+
+ // 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() = mCacheEntry ? true : false;
+
+ httpChannelImpl->GetCacheKey(&args.cacheKey());
+ httpChannelImpl->GetAlternativeDataType(args.altDataType());
+ }
+
+ args.altDataLength() = chan->GetAltDataLength();
+ args.deliveringAltData() = chan->IsDeliveringAltData();
+
+ UpdateAndSerializeSecurityInfo(args.securityInfoSerialization());
+
+ chan->GetRedirectCount(&args.redirectCount());
+
+ 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;
+ }
+
+ 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;
+
+ if (mIPCClosed ||
+ !mBgParent->OnStartRequest(
+ *responseHead, useResponseHead,
+ cleanedUpRequest ? cleanedUpRequestHeaders : requestHead->Headers(),
+ args)) {
+ 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();
+ }
+
+ 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);
+ if (httpChannel) {
+ httpChannel->StealConsoleReports(consoleReports);
+ }
+
+ // 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)) {
+ 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);
+ if (httpChannelImpl) {
+ if (httpChannelImpl->IsReadingFromCache()) {
+ transportStatus = NS_NET_STATUS_READING;
+ }
+ }
+
+ 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)) {
+ 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();
+ }
+ AutoIPCStream autoStream;
+ if (mCacheEntry) {
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(inputStream));
+ if (NS_SUCCEEDED(rv)) {
+ PContentParent* pcp = Manager()->Manager();
+ Unused << autoStream.Serialize(inputStream,
+ static_cast<ContentParent*>(pcp));
+ }
+ }
+
+ Unused << SendOriginalCacheInputStreamAvailable(
+ autoStream.TakeOptionalValue());
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult HttpChannelParent::RecvOpenAltDataCacheInputStream(
+ const nsCString& aType) {
+ if (mIPCClosed) {
+ return IPC_OK();
+ }
+ AutoIPCStream autoStream;
+ if (mCacheEntry) {
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = mCacheEntry->OpenAlternativeInputStream(
+ aType, getter_AddRefs(inputStream));
+ if (NS_SUCCEEDED(rv)) {
+ PContentParent* pcp = Manager()->Manager();
+ Unused << autoStream.Serialize(inputStream,
+ static_cast<ContentParent*>(pcp));
+ }
+ }
+
+ Unused << SendAltDataCacheInputStreamAvailable(
+ autoStream.TakeOptionalValue());
+ 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::NotifyFlashPluginStateChanged(
+ nsIHttpChannel::FlashPluginState aState) {
+ LOG(("HttpChannelParent::NotifyFlashPluginStateChanged [this=%p]\n", this));
+ if (!mIPCClosed) {
+ MOZ_ASSERT(mBgParent);
+ Unused << mBgParent->OnNotifyFlashPluginStateChanged(aState);
+ }
+ 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;
+}
+
+//-----------------------------------------------------------------------------
+// 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, 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);
+
+ // We only want to hide the special internal redirect from nsHttpChannel
+ // to InterceptedHttpChannel. We want to allow through internal redirects
+ // initiated from the InterceptedHttpChannel even if they are to another
+ // InterceptedHttpChannel.
+ if (!oldIntercepted && newIntercepted) {
+ // 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());
+ }
+
+ // Re-link the HttpChannelParent to the new InterceptedHttpChannel.
+ nsCOMPtr<nsIChannel> linkedChannel;
+ rv = NS_LinkRedirectChannels(mRedirectChannelId, this,
+ getter_AddRefs(linkedChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(linkedChannel == newChannel);
+
+ // We immediately store the InterceptedHttpChannel 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));
+
+ URIParams uriParams;
+ SerializeURI(newOriginalURI, uriParams);
+
+ uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL;
+ MOZ_ALWAYS_SUCCEEDS(newChannel->GetLoadFlags(&newLoadFlags));
+
+ nsCString secInfoSerialization;
+ UpdateAndSerializeSecurityInfo(secInfoSerialization);
+
+ // 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;
+ }
+
+ bool result = false;
+ if (!mIPCClosed) {
+ result = SendRedirect1Begin(
+ mRedirectChannelId, uriParams, newLoadFlags, redirectFlags,
+ loadInfoForwarderArg, *responseHead, secInfoSerialization, channelId,
+ mChannel->GetPeerAddr(), GetTimingAttributes(mChannel));
+ }
+ if (!result) {
+ // Bug 621446 investigation
+ mSentRedirect1BeginFailed = true;
+ return NS_BINDING_ABORTED;
+ }
+
+ // Result is handled in RecvRedirect2Verify above
+
+ mRedirectChannel = newChannel;
+ mRedirectCallback = callback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::CompleteRedirect(bool succeeded) {
+ LOG(("HttpChannelParent::CompleteRedirect [this=%p succeeded=%d]\n", this,
+ succeeded));
+
+ // 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 (succeeded && !mIPCClosed) {
+ // TODO: check return value: assume child dead if failed
+ Unused << SendRedirect3Complete();
+ }
+
+ 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;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::GetAuthPrompt(uint32_t aPromptReason, const nsIID& iid,
+ void** aResult) {
+ nsCOMPtr<nsIAuthPrompt2> prompt =
+ new NeckoParent::NestedFrameAuthPrompt(Manager(), TabId(0));
+ prompt.forget(aResult);
+ return NS_OK;
+}
+
+void HttpChannelParent::UpdateAndSerializeSecurityInfo(
+ nsACString& aSerializedSecurityInfoOut) {
+ nsCOMPtr<nsISupports> secInfoSupp;
+ mChannel->GetSecurityInfo(getter_AddRefs(secInfoSupp));
+ if (secInfoSupp) {
+ nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(secInfoSupp);
+ if (secInfoSer) {
+ NS_SerializeToString(secInfoSer, aSerializedSecurityInfoOut);
+ }
+ }
+}
+
+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;
+}
+
+NS_IMETHODIMP
+HttpChannelParent::IssueWarning(uint32_t aWarning, bool aAsError) {
+ Unused << SendIssueDeprecationWarning(aWarning, aAsError);
+ 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) {
+ if (mIPCClosed || NS_WARN_IF(!SendLogBlockedCORSRequest(
+ nsString(aMessage), nsCString(aCategory)))) {
+ 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(bool succeeded) {
+ LOG(("HttpChannelParent::OnRedirectResult [this=%p, suc=%d]", this,
+ succeeded));
+
+ nsresult rv;
+
+ 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) {
+ succeeded = false;
+ }
+
+ CompleteRedirect(succeeded);
+
+ if (succeeded) {
+ 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));
+}
+
+void HttpChannelParent::SetCookie(nsCString&& aCookie) {
+ LOG(("HttpChannelParent::SetCookie [this=%p]", this));
+ MOZ_ASSERT(!mAfterOnStartRequestBegun);
+ MOZ_ASSERT(mCookie.IsEmpty());
+ mCookie = std::move(aCookie);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h
new file mode 100644
index 0000000000..7dd16f39c5
--- /dev/null
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -0,0 +1,327 @@
+/* -*- 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 "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 "nsIAuthPromptProvider.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "nsIDeprecationWarner.h"
+#include "nsIMultiPartChannel.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;
+
+// Note: nsIInterfaceRequestor must be the first base so that do_QueryObject()
+// works correctly on this object, as it's needed to compute a void* pointing to
+// the beginning of this object.
+
+class HttpChannelParent final : public nsIInterfaceRequestor,
+ public PHttpChannelParent,
+ public nsIParentRedirectingChannel,
+ public nsIProgressEventSink,
+ public nsIAuthPromptProvider,
+ public nsIDeprecationWarner,
+ 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_NSIAUTHPROMPTPROVIDER
+ NS_DECL_NSIDEPRECATIONWARNER
+ 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 aStatus);
+
+ [[nodiscard]] bool Init(const HttpChannelCreationArgs& aOpenArgs);
+
+ // 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);
+
+ // 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);
+
+ protected:
+ // used to connect redirected-to channel in parent with just created
+ // ChildChannel. Used during redirects.
+ [[nodiscard]] bool ConnectChannel(const uint32_t& channelId);
+
+ [[nodiscard]] bool DoAsyncOpen(
+ const URIParams& uri, const Maybe<URIParams>& originalUri,
+ const Maybe<URIParams>& docUri, nsIReferrerInfo* aReferrerInfo,
+ const Maybe<URIParams>& internalRedirectUri,
+ const Maybe<URIParams>& topWindowUri, const uint32_t& loadFlags,
+ const RequestHeaderTuples& requestHeaders, const nsCString& requestMethod,
+ const Maybe<IPCStream>& uploadStream, const bool& uploadStreamHasHeaders,
+ const int16_t& priority, const uint32_t& classOfService,
+ const uint8_t& redirectionLimit, const bool& allowSTS,
+ const uint32_t& thirdPartyFlags, const bool& doResumeAt,
+ const uint64_t& startPos, const nsCString& entityID,
+ const bool& chooseApplicationCache, const nsCString& appCacheClientID,
+ const bool& allowSpdy, const bool& allowHttp3, const bool& allowAltSvc,
+ const bool& beConservative, const uint32_t& tlsFlags,
+ const Maybe<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 uint32_t& aCorsMode, const uint32_t& aRedirectMode,
+ const uint64_t& aChannelId, const nsString& aIntegrityMetadata,
+ const uint64_t& aContentWindowId,
+ const nsTArray<PreferredAlternativeDataTypeParams>&
+ aPreferredAlternativeTypes,
+ const uint64_t& aTopLevelOuterContentWindowId,
+ 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 bool& hasNonEmptySandboxingFlag);
+
+ virtual mozilla::ipc::IPCResult RecvSetPriority(
+ const int16_t& priority) override;
+ virtual mozilla::ipc::IPCResult RecvSetClassOfService(
+ const uint32_t& 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) 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,
+ const Maybe<URIParams>& apiRedirectUri,
+ const Maybe<CorsPreflightArgs>& aCorsPreflightArgs,
+ const bool& aChooseAppcache) override;
+ virtual mozilla::ipc::IPCResult RecvDocumentChannelCleanup(
+ const bool& clearCacheEntry) override;
+ virtual mozilla::ipc::IPCResult RecvMarkOfflineCacheEntryAsForeign() override;
+ virtual mozilla::ipc::IPCResult RecvRemoveCorsPreflightCacheEntry(
+ const URIParams& uri,
+ const mozilla::ipc::PrincipalInfo& requestingPrincipal,
+ const OriginAttributes& originAttributes) override;
+ virtual mozilla::ipc::IPCResult RecvBytesRead(const int32_t& aCount) override;
+ virtual mozilla::ipc::IPCResult RecvOpenOriginalCacheInputStream() override;
+ virtual mozilla::ipc::IPCResult RecvOpenAltDataCacheInputStream(
+ const nsCString& aType) 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) 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:
+ void UpdateAndSerializeSecurityInfo(nsACString& aSerializedSecurityInfoOut);
+
+ // 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();
+
+ // 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();
+ int32_t mSendWindowSize;
+
+ friend class HttpBackgroundChannelParent;
+
+ RefPtr<HttpBaseChannel> mChannel;
+ nsCOMPtr<nsICacheEntry> mCacheEntry;
+
+ nsCOMPtr<nsIChannel> mRedirectChannel;
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+
+ UniquePtr<class nsHttpChannel::OfflineCacheEntryAsForeignMarker>
+ mOfflineForeignMarker;
+ 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 mSentRedirect1BeginFailed : 1;
+ uint8_t mReceivedRedirect2Verify : 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..747ac3cccf
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionBase.cpp
@@ -0,0 +1,70 @@
+/* -*- 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()
+ : mTransactionCaps(0),
+ mExperienced(false),
+ mBootstrappedTimingsSet(false),
+ mTotalBytesWritten(0),
+ mCallbacksLock("nsHttpConnection::mCallbacksLock"),
+ mRtt(0) {
+ 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);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpConnectionBase.h b/netwerk/protocol/http/HttpConnectionBase.h
new file mode 100644
index 0000000000..cd503e0d69
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionBase.h
@@ -0,0 +1,208 @@
+/* -*- 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 "TunnelUtils.h"
+#include "mozilla/Mutex.h"
+#include "ARefBase.h"
+#include "TimingStruct.h"
+#include "HttpTrafficAnalyzer.h"
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsITimer.h"
+
+class nsISocketTransport;
+class nsISSLSocketControl;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpHandler;
+class ASpdySession;
+
+// 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();
+
+ // 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,
+ nsIInterfaceRequestor*, PRIntervalTime) = 0;
+
+ // 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;
+
+ nsISocketTransport* Transport() { return mSocketTransport; }
+ 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;
+
+ 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 bool CanAcceptWebsocket() { return false; }
+
+ void GetConnectionInfo(nsHttpConnectionInfo** ci) {
+ NS_IF_ADDREF(*ci = mConnInfo);
+ }
+ virtual void GetSecurityInfo(nsISupports** 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;
+ int64_t BytesWritten() { return mTotalBytesWritten; } // 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;
+
+ protected:
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+
+ // The capabailities associated with the most recent transaction
+ uint32_t mTransactionCaps;
+
+ RefPtr<nsHttpConnectionInfo> mConnInfo;
+
+ bool mExperienced;
+
+ bool mBootstrappedTimingsSet;
+ TimingStruct mBootstrappedTimings;
+
+ int64_t mTotalBytesWritten; // does not include CONNECT tunnel
+
+ Mutex mCallbacksLock;
+ nsMainThreadPtrHandle<nsIInterfaceRequestor> mCallbacks;
+
+ nsTArray<HttpTrafficCategory> mTrafficCategory;
+ PRIntervalTime mRtt;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpConnectionBase, HTTPCONNECTIONBASE_IID)
+
+#define NS_DECL_HTTPCONNECTIONBASE \
+ [[nodiscard]] nsresult Init( \
+ nsHttpConnectionInfo*, uint16_t, nsISocketTransport*, \
+ nsIAsyncInputStream*, nsIAsyncOutputStream*, bool, \
+ nsIInterfaceRequestor*, PRIntervalTime) override; \
+ [[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 GetSecurityInfo(nsISupports** 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;
+
+} // 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..e76d170c8a
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrChild.cpp
@@ -0,0 +1,182 @@
+/* -*- 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 "nsHttpConnectionInfo.h"
+#include "nsHttpConnectionMgr.h"
+#include "nsHttpHandler.h"
+#include "nsISpeculativeConnect.h"
+
+namespace mozilla {
+namespace 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::RecvUpdateCurrentTopLevelOuterContentWindowId(
+ const uint64_t& aWindowId) {
+ mConnMgr->UpdateCurrentTopLevelOuterContentWindowId(aWindowId);
+ 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 uint32_t& 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(
+ 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();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpConnectionMgrChild.h b/netwerk/protocol/http/HttpConnectionMgrChild.h
new file mode 100644
index 0000000000..d382f8dece
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrChild.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef HttpConnectionMgrChild_h__
+#define HttpConnectionMgrChild_h__
+
+#include "mozilla/net/PHttpConnectionMgrChild.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+namespace 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 RecvUpdateCurrentTopLevelOuterContentWindowId(
+ const uint64_t& aWindowId);
+ 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 uint32_t& aClassOfService);
+ mozilla::ipc::IPCResult RecvCancelTransaction(PHttpTransactionChild* aTrans,
+ const nsresult& aReason);
+ mozilla::ipc::IPCResult RecvSpeculativeConnect(
+ HttpConnectionInfoCloneArgs aConnInfo,
+ Maybe<SpeculativeConnectionOverriderArgs> aOverriderArgs, uint32_t aCaps,
+ Maybe<PAltSvcTransactionChild*> aTrans, const bool& aFetchHTTPSRR);
+
+ private:
+ virtual ~HttpConnectionMgrChild();
+
+ RefPtr<nsHttpConnectionMgr> mConnMgr;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // HttpConnectionMgrChild_h__
diff --git a/netwerk/protocol/http/HttpConnectionMgrParent.cpp b/netwerk/protocol/http/HttpConnectionMgrParent.cpp
new file mode 100644
index 0000000000..f50ce84af7
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrParent.cpp
@@ -0,0 +1,273 @@
+/* -*- 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 "nsHttpConnectionInfo.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsISpeculativeConnect.h"
+#include "nsIOService.h"
+#include "nsQueryObject.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS0(HttpConnectionMgrParent)
+
+HttpConnectionMgrParent::HttpConnectionMgrParent() : mShutDown(false) {}
+
+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::UpdateCurrentTopLevelOuterContentWindowId(
+ uint64_t aWindowId) {
+ RefPtr<HttpConnectionMgrParent> self = this;
+ auto task = [self, aWindowId]() {
+ Unused << self->SendUpdateCurrentTopLevelOuterContentWindowId(aWindowId);
+ };
+ 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(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(
+ aTrans->AsHttpTransactionParent(), aPriority,
+ 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(aTrans->AsHttpTransactionParent(),
+ aPriority);
+ return NS_OK;
+}
+
+void HttpConnectionMgrParent::UpdateClassOfServiceOnTransaction(
+ HttpTransactionShell* aTrans, uint32_t aClassOfService) {
+ MOZ_ASSERT(gIOService->SocketProcessReady());
+
+ if (!CanSend()) {
+ return;
+ }
+
+ Unused << SendUpdateClassOfServiceOnTransaction(
+ aTrans->AsHttpTransactionParent(), aClassOfService);
+}
+
+nsresult HttpConnectionMgrParent::CancelTransaction(
+ HttpTransactionShell* aTrans, nsresult aReason) {
+ MOZ_ASSERT(gIOService->SocketProcessReady());
+
+ if (!CanSend()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ Unused << SendCancelTransaction(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<AltSvcTransactionParent*> maybeTrans;
+ if (trans) {
+ maybeTrans.emplace(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) {
+ MOZ_ASSERT_UNREACHABLE("ExcludeHttp2 should not be called");
+}
+
+void HttpConnectionMgrParent::ExcludeHttp3(const nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT_UNREACHABLE("ExcludeHttp3 should not be called");
+}
+
+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) {
+ // TODO: fix this in bug 1497249
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsHttpConnectionMgr* HttpConnectionMgrParent::AsHttpConnectionMgr() {
+ return nullptr;
+}
+
+HttpConnectionMgrParent* HttpConnectionMgrParent::AsHttpConnectionMgrParent() {
+ return this;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpConnectionMgrParent.h b/netwerk/protocol/http/HttpConnectionMgrParent.h
new file mode 100644
index 0000000000..46d6670cdc
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrParent.h
@@ -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/. */
+
+#ifndef HttpConnectionMgrParent_h__
+#define HttpConnectionMgrParent_h__
+
+#include "HttpConnectionMgrShell.h"
+#include "mozilla/net/PHttpConnectionMgrParent.h"
+
+namespace mozilla {
+namespace 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();
+
+ private:
+ virtual ~HttpConnectionMgrParent() = default;
+
+ bool mShutDown;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // HttpConnectionMgrParent_h__
diff --git a/netwerk/protocol/http/HttpConnectionMgrShell.h b/netwerk/protocol/http/HttpConnectionMgrShell.h
new file mode 100644
index 0000000000..da1944730d
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionMgrShell.h
@@ -0,0 +1,232 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 {
+namespace net {
+
+class ARefBase;
+class EventTokenBucket;
+class HttpTransactionShell;
+class nsHttpConnectionInfo;
+class HttpConnectionBase;
+class nsHttpConnectionMgr;
+class HttpConnectionMgrParent;
+class SpeculativeTransaction;
+
+//----------------------------------------------------------------------------
+// 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 UpdateCurrentTopLevelOuterContentWindowId(
+ uint64_t aWindowId) = 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*,
+ uint32_t 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 UpdateCurrentTopLevelOuterContentWindowId( \
+ uint64_t aWindowId) 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*, uint32_t 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 net
+} // namespace mozilla
+
+#endif // HttpConnectionMgrShell_h__
diff --git a/netwerk/protocol/http/HttpConnectionUDP.cpp b/netwerk/protocol/http/HttpConnectionUDP.cpp
new file mode 100644
index 0000000000..4d25b24ef6
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionUDP.cpp
@@ -0,0 +1,772 @@
+/* -*- 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/ChaosMode.h"
+#include "mozilla/Telemetry.h"
+#include "HttpConnectionUDP.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+#include "nsIClassOfService.h"
+#include "nsIOService.h"
+#include "nsISocketTransport.h"
+#include "nsSocketTransportService2.h"
+#include "nsISSLSocketControl.h"
+#include "nsISupportsPriority.h"
+#include "nsPreloadedStream.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransport2.h"
+#include "nsStringStream.h"
+#include "mozpkix/pkixnss.h"
+#include "sslt.h"
+#include "NSSErrorsService.h"
+#include "TunnelUtils.h"
+#include "TCPFastOpenLayer.h"
+#include "Http3Session.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// HttpConnectionUDP <public>
+//-----------------------------------------------------------------------------
+
+HttpConnectionUDP::HttpConnectionUDP()
+ : mHttpHandler(gHttpHandler),
+ mLastReadTime(0),
+ mLastWriteTime(0),
+ mTotalBytesRead(0),
+ mConnectedTransport(false),
+ mDontReuse(false),
+ mIsReused(false),
+ mLastTransactionExpectedNoContent(false),
+ mPriority(nsISupportsPriority::PRIORITY_NORMAL),
+ mForceSendPending(false),
+ mLastRequestBytesSentTime(0) {
+ LOG(("Creating HttpConnectionUDP @%p\n", this));
+
+ mThroughCaptivePortal = gHttpHandler->GetThroughCaptivePortal();
+}
+
+HttpConnectionUDP::~HttpConnectionUDP() {
+ LOG(("Destroying HttpConnectionUDP @%p\n", this));
+
+ 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;
+ }
+}
+
+nsresult HttpConnectionUDP::Init(
+ nsHttpConnectionInfo* info, uint16_t maxHangTime,
+ nsISocketTransport* transport, nsIAsyncInputStream* instream,
+ nsIAsyncOutputStream* outstream, bool connectedTransport,
+ nsIInterfaceRequestor* callbacks, PRIntervalTime rtt) {
+ LOG1(("HttpConnectionUDP::Init this=%p sockettransport=%p", this, transport));
+ NS_ENSURE_ARG_POINTER(info);
+ NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
+
+ mConnectedTransport = connectedTransport;
+ mConnInfo = info;
+ MOZ_ASSERT(mConnInfo);
+
+ mLastWriteTime = mLastReadTime = PR_IntervalNow();
+ mRtt = rtt;
+
+ mSocketTransport = transport;
+ mSocketIn = instream;
+ mSocketOut = outstream;
+
+ MOZ_ASSERT(mConnInfo->IsHttp3());
+ mHttp3Session = new Http3Session();
+ nsresult rv = mHttp3Session->Init(mConnInfo, mSocketTransport, this);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("HttpConnectionUDP::Init mHttp3Session->Init failed "
+ "[this=%p rv=%x]\n",
+ this, static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ // See explanation for non-strictness of this operation in
+ // SetSecurityCallbacks.
+ mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(
+ "HttpConnectionUDP::mCallbacks", callbacks, false);
+
+ mSocketTransport->SetEventSink(this, nullptr);
+ mSocketTransport->SetSecurityCallbacks(this);
+
+ 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()) {
+ // For QUIC and TFO we have HttpConnecitonUDP before the actual connection
+ // has been establish so wait fo TFO and TLS handshake to be finished before
+ // we mark the connection 'experienced'.
+ if (!mExperienced && 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);
+
+ // 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 (!mConnectedTransport) {
+ uint32_t count;
+ nsresult rv = NS_OK;
+ if (mSocketOut) {
+ rv = mSocketOut->Write("", 0, &count);
+ }
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG(("HttpConnectionUDP::Activate [this=%p] Bad Socket %" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv)));
+ mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ CloseTransaction(mHttp3Session, rv);
+ trans->Close(rv);
+ return rv;
+ }
+ }
+
+ if (!mHttp3Session->AddStream(trans, pri, mCallbacks)) {
+ MOZ_ASSERT(false); // this cannot happen!
+ trans->Close(NS_ERROR_ABORT);
+ return NS_ERROR_FAILURE;
+ }
+
+ 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 (mForceSendTimer) {
+ mForceSendTimer->Cancel();
+ mForceSendTimer = nullptr;
+ }
+
+ if (!mTrafficCategory.IsEmpty()) {
+ HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer();
+ if (hta) {
+ hta->IncrementHttpConnection(std::move(mTrafficCategory));
+ MOZ_ASSERT(mTrafficCategory.IsEmpty());
+ }
+ }
+
+ if (mSocketTransport) {
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mSocketTransport->Close(reason);
+ if (mSocketOut) {
+ mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+
+ if (mSocketIn) {
+ mSocketIn->AsyncWait(nullptr, 0, 0, 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 (!mSocketTransport || !mConnectedTransport) 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?");
+
+ if (mHttp3Session) {
+ DebugOnly<nsresult> rv = responseHead->SetHeader(
+ nsHttp::X_Firefox_Http3, mHttp3Session->GetAlpnToken());
+ 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() - mLastWriteTime) < 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::GetSecurityInfo(nsISupports** secinfo) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("HttpConnectionUDP::GetSecurityInfo http3Session=%p socket=%p\n",
+ mHttp3Session.get(), mSocketTransport.get()));
+
+ if (mHttp3Session &&
+ NS_SUCCEEDED(mHttp3Session->GetTransactionSecurityInfo(secinfo))) {
+ return;
+ }
+
+ if (mSocketTransport &&
+ NS_SUCCEEDED(mSocketTransport->GetSecurityInfo(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) {
+ 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<HttpConnectionUDP> mConn;
+ bool mDoRecv;
+};
+
+nsresult HttpConnectionUDP::ResumeSend() {
+ LOG(("HttpConnectionUDP::ResumeSend [this=%p]\n", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mSocketOut) {
+ nsresult rv = mSocketOut->AsyncWait(this, 0, 0, nullptr);
+ LOG(("HttpConnectionUDP::ResumeSend [this=%p]\n", this));
+ return rv;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("no socket output stream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+nsresult HttpConnectionUDP::ResumeRecv() {
+ LOG(("HttpConnectionUDP::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) {
+ return mSocketIn->AsyncWait(this, 0, 0, nullptr);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("no socket input stream");
+ return NS_ERROR_UNEXPECTED;
+}
+
+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; }
+
+//-----------------------------------------------------------------------------
+// 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;
+}
+
+nsresult HttpConnectionUDP::OnReadSegment(const char* buf, uint32_t count,
+ uint32_t* countRead) {
+ LOG(("HttpConnectionUDP::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
+ }
+
+ nsresult rv = mSocketOut->Write(buf, count, countRead);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (*countRead == 0) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ mLastWriteTime = PR_IntervalNow();
+ mTotalBytesWritten += *countRead;
+
+ return NS_OK;
+}
+
+nsresult HttpConnectionUDP::OnSocketWritable() {
+ LOG(("HttpConnectionUDP::OnSocketWritable [this=%p] host=%s\n", this,
+ mConnInfo->Origin()));
+
+ if (!mHttp3Session) {
+ LOG((" No session In OnSocketWritable\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t transactionBytes = 0;
+ bool again = true;
+ LOG((" writing transaction request stream\n"));
+ nsresult rv = mHttp3Session->ReadSegmentsAgain(
+ this, nsIOService::gDefaultSegmentSize, &transactionBytes, &again);
+ return rv;
+}
+
+nsresult HttpConnectionUDP::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)) {
+ return rv;
+ }
+
+ if (*countWritten == 0) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ return NS_OK;
+}
+
+void HttpConnectionUDP::OnQuicTimeout(nsITimer* aTimer, void* aClosure) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("HttpConnectionUDP::OnQuicTimeout [this=%p]\n", aClosure));
+
+ HttpConnectionUDP* self = static_cast<HttpConnectionUDP*>(aClosure);
+ self->OnQuicTimeoutExpired();
+}
+
+void HttpConnectionUDP::OnQuicTimeoutExpired() {
+ // if the transaction was dropped...
+ if (!mHttp3Session) {
+ LOG((" no transaction; ignoring event\n"));
+ return;
+ }
+
+ nsresult rv = mHttp3Session->ProcessOutputAndEvents();
+ if (NS_FAILED(rv)) {
+ CloseTransaction(mHttp3Session, rv);
+ }
+}
+
+nsresult HttpConnectionUDP::OnSocketReadable() {
+ LOG(("HttpConnectionUDP::OnSocketReadable [this=%p]\n", this));
+
+ if (!mHttp3Session) {
+ LOG((" No session In OnSocketReadable\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ PRIntervalTime now = PR_IntervalNow();
+
+ // 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;
+
+ uint32_t n = 0;
+ bool again = true;
+
+ nsresult rv = mHttp3Session->WriteSegmentsAgain(
+ this, nsIOService::gDefaultSegmentSize, &n, &again);
+ LOG(("HttpConnectionUDP::OnSocketReadable %p trans->ws rv=%" PRIx32
+ " n=%d \n",
+ this, static_cast<uint32_t>(rv), n));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mTotalBytesRead += n;
+
+ return 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(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(HttpConnectionUDP)
+NS_INTERFACE_MAP_END
+
+//-----------------------------------------------------------------------------
+// HttpConnectionUDP::nsIInputStreamCallback
+//-----------------------------------------------------------------------------
+
+// called on the socket transport thread
+NS_IMETHODIMP
+HttpConnectionUDP::OnInputStreamReady(nsIAsyncInputStream* in) {
+ MOZ_ASSERT(in == mSocketIn, "unexpected stream");
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // if the transaction was dropped...
+ if (!mHttp3Session) {
+ LOG((" no transaction; ignoring event\n"));
+ return NS_OK;
+ }
+
+ nsresult rv = OnSocketReadable();
+ if (NS_FAILED(rv)) CloseTransaction(mHttp3Session, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpConnectionUDP::nsIOutputStreamCallback
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpConnectionUDP::OnOutputStreamReady(nsIAsyncOutputStream* out) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(out == mSocketOut, "unexpected socket");
+ // if the transaction was dropped...
+ if (!mHttp3Session) {
+ LOG((" no transaction; ignoring event\n"));
+ return NS_OK;
+ }
+
+ nsresult rv = OnSocketWritable();
+ if (NS_FAILED(rv)) CloseTransaction(mHttp3Session, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpConnectionUDP::nsITransportEventSink
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpConnectionUDP::OnTransportStatus(nsITransport* trans, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ if (mHttp3Session) mHttp3Session->OnTransportStatus(trans, status, progress);
+ 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; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpConnectionUDP.h b/netwerk/protocol/http/HttpConnectionUDP.h
new file mode 100644
index 0000000000..fccc0707b5
--- /dev/null
+++ b/netwerk/protocol/http/HttpConnectionUDP.h
@@ -0,0 +1,131 @@
+/* -*- 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 "TunnelUtils.h"
+#include "mozilla/Mutex.h"
+#include "ARefBase.h"
+#include "TimingStruct.h"
+#include "HttpTrafficAnalyzer.h"
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsITimer.h"
+#include "Http3Session.h"
+
+class nsISocketTransport;
+class nsISSLSocketControl;
+
+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 nsAHttpSegmentReader,
+ public nsAHttpSegmentWriter,
+ public nsIInputStreamCallback,
+ public nsIOutputStreamCallback,
+ public nsITransportEventSink,
+ public nsIInterfaceRequestor {
+ private:
+ virtual ~HttpConnectionUDP();
+
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(HTTPCONNECTIONUDP_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
+
+ HttpConnectionUDP();
+
+ friend class HttpConnectionUDPForceIO;
+
+ [[nodiscard]] static nsresult ReadFromStream(nsIInputStream*, void*,
+ const char*, uint32_t, uint32_t,
+ uint32_t*);
+
+ bool UsingHttp3() override { return true; }
+
+ static void OnQuicTimeout(nsITimer* aTimer, void* aClosure);
+ void OnQuicTimeoutExpired();
+
+ private:
+ [[nodiscard]] nsresult OnTransactionDone(nsresult reason);
+ [[nodiscard]] nsresult OnSocketWritable();
+ [[nodiscard]] nsresult OnSocketReadable();
+
+ private:
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+
+ PRIntervalTime mLastReadTime;
+ PRIntervalTime mLastWriteTime;
+ int64_t mTotalBytesRead; // total data read
+
+ RefPtr<nsIAsyncInputStream> mInputOverflow;
+
+ bool mConnectedTransport;
+ bool mDontReuse;
+ bool mIsReused;
+ bool mLastTransactionExpectedNoContent;
+
+ int32_t mPriority;
+
+ private:
+ // For ForceSend()
+ static void ForceSendIO(nsITimer* aTimer, void* aClosure);
+ [[nodiscard]] nsresult MaybeForceSendIO();
+ bool mForceSendPending;
+ nsCOMPtr<nsITimer> mForceSendTimer;
+
+ PRIntervalTime mLastRequestBytesSentTime;
+
+ private:
+ bool mThroughCaptivePortal;
+
+ // Http3
+ RefPtr<Http3Session> mHttp3Session;
+};
+
+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..0cb05e6afd
--- /dev/null
+++ b/netwerk/protocol/http/HttpLog.h
@@ -0,0 +1,70 @@
+/* -*- 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 {
+void LogCallingScriptLocation(void* instance);
+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..4fdabef14a
--- /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 const nsCString 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..f2ee2cdff7
--- /dev/null
+++ b/netwerk/protocol/http/HttpTransactionChild.cpp
@@ -0,0 +1,646 @@
+/* -*- 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/ipc/BackgroundParent.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"
+
+using mozilla::ipc::BackgroundParent;
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(HttpTransactionChild, nsIRequestObserver, nsIStreamListener,
+ nsITransportEventSink, nsIThrottledInputChannel,
+ nsIThreadRetargetableStreamListener);
+
+//-----------------------------------------------------------------------------
+// HttpTransactionChild <public>
+//-----------------------------------------------------------------------------
+
+HttpTransactionChild::HttpTransactionChild()
+ : mCanceled(false),
+ mStatus(NS_OK),
+ mChannelId(0),
+ mIsDocumentLoad(false),
+ mLogicalOffset(0) {
+ 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 topLevelOuterContentWindowId, uint8_t httpTrafficCategory,
+ uint64_t requestContextID, uint32_t 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().transWithPushedStreamChild());
+ transWithPushedStream = transChild->GetHttpTransaction();
+ pushedStreamId = aPushedStreamArg.ref().pushedStreamId();
+ }
+
+ nsresult rv = mTransaction->Init(
+ caps, cinfo, requestHead, requestBody, requestContentLength,
+ requestBodyHasHeaders, GetCurrentEventTarget(),
+ nullptr, // TODO: security callback, fix in bug 1512479.
+ this, topLevelOuterContentWindowId,
+ 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 uint32_t& 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);
+ };
+
+ LOG((" ODA to parent process"));
+ if (!nsHttp::SendDataInChunks(data, aOffset, aCount, sendFunc)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+ }
+
+ ipc::AssertIsOnBackgroundThread();
+ 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);
+ };
+
+ 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, to 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);
+ };
+
+ 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;
+ 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();
+
+ nsCString serializedSecurityInfoOut;
+ nsCOMPtr<nsISupports> secInfoSupp = mTransaction->SecurityInfo();
+ if (secInfoSupp) {
+ nsCOMPtr<nsITransportSecurityInfo> info = do_QueryInterface(secInfoSupp);
+ nsAutoCString protocol;
+ if (info && NS_SUCCEEDED(info->GetNegotiatedNPN(protocol)) &&
+ !protocol.IsEmpty()) {
+ mProtocolVersion.Assign(protocol);
+ }
+ nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(secInfoSupp);
+ if (secInfoSer) {
+ NS_SerializeToString(secInfoSer, serializedSecurityInfoOut);
+ }
+ }
+
+ 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 (mTransaction->Caps() & NS_HTTP_CALL_CONTENT_SNIFFER) {
+ nsAutoCString contentTypeOptionsHeader;
+ if (!(head->GetContentTypeOptionsHeader(contentTypeOptionsHeader) &&
+ contentTypeOptionsHeader.EqualsIgnoreCase("nosniff"))) {
+ 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<nsIThread> 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();
+
+ Unused << SendOnStartRequest(
+ status, optionalHead, serializedSecurityInfoOut,
+ mTransaction->ProxyConnectFailed(),
+ ToTimingStructArgs(mTransaction->Timings()), proxyConnectResponseCode,
+ dataForSniffer, optionalAltSvcUsed, !!mDataBridgeParent,
+ mTransaction->TakeRestartedState(), mTransaction->HTTPSSVCReceivedStage(),
+ mTransaction->GetSupportsHTTP3());
+ 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.protocolVersion() = mProtocolVersion;
+ 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());
+ 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());
+ 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, mTransaction->Caps(), infoArgs);
+
+ 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;
+ if (mTransaction) {
+ mTransaction->GetNetworkAddresses(selfAddr, peerAddr, isTrr,
+ echConfigUsed);
+ } else {
+ nsCOMPtr<nsISocketTransport> socketTransport =
+ do_QueryInterface(aTransport);
+ if (socketTransport) {
+ socketTransport->GetSelfAddr(&selfAddr);
+ socketTransport->GetPeerAddr(&peerAddr);
+ socketTransport->ResolvedByTRR(&isTrr);
+ socketTransport->GetEchConfigUsed(&echConfigUsed);
+ }
+ }
+ arg.emplace(selfAddr, peerAddr, isTrr, 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;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpTransactionChild.h b/netwerk/protocol/http/HttpTransactionChild.h
new file mode 100644
index 0000000000..89129d9db1
--- /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 "nsIRequest.h"
+#include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsIThrottledInputChannel.h"
+#include "nsITransport.h"
+
+class nsInputStreamPump;
+
+namespace mozilla {
+namespace 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 nsIStreamListener,
+ public nsITransportEventSink,
+ public nsIThrottledInputChannel,
+ public nsIThreadRetargetableStreamListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSITHROTTLEDINPUTCHANNEL
+ NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+
+ 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 uint32_t& 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& aArgs,
+ nsHttpRequestHead* reqHeaders,
+ nsIInputStream* reqBody, // use the trick in bug 1277681
+ uint64_t reqContentLength, bool reqBodyIncludesHeaders,
+ uint64_t topLevelOuterContentWindowId, uint8_t httpTrafficCategory,
+ uint64_t requestContextID, uint32_t 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;
+ Atomic<nsresult, ReleaseAcquire> mStatus;
+ uint64_t mChannelId;
+ nsHttpRequestHead mRequestHead;
+ bool mIsDocumentLoad;
+ uint64_t mLogicalOffset;
+ 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 net
+} // namespace mozilla
+
+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..53cb9d10cb
--- /dev/null
+++ b/netwerk/protocol/http/HttpTransactionParent.cpp
@@ -0,0 +1,909 @@
+/* -*- 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/InputChannelThrottleQueueParent.h"
+#include "mozilla/net/ChannelEventQueue.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "nsHttpHandler.h"
+#include "nsQueryObject.h"
+#include "nsSerializationHelper.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+
+namespace mozilla {
+namespace 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");
+
+ if (!mRefCnt.isThreadSafe) {
+ NS_ASSERT_OWNINGTHREAD(HttpTransactionParent);
+ }
+
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "HttpTransactionParent");
+
+ if (count == 0) {
+ if (!nsAutoRefCnt::isThreadSafe) {
+ NS_ASSERT_OWNINGTHREAD(HttpTransactionParent);
+ }
+
+ 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)
+ : mEventTargetMutex("HttpTransactionParent::EventTargetMutex"),
+ mResponseIsComplete(false),
+ mTransferSize(0),
+ mRequestSize(0),
+ mProxyConnectFailed(false),
+ mCanceled(false),
+ mStatus(NS_OK),
+ mSuspendCount(0),
+ mResponseHeadTaken(false),
+ mResponseTrailersTaken(false),
+ mOnStartRequestCalled(false),
+ mOnStopRequestCalled(false),
+ mResolvedByTRR(false),
+ mEchConfigUsed(false),
+ mProxyConnectResponseCode(0),
+ mChannelId(0),
+ mDataSentToChildProcess(false),
+ mIsDocumentLoad(aIsDocumentLoad),
+ mRestarted(false),
+ mCaps(0) {
+ LOG(("Creating HttpTransactionParent @%p\n", this));
+
+ this->mSelfAddr.inet = {};
+ this->mPeerAddr.inet = {};
+
+#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;
+
+ mEventQ = new ChannelEventQueue(static_cast<nsIRequest*>(this));
+}
+
+HttpTransactionParent::~HttpTransactionParent() {
+ LOG(("Destroying HttpTransactionParent @%p\n", this));
+}
+
+//-----------------------------------------------------------------------------
+// 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 topLevelOuterContentWindowId, HttpTrafficCategory trafficCategory,
+ nsIRequestContext* requestContext, uint32_t 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 = GetCurrentEventTarget();
+ mChannelId = channelId;
+ mTransactionObserver = std::move(transactionObserver);
+ mOnPushCallback = std::move(aOnPushCallback);
+ mCaps = caps;
+ mConnInfo = cinfo->Clone();
+ mIsHttp3Used = cinfo->IsHttp3();
+
+ HttpConnectionInfoCloneArgs infoArgs;
+ nsHttpConnectionInfo::SerializeHttpConnectionInfo(cinfo, infoArgs);
+
+ mozilla::ipc::AutoIPCStream autoStream;
+ if (requestBody &&
+ !autoStream.Serialize(requestBody, SocketProcessParent::GetSingleton())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint64_t requestContextID = requestContext ? requestContext->GetID() : 0;
+
+ Maybe<H2PushedStreamArg> pushedStreamArg;
+ if (aTransWithPushedStream && aPushedStreamId) {
+ MOZ_ASSERT(aTransWithPushedStream->AsHttpTransactionParent());
+ pushedStreamArg.emplace();
+ pushedStreamArg.ref().transWithPushedStreamParent() =
+ aTransWithPushedStream->AsHttpTransactionParent();
+ pushedStreamArg.ref().pushedStreamId() = aPushedStreamId;
+ }
+
+ nsCOMPtr<nsIThrottledInputChannel> throttled = do_QueryInterface(mEventsink);
+ Maybe<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(tqParent.get());
+ }
+ }
+
+ // TODO: Figure out if we have to implement nsIThreadRetargetableRequest in
+ // bug 1544378.
+ if (!SendInit(caps, infoArgs, *requestHead,
+ requestBody ? Some(autoStream.TakeValue()) : Nothing(),
+ requestContentLength, requestBodyHasHeaders,
+ topLevelOuterContentWindowId,
+ 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(nsIEventTarget** aEventTarget) {
+ MutexAutoLock lock(mEventTargetMutex);
+
+ nsCOMPtr<nsIEventTarget> target = mODATarget;
+ if (!mODATarget) {
+ target = mTargetThread;
+ }
+ target.forget(aEventTarget);
+ return NS_OK;
+}
+
+already_AddRefed<nsIEventTarget> HttpTransactionParent::GetODATarget() {
+ nsCOMPtr<nsIEventTarget> target;
+ {
+ MutexAutoLock lock(mEventTargetMutex);
+ target = mODATarget ? mODATarget : mTargetThread;
+ }
+
+ if (!target) {
+ target = GetMainThreadEventTarget();
+ }
+ return target.forget();
+}
+
+NS_IMETHODIMP HttpTransactionParent::RetargetDeliveryTo(
+ nsIEventTarget* 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,
+ bool& aEchConfigUsed) {
+ self = mSelfAddr;
+ peer = mPeerAddr;
+ aResolvedByTRR = mResolvedByTRR;
+ 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;
+}
+
+const 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<nsISupports> 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 = GetMainThreadEventTarget();
+ return target.forget();
+}
+
+mozilla::ipc::IPCResult HttpTransactionParent::RecvOnStartRequest(
+ const nsresult& aStatus, const Maybe<nsHttpResponseHead>& aResponseHead,
+ const nsCString& aSecurityInfoSerialization,
+ 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) {
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<HttpTransactionParent>(this), aStatus,
+ aResponseHead, aSecurityInfoSerialization, aProxyConnectFailed,
+ aTimings, aProxyConnectResponseCode,
+ aDataForSniffer = CopyableTArray{std::move(aDataForSniffer)},
+ aAltSvcUsed, aDataToChildProcess, aRestarted,
+ aHTTPSSVCReceivedStage, aSupportsHttp3]() mutable {
+ self->DoOnStartRequest(
+ aStatus, aResponseHead, aSecurityInfoSerialization,
+ aProxyConnectFailed, aTimings, aProxyConnectResponseCode,
+ std::move(aDataForSniffer), aAltSvcUsed, aDataToChildProcess,
+ aRestarted, aHTTPSSVCReceivedStage, aSupportsHttp3);
+ }));
+ 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();
+}
+
+void HttpTransactionParent::DoOnStartRequest(
+ const nsresult& aStatus, const Maybe<nsHttpResponseHead>& aResponseHead,
+ const nsCString& aSecurityInfoSerialization,
+ 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) {
+ 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;
+
+ if (!aSecurityInfoSerialization.IsEmpty()) {
+ NS_DeserializeObject(aSecurityInfoSerialization,
+ getter_AddRefs(mSecurityInfo));
+ }
+
+ 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(
+ nsDependentCString(nsHttp::Alternate_Service_Used), 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();
+ 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) {
+ 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]() { self->DoOnDataAvailable(aData, aOffset, aCount); }));
+ return IPC_OK();
+}
+
+void HttpTransactionParent::DoOnDataAvailable(const nsCString& aData,
+ const uint64_t& aOffset,
+ const uint32_t& aCount) {
+ 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;
+ }
+
+ 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 uint32_t& aCaps,
+ const HttpConnectionInfoCloneArgs& aArgs) {
+ 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)},
+ aCaps, cinfo{std::move(cinfo)}]() mutable {
+ self->DoOnStopRequest(aStatus, aResponseIsComplete, aTransferSize,
+ aTimings, aResponseTrailers,
+ std::move(aTransactionObserverResult), aCaps,
+ cinfo);
+ }));
+ 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,
+ const uint32_t& aCaps, nsHttpConnectionInfo* aConnInfo) {
+ 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;
+
+ TimingStructArgsToTimingsStruct(aTimings, mTimings);
+
+ if (aResponseTrailers.isSome()) {
+ mResponseTrailers = MakeUnique<nsHttpHeaderArray>(aResponseTrailers.ref());
+ }
+ mCaps = aCaps;
+ 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
+
+//-----------------------------------------------------------------------------
+// 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::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; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/HttpTransactionParent.h b/netwerk/protocol/http/HttpTransactionParent.h
new file mode 100644
index 0000000000..ec7b032c67
--- /dev/null
+++ b/netwerk/protocol/http/HttpTransactionParent.h
@@ -0,0 +1,170 @@
+/* -*- 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 "nsHttp.h"
+#include "nsCOMPtr.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsITransport.h"
+#include "nsIRequest.h"
+
+namespace mozilla {
+namespace 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,
+ const nsCString& aSecurityInfoSerialization,
+ 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);
+ 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);
+ 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 uint32_t& aCaps,
+ const HttpConnectionInfoCloneArgs& aArgs);
+ mozilla::ipc::IPCResult RecvOnInitFailed(const nsresult& aStatus);
+
+ mozilla::ipc::IPCResult RecvOnH2PushStream(const uint32_t& aPushedStreamId,
+ const nsCString& aResourceUrl,
+ const nsCString& aRequestString);
+
+ already_AddRefed<nsIEventTarget> GetNeckoTarget();
+
+ void SetSniffedTypeToChannel(
+ nsInputStreamPump::PeekSegmentFun aCallTypeSniffers,
+ nsIChannel* aChannel);
+
+ void SetRedirectTimestamp(TimeStamp aRedirectStart, TimeStamp aRedirectEnd) {
+ mRedirectStart = aRedirectStart;
+ mRedirectEnd = aRedirectEnd;
+ }
+
+ private:
+ virtual ~HttpTransactionParent();
+
+ void GetStructFromInfo(nsHttpConnectionInfo* aInfo,
+ HttpConnectionInfoCloneArgs& aArgs);
+ void DoOnStartRequest(
+ const nsresult& aStatus, const Maybe<nsHttpResponseHead>& aResponseHead,
+ const nsCString& aSecurityInfoSerialization,
+ 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);
+ void DoOnDataAvailable(const nsCString& aData, const uint64_t& aOffset,
+ const uint32_t& aCount);
+ void DoOnStopRequest(
+ const nsresult& aStatus, const bool& aResponseIsComplete,
+ const int64_t& aTransferSize, const TimingStructArgs& aTimings,
+ const Maybe<nsHttpHeaderArray>& responseTrailers,
+ Maybe<TransactionObserverResult>&& aTransactionObserverResult,
+ const uint32_t& aCaps, nsHttpConnectionInfo* aConnInfo);
+ void DoNotifyListener();
+ void ContinueDoNotifyListener();
+ // Get event target for ODA.
+ already_AddRefed<nsIEventTarget> GetODATarget();
+ void CancelOnMainThread(nsresult aRv);
+ void HandleAsyncAbort();
+
+ nsCOMPtr<nsITransportEventSink> mEventsink;
+ nsCOMPtr<nsIStreamListener> mChannel;
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+ nsCOMPtr<nsIEventTarget> mODATarget;
+ Mutex mEventTargetMutex;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+ UniquePtr<nsHttpResponseHead> mResponseHead;
+ UniquePtr<nsHttpHeaderArray> mResponseTrailers;
+ RefPtr<ChannelEventQueue> mEventQ;
+
+ bool mResponseIsComplete;
+ int64_t mTransferSize;
+ int64_t mRequestSize;
+ bool mIsHttp3Used = false;
+ bool mProxyConnectFailed;
+ Atomic<bool, ReleaseAcquire> mCanceled;
+ Atomic<nsresult, ReleaseAcquire> mStatus;
+ int32_t mSuspendCount;
+ bool mResponseHeadTaken;
+ bool mResponseTrailersTaken;
+ bool mOnStartRequestCalled;
+ bool mOnStopRequestCalled;
+ bool mResolvedByTRR;
+ bool mEchConfigUsed = false;
+ int32_t mProxyConnectResponseCode;
+ uint64_t mChannelId;
+ bool mDataSentToChildProcess;
+ bool mIsDocumentLoad;
+ bool mRestarted;
+ uint32_t mCaps;
+ TimeStamp mRedirectStart;
+ TimeStamp mRedirectEnd;
+
+ NetAddr mSelfAddr;
+ NetAddr mPeerAddr;
+ TimingStruct mTimings;
+ TimeStamp mDomainLookupStart;
+ TimeStamp mDomainLookupEnd;
+ 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 net
+} // namespace mozilla
+
+#endif // nsHttpTransactionParent_h__
diff --git a/netwerk/protocol/http/HttpTransactionShell.h b/netwerk/protocol/http/HttpTransactionShell.h
new file mode 100644
index 0000000000..00e2f198cb
--- /dev/null
+++ b/netwerk/protocol/http/HttpTransactionShell.h
@@ -0,0 +1,227 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsISupports.h"
+#include "TimingStruct.h"
+#include "nsInputStreamPump.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+
+class nsIEventTraget;
+class nsIInputStream;
+class nsIInterfaceRequestor;
+class nsIRequest;
+class nsIRequestContext;
+class nsITransportEventSink;
+
+namespace mozilla {
+namespace 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 topLevelOuterContentWindowId
+ // indicate the top level outer content window 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 topLevelOuterContentWindowId,
+ HttpTrafficCategory trafficCategory, nsIRequestContext* requestContext,
+ uint32_t 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<nsISupports> SecurityInfo() = 0;
+ virtual void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) = 0;
+
+ virtual void GetNetworkAddresses(NetAddr& self, NetAddr& peer,
+ bool& aResolvedByTRR,
+ 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 const TimingStruct Timings() = 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;
+};
+
+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 topLevelOuterContentWindowId, \
+ HttpTrafficCategory trafficCategory, nsIRequestContext* requestContext, \
+ uint32_t 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<nsISupports> SecurityInfo() override; \
+ virtual void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) \
+ override; \
+ virtual void GetNetworkAddresses(NetAddr& self, NetAddr& peer, \
+ bool& aResolvedByTRR, 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 const 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;
+} // namespace net
+} // namespace mozilla
+
+#endif // HttpTransactionShell_h__
diff --git a/netwerk/protocol/http/InterceptedChannel.cpp b/netwerk/protocol/http/InterceptedChannel.cpp
new file mode 100644
index 0000000000..09a0f701ee
--- /dev/null
+++ b/netwerk/protocol/http/InterceptedChannel.cpp
@@ -0,0 +1,232 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: -*- */
+/* 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/. */
+
+#include "HttpLog.h"
+
+#include "InterceptedChannel.h"
+#include "nsInputStreamPump.h"
+#include "nsITimedChannel.h"
+#include "nsHttpChannel.h"
+#include "HttpChannelChild.h"
+#include "nsHttpResponseHead.h"
+#include "nsNetUtil.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/dom/ChannelInfo.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+extern nsresult DoUpdateExpirationTime(nsHttpChannel* aSelf,
+ nsICacheEntry* aCacheEntry,
+ nsHttpResponseHead* aResponseHead,
+ uint32_t& aExpirationTime);
+extern nsresult DoAddCacheEntryHeaders(nsHttpChannel* self,
+ nsICacheEntry* entry,
+ nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ nsISupports* securityInfo);
+
+NS_IMPL_ISUPPORTS(InterceptedChannelBase, nsIInterceptedChannel)
+
+InterceptedChannelBase::InterceptedChannelBase(
+ nsINetworkInterceptController* aController)
+ : mController(aController),
+ mReportCollector(new ConsoleReportCollector()),
+ mClosed(false),
+ mSynthesizedOrReset(Invalid) {}
+
+void InterceptedChannelBase::EnsureSynthesizedResponse() {
+ if (mSynthesizedResponseHead.isNothing()) {
+ mSynthesizedResponseHead.emplace(new nsHttpResponseHead());
+ }
+}
+
+void InterceptedChannelBase::DoNotifyController() {
+ nsresult rv = NS_OK;
+
+ if (NS_WARN_IF(!mController)) {
+ rv = ResetInterception();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to resume intercepted network request");
+ CancelInterception(rv);
+ }
+ return;
+ }
+
+ rv = mController->ChannelIntercepted(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ rv = ResetInterception();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to resume intercepted network request");
+ CancelInterception(rv);
+ }
+ }
+ mController = nullptr;
+}
+
+void InterceptedChannelBase::DoSynthesizeStatus(uint16_t aStatus,
+ const nsACString& aReason) {
+ EnsureSynthesizedResponse();
+
+ // Always assume HTTP 1.1 for synthesized responses.
+ nsAutoCString statusLine;
+ statusLine.AppendLiteral("HTTP/1.1 ");
+ statusLine.AppendInt(aStatus);
+ statusLine.AppendLiteral(" ");
+ statusLine.Append(aReason);
+
+ (*mSynthesizedResponseHead)->ParseStatusLine(statusLine);
+}
+
+nsresult InterceptedChannelBase::DoSynthesizeHeader(const nsACString& aName,
+ const nsACString& aValue) {
+ EnsureSynthesizedResponse();
+
+ nsAutoCString header = aName + ": "_ns + aValue;
+ // Overwrite any existing header.
+ return (*mSynthesizedResponseHead)->ParseHeaderLine(header);
+}
+
+NS_IMETHODIMP
+InterceptedChannelBase::GetConsoleReportCollector(
+ nsIConsoleReportCollector** aCollectorOut) {
+ MOZ_ASSERT(aCollectorOut);
+ nsCOMPtr<nsIConsoleReportCollector> ref = mReportCollector;
+ ref.forget(aCollectorOut);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelBase::SetReleaseHandle(nsISupports* aHandle) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mReleaseHandle);
+ MOZ_ASSERT(aHandle);
+
+ // We need to keep it and mChannel alive until destructor clear it up.
+ mReleaseHandle = aHandle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelBase::SaveTimeStamps() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If we were not able to start the fetch event for some reason (like
+ // corrupted scripts), then just do nothing here.
+ if (mHandleFetchEventStart.IsNull()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIChannel> underlyingChannel;
+ nsresult rv = GetChannel(getter_AddRefs(underlyingChannel));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(underlyingChannel);
+ MOZ_ASSERT(timedChannel);
+
+ rv = timedChannel->SetLaunchServiceWorkerStart(mLaunchServiceWorkerStart);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = timedChannel->SetLaunchServiceWorkerEnd(mLaunchServiceWorkerEnd);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = timedChannel->SetDispatchFetchEventStart(mDispatchFetchEventStart);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = timedChannel->SetDispatchFetchEventEnd(mDispatchFetchEventEnd);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = timedChannel->SetHandleFetchEventStart(mHandleFetchEventStart);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = timedChannel->SetHandleFetchEventEnd(mHandleFetchEventEnd);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIChannel> channel;
+ GetChannel(getter_AddRefs(channel));
+ if (NS_WARN_IF(!channel)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isNonSubresourceRequest =
+ nsContentUtils::IsNonSubresourceRequest(channel);
+ nsCString navigationOrSubresource =
+ isNonSubresourceRequest ? "navigation"_ns : "subresource"_ns;
+
+ nsAutoCString subresourceKey(""_ns);
+ GetSubresourceTimeStampKey(channel, subresourceKey);
+
+ // We may have null timestamps if the fetch dispatch runnable was cancelled
+ // and we defaulted to resuming the request.
+ if (!mFinishResponseStart.IsNull() && !mFinishResponseEnd.IsNull()) {
+ MOZ_ASSERT(mSynthesizedOrReset != Invalid);
+
+ Telemetry::HistogramID id =
+ (mSynthesizedOrReset == Synthesized)
+ ? Telemetry::
+ SERVICE_WORKER_FETCH_EVENT_FINISH_SYNTHESIZED_RESPONSE_MS
+ : Telemetry::SERVICE_WORKER_FETCH_EVENT_CHANNEL_RESET_MS;
+ Telemetry::Accumulate(
+ id, navigationOrSubresource,
+ static_cast<uint32_t>(
+ (mFinishResponseEnd - mFinishResponseStart).ToMilliseconds()));
+ if (!isNonSubresourceRequest && !subresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(
+ id, subresourceKey,
+ static_cast<uint32_t>(
+ (mFinishResponseEnd - mFinishResponseStart).ToMilliseconds()));
+ }
+ }
+
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS,
+ navigationOrSubresource,
+ static_cast<uint32_t>((mHandleFetchEventStart - mDispatchFetchEventStart)
+ .ToMilliseconds()));
+
+ if (!isNonSubresourceRequest && !subresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS,
+ subresourceKey,
+ static_cast<uint32_t>((mHandleFetchEventStart -
+ mDispatchFetchEventStart)
+ .ToMilliseconds()));
+ }
+
+ if (!mFinishResponseEnd.IsNull()) {
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS,
+ navigationOrSubresource,
+ static_cast<uint32_t>(
+ (mFinishResponseEnd - mDispatchFetchEventStart).ToMilliseconds()));
+ if (!isNonSubresourceRequest && !subresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS,
+ subresourceKey,
+ static_cast<uint32_t>((mFinishResponseEnd - mDispatchFetchEventStart)
+ .ToMilliseconds()));
+ }
+ }
+
+ return rv;
+}
+
+/* static */
+already_AddRefed<nsIURI> InterceptedChannelBase::SecureUpgradeChannelURI(
+ nsIChannel* aChannel) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIURI> upgradedURI;
+ rv = NS_GetSecureUpgradedURI(uri, getter_AddRefs(upgradedURI));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return upgradedURI.forget();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/InterceptedChannel.h b/netwerk/protocol/http/InterceptedChannel.h
new file mode 100644
index 0000000000..53e96bfe30
--- /dev/null
+++ b/netwerk/protocol/http/InterceptedChannel.h
@@ -0,0 +1,155 @@
+/* -*- 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 InterceptedChannel_h
+#define InterceptedChannel_h
+
+#include "nsINetworkInterceptController.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Maybe.h"
+
+class nsICacheEntry;
+class nsInputStreamPump;
+class nsIStreamListener;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpChannel;
+class HttpChannelChild;
+class nsHttpResponseHead;
+class InterceptStreamListener;
+
+// An object representing a channel that has been intercepted. This avoids
+// complicating the actual channel implementation with the details of
+// synthesizing responses.
+class InterceptedChannelBase : public nsIInterceptedChannel {
+ protected:
+ // The interception controller to notify about the successful channel
+ // interception
+ nsCOMPtr<nsINetworkInterceptController> mController;
+
+ // Response head for use when synthesizing
+ Maybe<UniquePtr<nsHttpResponseHead>> mSynthesizedResponseHead;
+
+ nsCOMPtr<nsIConsoleReportCollector> mReportCollector;
+ nsCOMPtr<nsISupports> mReleaseHandle;
+
+ bool mClosed;
+
+ void EnsureSynthesizedResponse();
+ void DoNotifyController();
+ void DoSynthesizeStatus(uint16_t aStatus, const nsACString& aReason);
+ [[nodiscard]] nsresult DoSynthesizeHeader(const nsACString& aName,
+ const nsACString& aValue);
+
+ TimeStamp mLaunchServiceWorkerStart;
+ TimeStamp mLaunchServiceWorkerEnd;
+ TimeStamp mDispatchFetchEventStart;
+ TimeStamp mDispatchFetchEventEnd;
+ TimeStamp mHandleFetchEventStart;
+ TimeStamp mHandleFetchEventEnd;
+
+ TimeStamp mFinishResponseStart;
+ TimeStamp mFinishResponseEnd;
+ enum { Invalid = 0, Synthesized, Reset } mSynthesizedOrReset;
+
+ virtual ~InterceptedChannelBase() = default;
+
+ public:
+ explicit InterceptedChannelBase(nsINetworkInterceptController* aController);
+
+ // Notify the interception controller that the channel has been intercepted
+ // and prepare the response body output stream.
+ virtual void NotifyController() = 0;
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD GetConsoleReportCollector(
+ nsIConsoleReportCollector** aCollectorOut) override;
+ NS_IMETHOD SetReleaseHandle(nsISupports* aHandle) override;
+
+ NS_IMETHODIMP
+ SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) override {
+ mLaunchServiceWorkerStart = aTimeStamp;
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP
+ GetLaunchServiceWorkerStart(TimeStamp* aTimeStamp) override {
+ MOZ_DIAGNOSTIC_ASSERT(aTimeStamp);
+ *aTimeStamp = mLaunchServiceWorkerStart;
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP
+ SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) override {
+ mLaunchServiceWorkerEnd = aTimeStamp;
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP
+ GetLaunchServiceWorkerEnd(TimeStamp* aTimeStamp) override {
+ MOZ_DIAGNOSTIC_ASSERT(aTimeStamp);
+ *aTimeStamp = mLaunchServiceWorkerEnd;
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP
+ SetDispatchFetchEventStart(TimeStamp aTimeStamp) override {
+ mDispatchFetchEventStart = aTimeStamp;
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP
+ SetDispatchFetchEventEnd(TimeStamp aTimeStamp) override {
+ mDispatchFetchEventEnd = aTimeStamp;
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP
+ SetHandleFetchEventStart(TimeStamp aTimeStamp) override {
+ mHandleFetchEventStart = aTimeStamp;
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP
+ SetHandleFetchEventEnd(TimeStamp aTimeStamp) override {
+ mHandleFetchEventEnd = aTimeStamp;
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP
+ SetFinishResponseStart(TimeStamp aTimeStamp) override {
+ mFinishResponseStart = aTimeStamp;
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP
+ SetFinishSynthesizedResponseEnd(TimeStamp aTimeStamp) override {
+ MOZ_ASSERT(mSynthesizedOrReset == Invalid);
+ mSynthesizedOrReset = Synthesized;
+ mFinishResponseEnd = aTimeStamp;
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP
+ SetChannelResetEnd(TimeStamp aTimeStamp) override {
+ MOZ_ASSERT(mSynthesizedOrReset == Invalid);
+ mSynthesizedOrReset = Reset;
+ mFinishResponseEnd = aTimeStamp;
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP SaveTimeStamps() override;
+
+ static already_AddRefed<nsIURI> SecureUpgradeChannelURI(nsIChannel* aChannel);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // InterceptedChannel_h
diff --git a/netwerk/protocol/http/InterceptedHttpChannel.cpp b/netwerk/protocol/http/InterceptedHttpChannel.cpp
new file mode 100644
index 0000000000..667990b9d4
--- /dev/null
+++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp
@@ -0,0 +1,1311 @@
+/* -*- 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 "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 "nsIRedirectResultListener.h"
+#include "nsStringStream.h"
+#include "nsStreamUtils.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS_INHERITED(InterceptedHttpChannel, HttpBaseChannel,
+ nsIInterceptedChannel, nsICacheInfoChannel,
+ nsIAsyncVerifyRedirectCallback, nsIRequestObserver,
+ nsIStreamListener, nsIThreadRetargetableRequest,
+ nsIThreadRetargetableStreamListener)
+
+InterceptedHttpChannel::InterceptedHttpChannel(
+ PRTime aCreationTime, const TimeStamp& aCreationTimestamp,
+ const TimeStamp& aAsyncOpenTimestamp)
+ : HttpAsyncAborter<InterceptedHttpChannel>(this),
+ mProgress(0),
+ mProgressReported(0),
+ mSynthesizedStreamLength(-1),
+ mResumeStartPos(0),
+ mSynthesizedOrReset(Invalid),
+ 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.
+ mChannelCreationTime = aCreationTime;
+ mChannelCreationTimestamp = aCreationTimestamp;
+ 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) {
+ 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() {
+ // 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;
+
+ // 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);
+ }
+ });
+
+ 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();
+ 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);
+ }
+
+ 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);
+
+ newChannel->SetLoadInfo(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() {
+ 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(TaskCategory::Other, 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::Cancel(nsresult aStatus) {
+ // 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;
+ }
+ mCanceled = true;
+
+ 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(nsISupports** aSecurityInfo) {
+ nsCOMPtr<nsISupports> ref(mSecurityInfo);
+ ref.forget(aSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::AsyncOpen(nsIStreamListener* 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) {
+ // 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::SetupFallbackChannel(const char* aFallbackKey) {
+ // AppCache should not be used with service worker intercepted channels.
+ // This should never be called.
+ 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 = aClassFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::ClearClassFlags(uint32_t aClassFlags) {
+ mClassOfService &= ~aClassFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::AddClassFlags(uint32_t aClassFlags) {
+ mClassOfService |= aClassFlags;
+ 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);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::ResetInterception(void) {
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(mURI, flags);
+ nsresult rv =
+ NS_NewChannelInternal(getter_AddRefs(newChannel), mURI, redirectLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ mLoadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupReplacementChannel(mURI, newChannel, true, flags);
+ 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);
+ }
+
+ 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);
+
+ mSynthesizedResponseHead->ParseStatusLine(statusLine);
+ 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;
+ }
+
+ // TODO: Remove this API after interception moves to the parent process in
+ // e10s mode.
+
+ 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::GetLaunchServiceWorkerStart(
+ mozilla::TimeStamp* aTimeStamp) {
+ return HttpBaseChannel::GetLaunchServiceWorkerStart(aTimeStamp);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetLaunchServiceWorkerStart(
+ mozilla::TimeStamp aTimeStamp) {
+ return HttpBaseChannel::SetLaunchServiceWorkerStart(aTimeStamp);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetLaunchServiceWorkerEnd(
+ mozilla::TimeStamp* aTimeStamp) {
+ return HttpBaseChannel::GetLaunchServiceWorkerEnd(aTimeStamp);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetLaunchServiceWorkerEnd(
+ mozilla::TimeStamp aTimeStamp) {
+ return HttpBaseChannel::SetLaunchServiceWorkerEnd(aTimeStamp);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetDispatchFetchEventStart(
+ mozilla::TimeStamp aTimeStamp) {
+ return HttpBaseChannel::SetDispatchFetchEventStart(aTimeStamp);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetDispatchFetchEventEnd(
+ mozilla::TimeStamp aTimeStamp) {
+ return HttpBaseChannel::SetDispatchFetchEventEnd(aTimeStamp);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetHandleFetchEventStart(
+ mozilla::TimeStamp aTimeStamp) {
+ return HttpBaseChannel::SetHandleFetchEventStart(aTimeStamp);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetHandleFetchEventEnd(mozilla::TimeStamp aTimeStamp) {
+ return HttpBaseChannel::SetHandleFetchEventEnd(aTimeStamp);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetFinishResponseStart(mozilla::TimeStamp aTimeStamp) {
+ mFinishResponseStart = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetFinishSynthesizedResponseEnd(
+ mozilla::TimeStamp aTimeStamp) {
+ MOZ_ASSERT(mSynthesizedOrReset == Invalid);
+ mSynthesizedOrReset = Synthesized;
+ mFinishResponseEnd = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetChannelResetEnd(mozilla::TimeStamp aTimeStamp) {
+ MOZ_ASSERT(mSynthesizedOrReset == Invalid);
+ mSynthesizedOrReset = Reset;
+ mFinishResponseEnd = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SaveTimeStamps(void) {
+ // If we were not able to start the fetch event for some reason (like
+ // corrupted scripts), then just do nothing here.
+ if (mHandleFetchEventStart.IsNull()) {
+ return NS_OK;
+ }
+
+ bool isNonSubresourceRequest = nsContentUtils::IsNonSubresourceRequest(this);
+ nsCString navigationOrSubresource =
+ isNonSubresourceRequest ? "navigation"_ns : "subresource"_ns;
+
+ nsAutoCString subresourceKey(""_ns);
+ GetSubresourceTimeStampKey(this, subresourceKey);
+
+ // We may have null timestamps if the fetch dispatch runnable was cancelled
+ // and we defaulted to resuming the request.
+ if (!mFinishResponseStart.IsNull() && !mFinishResponseEnd.IsNull()) {
+ Telemetry::HistogramID id =
+ (mSynthesizedOrReset == Synthesized)
+ ? Telemetry::
+ SERVICE_WORKER_FETCH_EVENT_FINISH_SYNTHESIZED_RESPONSE_MS
+ : Telemetry::SERVICE_WORKER_FETCH_EVENT_CHANNEL_RESET_MS;
+ Telemetry::Accumulate(
+ id, navigationOrSubresource,
+ static_cast<uint32_t>(
+ (mFinishResponseEnd - mFinishResponseStart).ToMilliseconds()));
+ if (!isNonSubresourceRequest && !subresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(
+ id, subresourceKey,
+ static_cast<uint32_t>(
+ (mFinishResponseEnd - mFinishResponseStart).ToMilliseconds()));
+ }
+ }
+
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS,
+ navigationOrSubresource,
+ static_cast<uint32_t>((mHandleFetchEventStart - mDispatchFetchEventStart)
+ .ToMilliseconds()));
+
+ if (!isNonSubresourceRequest && !subresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS,
+ subresourceKey,
+ static_cast<uint32_t>((mHandleFetchEventStart -
+ mDispatchFetchEventStart)
+ .ToMilliseconds()));
+ }
+
+ if (!mFinishResponseEnd.IsNull()) {
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS,
+ navigationOrSubresource,
+ static_cast<uint32_t>(
+ (mFinishResponseEnd - mDispatchFetchEventStart).ToMilliseconds()));
+ if (!isNonSubresourceRequest && !subresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS,
+ subresourceKey,
+ static_cast<uint32_t>((mFinishResponseEnd - mDispatchFetchEventStart)
+ .ToMilliseconds()));
+ }
+ }
+
+ 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(NS_SUCCEEDED(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) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mProgressSink) {
+ GetCallback(mProgressSink);
+ }
+
+ 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) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ MaybeCallBodyCallback();
+
+ // 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();
+
+ 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::RetargetDeliveryTo(nsIEventTarget* 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(nsIEventTarget** 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(int32_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::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,
+ bool 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::GetAltDataInputStream(
+ const nsACString& aType, nsIInputStreamReceiver* aReceiver) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetAltDataInputStream(aType, aReceiver);
+ }
+ 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;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/InterceptedHttpChannel.h b/netwerk/protocol/http/InterceptedHttpChannel.h
new file mode 100644
index 0000000000..9ceadb9a42
--- /dev/null
+++ b/netwerk/protocol/http/InterceptedHttpChannel.h
@@ -0,0 +1,188 @@
+/* -*- 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 {
+namespace 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 nsIStreamListener,
+ 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 mFinishResponseStart;
+ TimeStamp mFinishResponseEnd;
+ Atomic<int64_t> mProgress;
+ int64_t mProgressReported;
+ int64_t mSynthesizedStreamLength;
+ uint64_t mResumeStartPos;
+ nsCString mResumeEntityId;
+ nsString mStatusHost;
+ enum { Invalid = 0, Synthesized, Reset } mSynthesizedOrReset;
+ Atomic<bool> mCallingStatusAndProgress;
+
+ 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();
+
+ 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
+ Cancel(nsresult aStatus) override;
+
+ NS_IMETHOD
+ Suspend(void) override;
+
+ NS_IMETHOD
+ Resume(void) override;
+
+ NS_IMETHOD
+ GetSecurityInfo(nsISupports** aSecurityInfo) override;
+
+ NS_IMETHOD
+ AsyncOpen(nsIStreamListener* aListener) override;
+
+ NS_IMETHOD
+ LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory) override;
+
+ NS_IMETHOD
+ LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) override;
+
+ NS_IMETHOD
+ SetupFallbackChannel(const char* aFallbackKey) 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
+ ResumeAt(uint64_t startPos, const nsACString& entityID) override;
+
+ void DoNotifyListenerCleanup() override;
+
+ void DoAsyncAbort(nsresult aStatus) override;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_InterceptedHttpChannel_h
diff --git a/netwerk/protocol/http/NullHttpChannel.cpp b/netwerk/protocol/http/NullHttpChannel.cpp
new file mode 100644
index 0000000000..f818bd8117
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpChannel.cpp
@@ -0,0 +1,867 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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()
+ : mAllRedirectsSameOrigin(false), mAllRedirectsPassTimingAllowCheck(false) {
+ 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::GetTopLevelOuterContentWindowId(uint64_t* aWindowId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetTopLevelOuterContentWindowId(uint64_t aWindowId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetFlashPluginState(
+ nsIHttpChannel::FlashPluginState* aResult) {
+ 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::GetAllowPipelining(bool* aAllowPipelining) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetAllowPipelining(bool aAllowPipelining) {
+ 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;
+}
+
+//-----------------------------------------------------------------------------
+// NullHttpChannel::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+NullHttpChannel::GetOriginalURI(nsIURI** aOriginalURI) {
+ NS_IF_ADDREF(*aOriginalURI = mOriginalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::SetOriginalURI(nsIURI* aOriginalURI) {
+ mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NullHttpChannel::GetURI(nsIURI** aURI) {
+ NS_IF_ADDREF(*aURI = mURI);
+ 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(nsISupports** 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::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) {
+ if (!mResourcePrincipal || !aOrigin) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ bool sameOrigin = false;
+ nsresult rv = mResourcePrincipal->Equals(aOrigin, &sameOrigin);
+ if (NS_SUCCEEDED(rv) && sameOrigin) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ if (mTimingAllowOriginHeader == "*") {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ nsAutoCString origin;
+ aOrigin->GetAsciiOrigin(origin);
+
+ if (mTimingAllowOriginHeader == origin) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ *_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::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& aCategory) {
+ 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;
+}
+
+#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)
+
+#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..5625796a31
--- /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;
+ bool mAllRedirectsPassTimingAllowCheck;
+};
+
+} // 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..8871fd9912
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpTransaction.cpp
@@ -0,0 +1,220 @@
+/* -*- 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"
+#include "TCPFastOpenLayer.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),
+ mFastOpenStatus(TFO_NOT_TRIED),
+ 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;
+ }
+ // After a socket is connected we know for sure whether data has been
+ // sent on SYN packet and if not we should update TLS start timing.
+ if ((mFastOpenStatus != TFO_DATA_SENT) &&
+ !mTimings.secureConnectionStart.IsNull()) {
+ mTimings.secureConnectionStart = 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..c040e4671b
--- /dev/null
+++ b/netwerk/protocol/http/NullHttpTransaction.h
@@ -0,0 +1,99 @@
+/* -*- 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 TopLevelOuterContentWindowId() override { return 0; }
+
+ TimingStruct Timings() { return mTimings; }
+
+ mozilla::TimeStamp GetTcpConnectEnd() { return mTimings.tcpConnectEnd; }
+ mozilla::TimeStamp GetSecureConnectionStart() {
+ return mTimings.secureConnectionStart;
+ }
+
+ void SetFastOpenStatus(uint8_t aStatus) override {
+ mFastOpenStatus = aStatus;
+ }
+
+ protected:
+ virtual ~NullHttpTransaction();
+
+ private:
+ nsresult mStatus;
+
+ protected:
+ uint32_t mCaps;
+ nsHttpRequestHead* mRequestHead;
+
+ private:
+ bool mIsDone;
+ bool mClaimed;
+ TimingStruct mTimings;
+ uint8_t mFastOpenStatus;
+
+ 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/PAltDataOutputStream.ipdl b/netwerk/protocol/http/PAltDataOutputStream.ipdl
new file mode 100644
index 0000000000..f8234a8abe
--- /dev/null
+++ b/netwerk/protocol/http/PAltDataOutputStream.ipdl
@@ -0,0 +1,41 @@
+/* -*- 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 {
+
+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..ea96c5b5b1
--- /dev/null
+++ b/netwerk/protocol/http/PAltService.ipdl
@@ -0,0 +1,41 @@
+/* -*- 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 {
+
+refcounted protocol PAltService
+{
+ manager PSocketProcess;
+
+parent:
+ async ClearHostMapping(nsCString host, int32_t port,
+ OriginAttributes originAttributes,
+ nsCString topWindowOrigin);
+
+ async ProcessHeader(nsCString buf,
+ nsCString originScheme,
+ nsCString originHost,
+ int32_t originPort,
+ nsCString username,
+ nsCString topWindowOrigin,
+ bool privateBrowsing,
+ bool isolated,
+ 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..1b8a81db30
--- /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 {
+
+refcounted 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..31bacc8435
--- /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 protocol PBackground;
+include HttpChannelParams;
+include NeckoChannelParams;
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+async refcounted protocol PBackgroundDataBridge
+{
+ manager PBackground;
+
+child:
+ async OnTransportAndData(uint64_t offset,
+ uint32_t count,
+ nsDependentCSubstring data);
+
+ async OnStopRequest(nsresult aStatus,
+ ResourceTimingStructArgs timing,
+ TimeStamp lastActiveTabOptimization,
+ nsHttpHeaderArray responseTrailers);
+
+both:
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/PClassifierDummyChannel.ipdl b/netwerk/protocol/http/PClassifierDummyChannel.ipdl
new file mode 100644
index 0000000000..6530b1465c
--- /dev/null
+++ b/netwerk/protocol/http/PClassifierDummyChannel.ipdl
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 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 {
+
+// This protocol provides a mechanism for the "child intercept" mode of
+// ServiceWorker operation to work correctly with Classified channels.
+// ServiceWorkers should not be allowed for third-party iframes which are
+// annotated as tracking origins.
+//
+// In child intercept mode, the decision to intercept a channel is made in the
+// child process without consulting the parent process. The decision is based
+// on whether there is a ServiceWorker with a scope covering the URL in question
+// and whether storage is allowed for the origin/URL. When the
+// "network.cookie.cookieBehavior" preference is set to BEHAVIOR_REJECT_TRACKER,
+// annotated channels are denied storage which means that the ServiceWorker
+// should not intercept the channel. However, the decision for tracking
+// protection to annotate a channel only occurs in the parent process. The
+// dummy channel is a hack to allow the intercept decision process to ask the
+// parent process if the channel should be annotated. Because this round-trip
+// to the parent has overhead, the dummy channel is only created 1) if the
+// ServiceWorker initially determines that the channel should be intercepted and
+// 2) it's a navigation request.
+//
+// This hack can be removed once Bug 1231208's new "parent intercept" mechanism
+// fully lands, the pref is enabled by default it stays enabled for long enough
+// to be confident we will never need/want to turn it off. Then as part of bug
+// 1496997 we can remove this implementation. Bug 1498259 covers removing this
+// hack in particular.
+protocol PClassifierDummyChannel
+{
+ manager PNecko;
+
+child:
+ async __delete__(uint32_t aClassificationFlags);
+};
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/netwerk/protocol/http/PHttpBackgroundChannel.ipdl b/netwerk/protocol/http/PHttpBackgroundChannel.ipdl
new file mode 100644
index 0000000000..d52649506b
--- /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";
+
+using nsIHttpChannel::FlashPluginState from "nsIHttpChannel.h";
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+async refcounted protocol PHttpBackgroundChannel
+{
+ manager PBackground;
+
+child:
+ async OnStartRequest(nsHttpResponseHead responseHead,
+ bool useResponseHead,
+ nsHttpHeaderArray requestHeaders,
+ HttpChannelOnStartRequestArgs args);
+
+ // 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,
+ nsDependentCSubstring data,
+ bool dataFromSocketProcess);
+
+
+ async OnStopRequest(nsresult channelStatus,
+ ResourceTimingStructArgs timing,
+ TimeStamp lastActiveTabOptimization,
+ nsHttpHeaderArray responseTrailers,
+ ConsoleReportCollected[] consoleReport,
+ bool fromSocketProcess);
+
+ 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 that the current channel's document is not allowed to load
+ // flash content.
+ async NotifyFlashPluginStateChanged(FlashPluginState aState);
+
+ // 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 __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..7eab2f7562
--- /dev/null
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -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 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 URIParams;
+include PBackgroundSharedTypes;
+include NeckoChannelParams;
+include IPCServiceWorkerDescriptor;
+include IPCStream;
+include HttpChannelParams;
+
+include "mozilla/dom/ReferrerInfoUtils.h";
+include "mozilla/net/NeckoMessageUtils.h";
+
+using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+refcounted protocol PHttpChannel
+{
+ manager PNecko;
+
+parent:
+ // Note: channels are opened during construction, so no open method here:
+ // see PNecko.ipdl
+
+ async SetClassOfService(uint32_t cos);
+
+ async Suspend();
+ async Resume();
+
+ async Cancel(nsresult status, uint32_t requestBlockingReason);
+
+ // Reports approval/veto of redirect by child process redirect observers
+ async Redirect2Verify(nsresult result, RequestHeaderTuples changedHeaders,
+ uint32_t sourceRequestBlockingReason,
+ ChildLoadInfoForwarderArgs? targetLoadInfoForwarder,
+ uint32_t loadFlags, nsIReferrerInfo referrerInfo,
+ URIParams? apiRedirectTo,
+ CorsPreflightArgs? corsPreflightArgs,
+ bool chooseAppcache);
+
+ // 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);
+
+ // This might have to be sync. If this fails we must fail the document load
+ // to avoid endless loop.
+ //
+ // Explanation: the document loaded was loaded from the offline cache. But
+ // the cache group id (the manifest URL) of the cache group it was loaded
+ // from is different then the manifest the document refers to in the html
+ // tag. If we detect this during the cache selection algorithm, we must not
+ // load this document from the offline cache group it was just loaded from.
+ // Marking the cache entry as foreign in its cache group will prevent
+ // the document to load from the bad offline cache group. After it is marked,
+ // we reload the document to take the effect. If we fail to mark the entry
+ // as foreign, we will end up in the same situation and reload again and
+ // again, indefinitely.
+ async MarkOfflineCacheEntryAsForeign();
+
+ // 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(URIParams uri,
+ PrincipalInfo requestingPrincipal,
+ OriginAttributes originAttributes);
+
+ // 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();
+
+ // Called to get the input stream when altData is available.
+ async OpenAltDataCacheInputStream(nsCString aType);
+
+ // 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,
+ URIParams newOriginalUri,
+ uint32_t newLoadFlags,
+ uint32_t redirectFlags,
+ ParentLoadInfoForwarderArgs loadInfoForwarder,
+ nsHttpResponseHead responseHead,
+ nsCString securityInfoSerialization,
+ uint64_t channelId,
+ NetAddr oldPeerAddr,
+ ResourceTimingStructArgs timing);
+
+ // Called if redirect successful so that child can complete setup.
+ async Redirect3Complete();
+
+ // 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();
+
+ // Tell the child to issue a deprecation warning.
+ async IssueDeprecationWarning(uint32_t warning, bool asError);
+
+ // 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);
+
+ async LogMimeTypeMismatch(nsCString messageName,
+ bool warning,
+ nsString url,
+ nsString contentType);
+
+ async OriginalCacheInputStreamAvailable(IPCStream? stream);
+
+ async AltDataCacheInputStreamAvailable(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..ef7582dfe5
--- /dev/null
+++ b/netwerk/protocol/http/PHttpChannelParams.h
@@ -0,0 +1,280 @@
+/* -*- 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"
+#include "mozilla/Tuple.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(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mHeader);
+ WriteParam(aMsg, aParam.mValue);
+ WriteParam(aMsg, aParam.mMerge);
+ WriteParam(aMsg, aParam.mEmpty);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ if (!ReadParam(aMsg, aIter, &aResult->mHeader) ||
+ !ReadParam(aMsg, aIter, &aResult->mValue) ||
+ !ReadParam(aMsg, aIter, &aResult->mMerge) ||
+ !ReadParam(aMsg, aIter, &aResult->mEmpty))
+ return false;
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::nsHttpAtom> {
+ typedef mozilla::net::nsHttpAtom paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ // aParam.get() cannot be null.
+ MOZ_ASSERT(aParam.get(), "null nsHTTPAtom value");
+ nsAutoCString value(aParam.get());
+ WriteParam(aMsg, value);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ nsAutoCString value;
+ if (!ReadParam(aMsg, aIter, &value)) return false;
+
+ *aResult = mozilla::net::nsHttp::ResolveAtom(value.get());
+ 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(Message* aMsg, const paramType& aParam) {
+ if (aParam.headerNameOriginal.IsEmpty()) {
+ WriteParam(aMsg, aParam.header);
+ } else {
+ WriteParam(aMsg, aParam.headerNameOriginal);
+ }
+ WriteParam(aMsg, aParam.value);
+ switch (aParam.variety) {
+ case mozilla::net::nsHttpHeaderArray::eVarietyUnknown:
+ WriteParam(aMsg, (uint8_t)0);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyRequestOverride:
+ WriteParam(aMsg, (uint8_t)1);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyRequestDefault:
+ WriteParam(aMsg, (uint8_t)2);
+ break;
+ case mozilla::net::nsHttpHeaderArray::
+ eVarietyResponseNetOriginalAndResponse:
+ WriteParam(aMsg, (uint8_t)3);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyResponseNetOriginal:
+ WriteParam(aMsg, (uint8_t)4);
+ break;
+ case mozilla::net::nsHttpHeaderArray::eVarietyResponse:
+ WriteParam(aMsg, (uint8_t)5);
+ }
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ uint8_t variety;
+ nsAutoCString header;
+ if (!ReadParam(aMsg, aIter, &header) ||
+ !ReadParam(aMsg, aIter, &aResult->value) ||
+ !ReadParam(aMsg, aIter, &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::
+ eVarietyResponseNetOriginalAndResponse;
+ break;
+ case 4:
+ aResult->variety =
+ mozilla::net::nsHttpHeaderArray::eVarietyResponseNetOriginal;
+ break;
+ case 5:
+ 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(Message* aMsg, const paramType& aParam) {
+ paramType& p = const_cast<paramType&>(aParam);
+
+ WriteParam(aMsg, p.mHeaders);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ if (!ReadParam(aMsg, aIter, &aResult->mHeaders)) return false;
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::net::nsHttpRequestHead> {
+ typedef mozilla::net::nsHttpRequestHead paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mHeaders);
+ WriteParam(aMsg, aParam.mMethod);
+ WriteParam(aMsg, static_cast<uint32_t>(aParam.mVersion));
+ WriteParam(aMsg, aParam.mRequestURI);
+ WriteParam(aMsg, aParam.mPath);
+ WriteParam(aMsg, aParam.mOrigin);
+ WriteParam(aMsg, static_cast<uint8_t>(aParam.mParsedMethod));
+ WriteParam(aMsg, aParam.mHTTPS);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ uint32_t version;
+ uint8_t method;
+ if (!ReadParam(aMsg, aIter, &aResult->mHeaders) ||
+ !ReadParam(aMsg, aIter, &aResult->mMethod) ||
+ !ReadParam(aMsg, aIter, &version) ||
+ !ReadParam(aMsg, aIter, &aResult->mRequestURI) ||
+ !ReadParam(aMsg, aIter, &aResult->mPath) ||
+ !ReadParam(aMsg, aIter, &aResult->mOrigin) ||
+ !ReadParam(aMsg, aIter, &method) ||
+ !ReadParam(aMsg, aIter, &aResult->mHTTPS)) {
+ return false;
+ }
+
+ aResult->mVersion = static_cast<mozilla::net::HttpVersion>(version);
+ aResult->mParsedMethod =
+ static_cast<mozilla::net::nsHttpRequestHead::ParsedMethodType>(method);
+ 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(Message* aMsg, const paramType& aParam) {
+ WriteParam(aMsg, aParam.mHeaders);
+ WriteParam(aMsg, static_cast<uint32_t>(aParam.mVersion));
+ WriteParam(aMsg, aParam.mStatus);
+ WriteParam(aMsg, aParam.mStatusText);
+ WriteParam(aMsg, aParam.mContentLength);
+ WriteParam(aMsg, aParam.mContentType);
+ WriteParam(aMsg, aParam.mContentCharset);
+ WriteParam(aMsg, aParam.mHasCacheControl);
+ WriteParam(aMsg, aParam.mCacheControlPublic);
+ WriteParam(aMsg, aParam.mCacheControlPrivate);
+ WriteParam(aMsg, aParam.mCacheControlNoStore);
+ WriteParam(aMsg, aParam.mCacheControlNoCache);
+ WriteParam(aMsg, aParam.mCacheControlImmutable);
+ WriteParam(aMsg, aParam.mCacheControlStaleWhileRevalidateSet);
+ WriteParam(aMsg, aParam.mCacheControlStaleWhileRevalidate);
+ WriteParam(aMsg, aParam.mCacheControlMaxAgeSet);
+ WriteParam(aMsg, aParam.mCacheControlMaxAge);
+ WriteParam(aMsg, aParam.mPragmaNoCache);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ uint32_t version;
+ if (!ReadParam(aMsg, aIter, &aResult->mHeaders) ||
+ !ReadParam(aMsg, aIter, &version) ||
+ !ReadParam(aMsg, aIter, &aResult->mStatus) ||
+ !ReadParam(aMsg, aIter, &aResult->mStatusText) ||
+ !ReadParam(aMsg, aIter, &aResult->mContentLength) ||
+ !ReadParam(aMsg, aIter, &aResult->mContentType) ||
+ !ReadParam(aMsg, aIter, &aResult->mContentCharset) ||
+ !ReadParam(aMsg, aIter, &aResult->mHasCacheControl) ||
+ !ReadParam(aMsg, aIter, &aResult->mCacheControlPublic) ||
+ !ReadParam(aMsg, aIter, &aResult->mCacheControlPrivate) ||
+ !ReadParam(aMsg, aIter, &aResult->mCacheControlNoStore) ||
+ !ReadParam(aMsg, aIter, &aResult->mCacheControlNoCache) ||
+ !ReadParam(aMsg, aIter, &aResult->mCacheControlImmutable) ||
+ !ReadParam(aMsg, aIter,
+ &aResult->mCacheControlStaleWhileRevalidateSet) ||
+ !ReadParam(aMsg, aIter, &aResult->mCacheControlStaleWhileRevalidate) ||
+ !ReadParam(aMsg, aIter, &aResult->mCacheControlMaxAgeSet) ||
+ !ReadParam(aMsg, aIter, &aResult->mCacheControlMaxAge) ||
+ !ReadParam(aMsg, aIter, &aResult->mPragmaNoCache))
+ return false;
+
+ aResult->mVersion = static_cast<mozilla::net::HttpVersion>(version);
+ 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..535ab34c68
--- /dev/null
+++ b/netwerk/protocol/http/PHttpConnectionMgr.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 PAltSvcTransaction;
+include protocol PSocketProcess;
+include protocol PHttpTransaction;
+
+include NeckoChannelParams;
+
+namespace mozilla {
+namespace net {
+
+async refcounted protocol PHttpConnectionMgr
+{
+ manager PSocketProcess;
+
+child:
+ async __delete__();
+
+ async DoShiftReloadConnectionCleanupWithConnInfo(HttpConnectionInfoCloneArgs aArgs);
+ async UpdateCurrentTopLevelOuterContentWindowId(uint64_t aWindowId);
+ 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,
+ uint32_t aClassOfService);
+ async CancelTransaction(PHttpTransaction aTrans, nsresult aReason);
+ async SpeculativeConnect(HttpConnectionInfoCloneArgs aConnInfo,
+ SpeculativeConnectionOverriderArgs? aOverriderArgs,
+ uint32_t aCaps, PAltSvcTransaction? aTrans,
+ bool aFetchHTTPSRR);
+};
+
+} // namespace net
+} // namespace mozilla \ No newline at end of file
diff --git a/netwerk/protocol/http/PHttpTransaction.ipdl b/netwerk/protocol/http/PHttpTransaction.ipdl
new file mode 100644
index 0000000000..0628dd6f93
--- /dev/null
+++ b/netwerk/protocol/http/PHttpTransaction.ipdl
@@ -0,0 +1,106 @@
+/* -*- 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 PFileDescriptorSet; // FIXME: bug #792908
+include protocol PInputChannelThrottleQueue;
+
+include IPCStream;
+include NeckoChannelParams;
+
+include "mozilla/net/NeckoMessageUtils.h";
+
+using class mozilla::net::nsHttpRequestHead from "nsHttpRequestHead.h";
+using class nsHttpHeaderArray from "nsHttpHeaderArray.h";
+using mozilla::net::NetAddr from "mozilla/net/DNS.h";
+
+namespace mozilla {
+namespace net {
+
+struct H2PushedStreamArg {
+ PHttpTransaction transWithPushedStream;
+ uint32_t pushedStreamId;
+};
+
+struct NetworkAddressArg {
+ NetAddr selfAddr;
+ NetAddr peerAddr;
+ bool resolvedByTRR;
+ bool echConfigUsed;
+};
+
+refcounted protocol PHttpTransaction
+{
+ manager PSocketProcess;
+
+parent:
+ async OnStartRequest(nsresult status,
+ nsHttpResponseHead? responseHead,
+ nsCString securityInfoSerialization,
+ bool proxyConnectFailed,
+ TimingStructArgs timings,
+ int32_t proxyConnectResponseCode,
+ uint8_t[] dataForSniffer,
+ nsCString? altSvcUsed,
+ bool dataToChildProcess,
+ bool restarted,
+ uint32_t HTTPSSVCReceivedStage,
+ bool supportsHttp3);
+ async OnTransportStatus(nsresult status,
+ int64_t progress,
+ int64_t progressMax,
+ NetworkAddressArg? networkAddressArg);
+ async OnDataAvailable(nsCString data,
+ uint64_t offset,
+ uint32_t count);
+ async OnStopRequest(nsresult status,
+ bool responseIsComplete,
+ int64_t transferSize,
+ TimingStructArgs timings,
+ nsHttpHeaderArray? responseTrailers,
+ TransactionObserverResult? transactionObserverResult,
+ TimeStamp lastActiveTabOptimization,
+ uint32_t caps,
+ HttpConnectionInfoCloneArgs connInfoArgs);
+ async OnInitFailed(nsresult status);
+ async OnH2PushStream(uint32_t pushedStreamId,
+ nsCString resourceUrl,
+ nsCString requestString);
+
+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,
+ uint32_t 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..1f04430745
--- /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 "nsDataHashtable.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:
+ nsDataHashtable<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..09a3432da0
--- /dev/null
+++ b/netwerk/protocol/http/ParentChannelListener.cpp
@@ -0,0 +1,282 @@
+/* -*- 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 "mozilla/Unused.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIPrompt.h"
+#include "nsISecureBrowserUI.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::Unused;
+using mozilla::dom::ServiceWorkerInterceptController;
+using mozilla::dom::ServiceWorkerParentInterceptEnabled;
+
+namespace mozilla {
+namespace net {
+
+ParentChannelListener::ParentChannelListener(
+ nsIStreamListener* aListener,
+ dom::CanonicalBrowsingContext* aBrowsingContext, bool aUsePrivateBrowsing)
+ : mNextListener(aListener), mBrowsingContext(aBrowsingContext) {
+ LOG(("ParentChannelListener::ParentChannelListener [this=%p, next=%p]", this,
+ aListener));
+
+ if (ServiceWorkerParentInterceptEnabled()) {
+ 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);
+}
+
+//-----------------------------------------------------------------------------
+// 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..1b33c0da77
--- /dev/null
+++ b/netwerk/protocol/http/ParentChannelListener.h
@@ -0,0 +1,92 @@
+/* -*- 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 \
+ } \
+ }
+
+// Note: nsIInterfaceRequestor must be the first base so that do_QueryObject()
+// works correctly on this object, as it's needed to compute a void* pointing to
+// the beginning of this object.
+
+class ParentChannelListener final : public nsIInterfaceRequestor,
+ public nsIStreamListener,
+ 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,
+ bool aUsePrivateBrowsing);
+
+ // 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..448d8757e4
--- /dev/null
+++ b/netwerk/protocol/http/PendingTransactionInfo.cpp
@@ -0,0 +1,135 @@
+/* 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 "HalfOpenSocket.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 (mHalfOpen) {
+ RefPtr<HalfOpenSocket> halfOpen = do_QueryReferent(mHalfOpen);
+ LOG(
+ ("PendingTransactionInfo::PendingTransactionInfo "
+ "[trans=%p halfOpen=%p]",
+ mTransaction.get(), halfOpen.get()));
+ if (halfOpen) {
+ halfOpen->Unclaim();
+ }
+ mHalfOpen = 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(), mHalfOpen.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 alreadyHalfOpenOrWaitingForTLS = false;
+ if (mHalfOpen) {
+ MOZ_ASSERT(!mActiveConn);
+ RefPtr<HalfOpenSocket> halfOpen = do_QueryReferent(mHalfOpen);
+ LOG(
+ ("PendingTransactionInfo::IsAlreadyClaimedInitializingConn "
+ "[trans=%p, halfOpen=%p]\n",
+ mTransaction.get(), halfOpen.get()));
+ if (halfOpen) {
+ alreadyHalfOpenOrWaitingForTLS = true;
+ } else {
+ // If we have not found the halfOpen socket, remove the pointer.
+ mHalfOpen = nullptr;
+ }
+ } else if (mActiveConn) {
+ MOZ_ASSERT(!mHalfOpen);
+ 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()))) {
+ alreadyHalfOpenOrWaitingForTLS = true;
+ } else {
+ // If we have not found the connection, remove the pointer.
+ mActiveConn = nullptr;
+ }
+ }
+
+ return alreadyHalfOpenOrWaitingForTLS;
+}
+
+void PendingTransactionInfo::AbandonHalfOpenAndForgetActiveConn() {
+ // Abandon all half-open sockets belonging to the given transaction.
+ RefPtr<HalfOpenSocket> half = do_QueryReferent(mHalfOpen);
+ if (half) {
+ half->Abandon();
+ }
+ mHalfOpen = nullptr;
+ mActiveConn = nullptr;
+}
+
+bool PendingTransactionInfo::TryClaimingHalfOpen(HalfOpenSocket* sock) {
+ if (sock->Claim()) {
+ mHalfOpen =
+ do_GetWeakReference(static_cast<nsISupportsWeakReference*>(sock));
+ return true;
+ }
+ return false;
+}
+
+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));
+ return true;
+ }
+ return false;
+}
+
+void PendingTransactionInfo::AddHalfOpen(HalfOpenSocket* sock) {
+ mHalfOpen = 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..73f3a16089
--- /dev/null
+++ b/netwerk/protocol/http/PendingTransactionInfo.h
@@ -0,0 +1,60 @@
+/* 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 "HalfOpenSocket.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 HalfOpen socket or
+ // a connection in TLS handshake phase.
+ bool IsAlreadyClaimedInitializingConn();
+
+ void AbandonHalfOpenAndForgetActiveConn();
+
+ // Try to claim a halfOpen socket. We can only claim it if it is not
+ // claimed yet.
+ bool TryClaimingHalfOpen(HalfOpenSocket* 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 AddHalfOpen(HalfOpenSocket* sock);
+
+ nsHttpTransaction* Transaction() const { return mTransaction; }
+
+ private:
+ RefPtr<nsHttpTransaction> mTransaction;
+ nsWeakPtr mHalfOpen;
+ 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..6fea59be03
--- /dev/null
+++ b/netwerk/protocol/http/PendingTransactionQueue.cpp
@@ -0,0 +1,290 @@
+/* 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->TopLevelOuterContentWindowId()
+ : 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, windowId=%" PRIu64 "\n",
+ info->Transaction(),
+ info->Transaction()->TopLevelOuterContentWindowId()));
+
+ uint64_t windowId = TabIdForQueuing(info->Transaction());
+ nsTArray<RefPtr<PendingTransactionInfo>>* infoArray;
+ if (!mPendingTransactionTable.Get(windowId, &infoArray)) {
+ infoArray = new nsTArray<RefPtr<PendingTransactionInfo>>();
+ mPendingTransactionTable.Put(windowId, infoArray);
+ }
+
+ 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 (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) {
+ if (windowId && it.Key() == windowId) {
+ continue;
+ }
+
+ uint32_t count = 0;
+ for (; count < it.UserData()->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, it.UserData()->ElementAt(count));
+ ++totalCount;
+ }
+ it.UserData()->RemoveElementsAt(0, count);
+
+ if (maxCount && totalCount == maxCount) {
+ if (it.UserData()->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 (auto it = mPendingTransactionTable.ConstIter(); !it.Done(); it.Next()) {
+ length += it.UserData()->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 (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) {
+ LOG(("] window id = %" PRIx64 " queue [", it.Key()));
+ for (const auto& info : *it.UserData()) {
+ LOG((" %p", info->Transaction()));
+ }
+ }
+ LOG(("]"));
+}
+
+void PendingTransactionQueue::Compact() {
+ mUrgentStartQ.Compact();
+ for (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) {
+ it.UserData()->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 (auto it = mPendingTransactionTable.Iter(); !it.Done(); it.Next()) {
+ for (const auto& pendingTransInfo : *it.UserData()) {
+ LOG(("PendingTransactionQueue::CancelAllTransactions %p\n",
+ pendingTransInfo->Transaction()));
+ pendingTransInfo->Transaction()->Close(reason);
+ }
+ it.UserData()->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..a9a142d2b5
--- /dev/null
+++ b/netwerk/protocol/http/QuicSocketControl.cpp
@@ -0,0 +1,138 @@
+/* -*- 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 "nsWeakReference.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "sslt.h"
+#include "ssl.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS_INHERITED(QuicSocketControl, TransportSecurityInfo,
+ nsISSLSocketControl, QuicSocketControl)
+
+QuicSocketControl::QuicSocketControl(uint32_t aProviderFlags)
+ : CommonSocketControl(aProviderFlags) {}
+
+void QuicSocketControl::SetCertVerificationResult(PRErrorCode errorCode) {
+ if (errorCode) {
+ mFailedVerification = true;
+ SetCanceled(errorCode);
+ }
+
+ if (OnSocketThread()) {
+ CallAuthenticated();
+ } else {
+ DebugOnly<nsresult> rv = gSocketTransportService->Dispatch(
+ NewRunnableMethod("QuicSocketControl::CallAuthenticated", this,
+ &QuicSocketControl::CallAuthenticated),
+ NS_DISPATCH_NORMAL);
+ }
+}
+
+NS_IMETHODIMP
+QuicSocketControl::GetSSLVersionOffered(int16_t* aSSLVersionOffered) {
+ *aSSLVersionOffered = nsISSLSocketControl::TLS_VERSION_1_3;
+ return NS_OK;
+}
+
+void QuicSocketControl::CallAuthenticated() {
+ RefPtr<Http3Session> http3Session = do_QueryReferent(mHttp3Session);
+ if (http3Session) {
+ http3Session->Authenticated(GetErrorCode());
+ }
+ mHttp3Session = nullptr;
+}
+
+void QuicSocketControl::SetAuthenticationCallback(Http3Session* aHttp3Session) {
+ mHttp3Session = do_GetWeakReference(
+ static_cast<nsISupportsWeakReference*>(aHttp3Session));
+}
+
+void QuicSocketControl::HandshakeCompleted() {
+ psm::RememberCertErrorsTable::GetInstance().LookupCertErrorBits(this);
+
+ uint32_t state = nsIWebProgressListener::STATE_IS_SECURE;
+
+ bool distrustImminent;
+ MutexAutoLock lock(mMutex);
+ nsresult rv =
+ IsCertificateDistrustImminent(mSucceededCertChain, distrustImminent);
+
+ if (NS_SUCCEEDED(rv) && distrustImminent) {
+ state |= nsIWebProgressListener::STATE_CERT_DISTRUST_IMMINENT;
+ }
+
+ // If we're here, the TLS handshake has succeeded. Thus if any of these
+ // booleans are true, the user has added an override for a certificate error.
+ if (mIsDomainMismatch || mIsUntrusted || mIsNotValidAtThisTime) {
+ state |= nsIWebProgressListener::STATE_CERT_USER_OVERRIDDEN;
+ }
+
+ SetSecurityState(state);
+ mHandshakeCompleted = true;
+}
+
+void QuicSocketControl::SetNegotiatedNPN(const nsACString& aValue) {
+ MutexAutoLock lock(mMutex);
+ mNegotiatedNPN = aValue;
+ mNPNCompleted = true;
+}
+
+void QuicSocketControl::SetInfo(uint16_t aCipherSuite,
+ uint16_t aProtocolVersion, uint16_t aKeaGroup,
+ uint16_t aSignatureScheme) {
+ SSLCipherSuiteInfo cipherInfo;
+ if (SSL_GetCipherSuiteInfo(aCipherSuite, &cipherInfo, sizeof cipherInfo) ==
+ SECSuccess) {
+ MutexAutoLock lock(mMutex);
+ mHaveCipherSuiteAndProtocol = true;
+ mCipherSuite = aCipherSuite;
+ mProtocolVersion = aProtocolVersion & 0xFF;
+ mKeaGroup = getKeaGroupName(aKeaGroup);
+ mSignatureSchemeName = getSignatureName(aSignatureScheme);
+ }
+}
+
+NS_IMETHODIMP QuicSocketControl::GetPeerId(nsACString& aResult) {
+ if (!mPeerId.IsEmpty()) {
+ aResult.Assign(mPeerId);
+ return NS_OK;
+ }
+
+ if (mProviderFlags &
+ nsISocketProvider::ANONYMOUS_CONNECT) { // See bug 466080
+ mPeerId.AppendLiteral("anon:");
+ }
+ if (mProviderFlags & nsISocketProvider::NO_PERMANENT_STORAGE) {
+ mPeerId.AppendLiteral("private:");
+ }
+ if (mProviderFlags & nsISocketProvider::BE_CONSERVATIVE) {
+ mPeerId.AppendLiteral("beConservative:");
+ }
+
+ mPeerId.Append(GetHostName());
+ mPeerId.Append(':');
+ mPeerId.AppendInt(GetPort());
+ nsAutoCString suffix;
+ GetOriginAttributes().CreateSuffix(suffix);
+ mPeerId.Append(suffix);
+
+ aResult.Assign(mPeerId);
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/QuicSocketControl.h b/netwerk/protocol/http/QuicSocketControl.h
new file mode 100644
index 0000000000..b1484eae01
--- /dev/null
+++ b/netwerk/protocol/http/QuicSocketControl.h
@@ -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/. */
+
+#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_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD GetSSLVersionOffered(int16_t* aSSLVersionOffered) override;
+
+ explicit QuicSocketControl(uint32_t providerFlags);
+
+ void SetNegotiatedNPN(const nsACString& aValue);
+ void SetInfo(uint16_t aCipherSuite, uint16_t aProtocolVersion,
+ uint16_t aKeaGroup, uint16_t aSignatureScheme);
+
+ void SetAuthenticationCallback(Http3Session* aHttp3Session);
+ void CallAuthenticated();
+
+ void HandshakeCompleted();
+ void SetCertVerificationResult(PRErrorCode errorCode) override;
+
+ NS_IMETHOD GetPeerId(nsACString& aResult) override;
+
+ private:
+ ~QuicSocketControl() = default;
+
+ // For Authentication done callback.
+ nsWeakPtr mHttp3Session;
+
+ nsCString mPeerId;
+};
+
+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..de7fbc6d30
--- /dev/null
+++ b/netwerk/protocol/http/SpeculativeTransaction.cpp
@@ -0,0 +1,84 @@
+/* -*- 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),
+ mTriedToWrite(false),
+ mCloseCallback(std::move(aCallback)) {}
+
+SpeculativeTransaction::~SpeculativeTransaction() {}
+
+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(GetCurrentEventTarget(),
+ 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) {
+ 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;
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/SpeculativeTransaction.h b/netwerk/protocol/http/SpeculativeTransaction.h
new file mode 100644
index 0000000000..98b6143eab
--- /dev/null
+++ b/netwerk/protocol/http/SpeculativeTransaction.h
@@ -0,0 +1,69 @@
+/* -*- 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;
+
+ protected:
+ virtual ~SpeculativeTransaction();
+
+ 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/TRRServiceChannel.cpp b/netwerk/protocol/http/TRRServiceChannel.cpp
new file mode 100644
index 0000000000..cecc632724
--- /dev/null
+++ b/netwerk/protocol/http/TRRServiceChannel.cpp
@@ -0,0 +1,1539 @@
+/* -*- 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"
+
+namespace mozilla {
+namespace 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_CONCRETE(TRRServiceChannel)
+NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
+
+TRRServiceChannel::TRRServiceChannel()
+ : HttpAsyncAborter<TRRServiceChannel>(this),
+ mTopWindowOriginComputed(false),
+ mPushedStreamId(0),
+ mProxyRequest(nullptr, "TRRServiceChannel::mProxyRequest"),
+ mCurrentEventTarget(GetCurrentEventTarget()) {
+ LOG(("TRRServiceChannel ctor [this=%p]\n", this));
+}
+
+TRRServiceChannel::~TRRServiceChannel() {
+ LOG(("TRRServiceChannel dtor [this=%p]\n", this));
+}
+
+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(nsISupports** securityInfo) {
+ NS_ENSURE_ARG_POINTER(securityInfo);
+ *securityInfo = mSecurityInfo;
+ NS_IF_ADDREF(*securityInfo);
+ 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::MaybeWaitForUploadStreamLength can only be used on main
+ // thread, so we can only return an error here.
+#ifdef NIGHTLY_BUILD
+ MOZ_ASSERT(!LoadPendingInputStreamLengthOperation());
+#endif
+ if (LoadPendingInputStreamLengthOperation()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!gHttpHandler->Active()) {
+ LOG((" after HTTP shutdown..."));
+ ReleaseListeners();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsAutoCString scheme;
+ mURI->GetScheme(scheme);
+ if (!scheme.LowerCaseEqualsLiteral("https")) {
+ return NS_ERROR_FAILURE;
+ }
+
+ 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 (!mProxyInfo &&
+ !(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;
+}
+
+const nsCString& TRRServiceChannel::GetTopWindowOrigin() {
+ if (mTopWindowOriginComputed) {
+ return mTopWindowOrigin;
+ }
+
+ nsresult rv = nsContentUtils::GetASCIIOrigin(mURI, mTopWindowOrigin);
+ NS_ENSURE_SUCCESS(rv, mTopWindowOrigin);
+
+ mTopWindowOriginComputed = true;
+ return mTopWindowOrigin;
+}
+
+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, GetTopWindowOrigin(), proxyInfo,
+ OriginAttributes(), isHttps);
+ // TODO: Bug 1622778 for using AltService in socket process.
+ StoreAllowAltSvc(XRE_IsParentProcess() && LoadAllowAltSvc());
+ bool http2Allowed = !gHttpHandler->IsHttp2Excluded(connInfo);
+ bool http3Allowed = !mUpgradeProtocolCallback && !mProxyInfo &&
+ !(mCaps & NS_HTTP_BE_CONSERVATIVE) &&
+ !LoadBeConservative();
+
+ 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, IsIsolated(),
+ GetTopWindowOrigin(), 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));
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
+ } 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 & nsIClassOfService::Leader) {
+ mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
+ }
+ if (mClassOfService & nsIClassOfService::Unblocked) {
+ mCaps |= NS_HTTP_LOAD_UNBLOCKED;
+ }
+ if (mClassOfService & 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->ConnMgr()->DoShiftReloadConnectionCleanupWithConnInfo(
+ mConnectionInfo);
+ if (NS_FAILED(rv)) {
+ LOG((
+ "TRRServiceChannel::BeginConnect "
+ "DoShiftReloadConnectionCleanupWithConnInfo failed: %08x [this=%p]",
+ static_cast<uint32_t>(rv), this));
+ }
+ }
+ }
+
+ 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_TRR_FLAGS_FROM_MODE(nsIRequest::GetTRRMode());
+
+ // Finalize ConnectionInfo flags before SpeculativeConnect
+ mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
+ mConnectionInfo->SetPrivate(mPrivateBrowsing);
+ mConnectionInfo->SetIsolated(IsIsolated());
+ 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);
+
+ 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=%u, prio=%d]\n", this,
+ mClassOfService, mPriority));
+
+ NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
+
+ nsresult rv;
+
+ if (!LoadAllowSpdy()) {
+ mCaps |= NS_HTTP_DISALLOW_SPDY;
+ }
+ if (!LoadAllowHttp3()) {
+ 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 (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
+ mCaps |= NS_HTTP_CALL_CONTENT_SNIFFER;
+ }
+
+ 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,
+ mTopLevelOuterContentWindowId, 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());
+ mDNSPrefetch->PrefetchHigh(mCaps & NS_HTTP_REFRESH_DNS);
+}
+
+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);
+ }
+
+ nsCString topWindowOrigin = GetTopWindowOrigin();
+ bool isIsolated = IsIsolated();
+ auto processHeaderTask = [altSvc, scheme, originHost, originPort,
+ userName(mUsername), topWindowOrigin,
+ privateBrowsing(mPrivateBrowsing), isIsolated,
+ callbacks, proxyInfo, caps(mCaps)]() {
+ if (XRE_IsSocketProcess()) {
+ AltServiceChild::ProcessHeader(
+ altSvc, scheme, originHost, originPort, userName, topWindowOrigin,
+ privateBrowsing, isIsolated, callbacks, proxyInfo,
+ caps & NS_HTTP_DISALLOW_SPDY, OriginAttributes());
+ return;
+ }
+
+ AltSvcMapping::ProcessHeader(
+ altSvc, scheme, originHost, originPort, userName, topWindowOrigin,
+ privateBrowsing, isIsolated, 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 ((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());
+ }
+
+ // Apply TRR specific settings.
+ return TRR::SetupTRRServiceChannelInternal(
+ httpChannel,
+ mRequestHead.ParsedMethod() == nsHttpRequestHead::kMethod_Get);
+}
+
+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) {
+ 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::SetupFallbackChannel(const char* aFallbackKey) {
+ 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=%u", this,
+ mClassOfService));
+
+ if (mTransaction) {
+ gHttpHandler->UpdateClassOfServiceOnTransaction(mTransaction,
+ mClassOfService);
+ }
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::SetClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService;
+ mClassOfService = inFlags;
+ if (previous != mClassOfService) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::AddClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService;
+ mClassOfService |= inFlags;
+ if (previous != mClassOfService) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TRRServiceChannel::ClearClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService;
+ mClassOfService &= ~inFlags;
+ if (previous != mClassOfService) {
+ 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 = mProxyInfo;
+ else
+ *result = mConnectionInfo->ProxyInfo();
+ NS_IF_ADDREF(*result);
+ 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 |
+ nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE)) {
+ 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 net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/TRRServiceChannel.h b/netwerk/protocol/http/TRRServiceChannel.h
new file mode 100644
index 0000000000..2e2cb08571
--- /dev/null
+++ b/netwerk/protocol/http/TRRServiceChannel.h
@@ -0,0 +1,169 @@
+/* -*- 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 {
+namespace 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 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(nsISupports** aSecurityInfo) override;
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+
+ NS_IMETHOD LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory) override;
+ NS_IMETHOD LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) override;
+ NS_IMETHOD SetupFallbackChannel(const char* aFallbackKey) 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;
+ // nsIResumableChannel
+ NS_IMETHOD ResumeAt(uint64_t startPos, const nsACString& entityID) override;
+
+ [[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);
+ const nsCString& GetTopWindowOrigin();
+ 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;
+
+ virtual bool SameOriginWithOriginalUri(nsIURI* aURI) override;
+ bool DispatchRelease();
+
+ // True only when we have computed the value of the top window origin.
+ bool mTopWindowOriginComputed;
+
+ nsCString mUsername;
+ // The origin of the top window, only valid when mTopWindowOriginComputed is
+ // true.
+ nsCString mTopWindowOrigin;
+
+ // Needed for accurate DNS timing
+ RefPtr<nsDNSPrefetch> mDNSPrefetch;
+
+ nsCOMPtr<nsIRequest> mTransactionPump;
+ RefPtr<HttpTransactionShell> mTransaction;
+ uint32_t mPushedStreamId;
+ 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 net
+} // namespace mozilla
+
+#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..14468a310e
--- /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;
+};
+
+struct ResourceTimingStruct : TimingStruct {
+ TimeStamp fetchStart;
+ TimeStamp redirectStart;
+ TimeStamp redirectEnd;
+ uint64_t transferSize;
+ uint64_t encodedBodySize;
+ nsCString protocolVersion;
+
+ // 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/TunnelUtils.cpp b/netwerk/protocol/http/TunnelUtils.cpp
new file mode 100644
index 0000000000..b62b1dd92c
--- /dev/null
+++ b/netwerk/protocol/http/TunnelUtils.cpp
@@ -0,0 +1,2172 @@
+/* -*- 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 "Http2Session.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "TCPFastOpen.h"
+#include "nsISocketProvider.h"
+#include "nsSocketProviderService.h"
+#include "nsISSLSocketControl.h"
+#include "nsISocketTransport.h"
+#include "nsISupportsPriority.h"
+#include "nsNetAddr.h"
+#include "prerror.h"
+#include "prio.h"
+#include "TunnelUtils.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsSocketTransport2.h"
+#include "nsSocketTransportService2.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Mutex.h"
+
+#if defined(FUZZING)
+# include "FuzzySecurityInfo.h"
+# include "mozilla/StaticPrefs_fuzzing.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+static PRDescIdentity sLayerIdentity;
+static PRIOMethods sLayerMethods;
+static PRIOMethods* sLayerMethodsPtr = nullptr;
+
+TLSFilterTransaction::TLSFilterTransaction(nsAHttpTransaction* aWrapped,
+ const char* aTLSHost,
+ int32_t aTLSPort,
+ nsAHttpSegmentReader* aReader,
+ nsAHttpSegmentWriter* aWriter)
+ : mTransaction(aWrapped),
+ mEncryptedTextUsed(0),
+ mEncryptedTextSize(0),
+ mSegmentReader(aReader),
+ mSegmentWriter(aWriter),
+ mFilterReadCode(NS_ERROR_NOT_INITIALIZED),
+ mFilterReadAmount(0),
+ mInOnReadSegment(false),
+ mForce(false),
+ mReadSegmentReturnValue(NS_OK),
+ mCloseReason(NS_ERROR_UNEXPECTED),
+ mNudgeCounter(0) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("TLSFilterTransaction ctor %p\n", this));
+
+ nsCOMPtr<nsISocketProvider> provider;
+ nsCOMPtr<nsISocketProviderService> spserv =
+ nsSocketProviderService::GetOrCreate();
+
+ if (spserv) {
+ spserv->GetSocketProvider("ssl", getter_AddRefs(provider));
+ }
+
+ // 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 (!sLayerMethodsPtr) {
+ // one time initialization
+ sLayerIdentity = PR_GetUniqueIdentity("TLSFilterTransaction Layer");
+ sLayerMethods = *PR_GetDefaultIOMethods();
+ sLayerMethods.getpeername = GetPeerName;
+ sLayerMethods.getsocketoption = GetSocketOption;
+ sLayerMethods.setsocketoption = SetSocketOption;
+ sLayerMethods.read = FilterRead;
+ sLayerMethods.write = FilterWrite;
+ sLayerMethods.send = FilterSend;
+ sLayerMethods.recv = FilterRecv;
+ sLayerMethods.close = FilterClose;
+ sLayerMethodsPtr = &sLayerMethods;
+ }
+
+ mFD = PR_CreateIOLayerStub(sLayerIdentity, &sLayerMethods);
+
+ bool addTLSLayer = true;
+#ifdef FUZZING
+ addTLSLayer = !StaticPrefs::fuzzing_necko_enabled();
+ if (!addTLSLayer) {
+ SOCKET_LOG(("Skipping TLS layer in TLSFilterTransaction for fuzzing.\n"));
+
+ mSecInfo = static_cast<nsISupports*>(
+ static_cast<nsISSLSocketControl*>(new FuzzySecurityInfo()));
+ }
+#endif
+
+ if (provider && mFD) {
+ mFD->secret = reinterpret_cast<PRFilePrivate*>(this);
+
+ if (addTLSLayer) {
+ provider->AddToSocket(PR_AF_INET, aTLSHost, aTLSPort, nullptr,
+ OriginAttributes(), 0, 0, mFD,
+ getter_AddRefs(mSecInfo));
+ }
+ }
+
+ if (mTransaction) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(mSecInfo));
+ if (secCtrl) {
+ secCtrl->SetNotificationCallbacks(callbacks);
+ }
+ }
+}
+
+TLSFilterTransaction::~TLSFilterTransaction() {
+ LOG(("TLSFilterTransaction dtor %p\n", this));
+
+ // Prevent call to OnReadSegment from FilterOutput, our mSegmentReader is now
+ // an invalid pointer.
+ mInOnReadSegment = true;
+
+ Cleanup();
+}
+
+void TLSFilterTransaction::Cleanup() {
+ LOG(("TLSFilterTransaction::Cleanup %p", this));
+
+ if (mTransaction) {
+ mTransaction->Close(NS_ERROR_ABORT);
+ mTransaction = nullptr;
+ }
+
+ if (mFD) {
+ PR_Close(mFD);
+ mFD = nullptr;
+ }
+ mSecInfo = nullptr;
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+void TLSFilterTransaction::Close(nsresult aReason) {
+ LOG(("TLSFilterTransaction::Close %p %" PRIx32, this,
+ static_cast<uint32_t>(aReason)));
+
+ if (!mTransaction) {
+ return;
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ mTransaction->Close(aReason);
+ mTransaction = nullptr;
+
+ if (gHttpHandler->Bug1563538()) {
+ if (NS_FAILED(aReason)) {
+ mCloseReason = aReason;
+ } else {
+ mCloseReason = NS_BASE_STREAM_CLOSED;
+ }
+ } else {
+ MOZ_ASSERT(NS_ERROR_UNEXPECTED == mCloseReason);
+ }
+}
+
+nsresult TLSFilterTransaction::OnReadSegment(const char* aData, uint32_t aCount,
+ uint32_t* outCountRead) {
+ LOG(("TLSFilterTransaction %p OnReadSegment %d (buffered %d)\n", this, aCount,
+ mEncryptedTextUsed));
+
+ mReadSegmentReturnValue = NS_OK;
+ MOZ_ASSERT(mSegmentReader);
+ if (!mSecInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ *outCountRead = 0;
+
+ // get rid of buffer first
+ if (mEncryptedTextUsed) {
+ rv = mSegmentReader->CommitToSegmentSize(mEncryptedTextUsed, mForce);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ return rv;
+ }
+
+ uint32_t amt;
+ rv = mSegmentReader->OnReadSegment(mEncryptedText.get(), mEncryptedTextUsed,
+ &amt);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mEncryptedTextUsed -= amt;
+ if (mEncryptedTextUsed) {
+ memmove(mEncryptedText.get(), &mEncryptedText[amt], mEncryptedTextUsed);
+ return NS_OK;
+ }
+ }
+
+ // encrypt for network write
+ // write aData down the SSL layer into the FilterWrite() method where it will
+ // be queued into mEncryptedText. We need to copy it like this in order to
+ // guarantee atomic writes
+
+ EnsureBuffer(mEncryptedText, aCount + 4096, 0, mEncryptedTextSize);
+
+ // Prevents call to OnReadSegment from inside FilterOutput, as we handle it
+ // here.
+ AutoRestore<bool> inOnReadSegment(mInOnReadSegment);
+ mInOnReadSegment = true;
+
+ while (aCount > 0) {
+ int32_t written = PR_Write(mFD, aData, aCount);
+ LOG(("TLSFilterTransaction %p OnReadSegment PRWrite(%d) = %d %d\n", this,
+ aCount, written, PR_GetError() == PR_WOULD_BLOCK_ERROR));
+
+ if (written < 1) {
+ if (*outCountRead) {
+ return NS_OK;
+ }
+ // mTransaction ReadSegments actually obscures this code, so
+ // keep it in a member var for this::ReadSegments to inspect. Similar
+ // to nsHttpConnection::mSocketOutCondition
+ PRErrorCode code = PR_GetError();
+ mReadSegmentReturnValue = ErrorAccordingToNSPR(code);
+
+ return mReadSegmentReturnValue;
+ }
+ aCount -= written;
+ aData += written;
+ *outCountRead += written;
+ mNudgeCounter = 0;
+ }
+
+ LOG(("TLSFilterTransaction %p OnReadSegment2 (buffered %d)\n", this,
+ mEncryptedTextUsed));
+
+ uint32_t amt = 0;
+ if (mEncryptedTextUsed) {
+ // If we are tunneled on spdy CommitToSegmentSize will prevent partial
+ // writes that could interfere with multiplexing. H1 is fine with
+ // partial writes.
+ rv = mSegmentReader->CommitToSegmentSize(mEncryptedTextUsed, mForce);
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ rv = mSegmentReader->OnReadSegment(mEncryptedText.get(),
+ mEncryptedTextUsed, &amt);
+ }
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ // return OK because all the data was consumed and stored in this buffer
+ // It is fine if the connection is null. We are likely a websocket and
+ // thus writing push is ensured by the caller.
+ if (Connection()) {
+ Connection()->TransactionHasDataToWrite(this);
+ }
+ return NS_OK;
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (amt == mEncryptedTextUsed) {
+ mEncryptedText = nullptr;
+ mEncryptedTextUsed = 0;
+ mEncryptedTextSize = 0;
+ } else {
+ memmove(mEncryptedText.get(), &mEncryptedText[amt],
+ mEncryptedTextUsed - amt);
+ mEncryptedTextUsed -= amt;
+ }
+ return NS_OK;
+}
+
+int32_t TLSFilterTransaction::FilterOutput(const char* aBuf, int32_t aAmount) {
+ EnsureBuffer(mEncryptedText, mEncryptedTextUsed + aAmount, mEncryptedTextUsed,
+ mEncryptedTextSize);
+ memcpy(&mEncryptedText[mEncryptedTextUsed], aBuf, aAmount);
+ mEncryptedTextUsed += aAmount;
+
+ LOG(("TLSFilterTransaction::FilterOutput %p %d buffered=%u mSegmentReader=%p",
+ this, aAmount, mEncryptedTextUsed, mSegmentReader));
+
+ if (!mInOnReadSegment) {
+ // When called externally, we must make sure any newly written data is
+ // actually sent to the higher level connection.
+ // This also covers the case when PR_Read() wrote a re-negotioation
+ // response.
+ uint32_t notUsed;
+ Unused << OnReadSegment("", 0, &notUsed);
+ }
+
+ return aAmount;
+}
+
+nsresult TLSFilterTransaction::CommitToSegmentSize(uint32_t size,
+ bool forceCommitment) {
+ if (!mSegmentReader) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // pad the commit by a little bit to leave room for encryption overhead
+ // this isn't foolproof and we may still have to buffer, but its a good start
+ mForce = forceCommitment;
+ return mSegmentReader->CommitToSegmentSize(size + 1024, forceCommitment);
+}
+
+nsresult TLSFilterTransaction::OnWriteSegment(char* aData, uint32_t aCount,
+ uint32_t* outCountRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mSegmentWriter);
+ LOG(("TLSFilterTransaction::OnWriteSegment %p max=%d\n", this, aCount));
+ if (!mSecInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // this will call through to FilterInput to get data from the higher
+ // level connection before removing the local TLS layer
+ mFilterReadCode = NS_OK;
+ mFilterReadAmount = 0;
+ int32_t bytesRead = PR_Read(mFD, aData, aCount);
+ if (bytesRead == -1) {
+ PRErrorCode code = PR_GetError();
+ if (code == PR_WOULD_BLOCK_ERROR) {
+ LOG(
+ ("TLSFilterTransaction::OnWriteSegment %p PR_Read would block, "
+ "actual read: %d\n",
+ this, mFilterReadAmount));
+
+ if (mFilterReadAmount == 0 && NS_SUCCEEDED(mFilterReadCode)) {
+ // No reading happened, but also no error occured, hence there is no
+ // condition to break the `again` loop, propagate WOULD_BLOCK through
+ // mFilterReadCode to break it and poll the socket again for reading.
+ mFilterReadCode = NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ // If reading from the socket succeeded (NS_SUCCEEDED(mFilterReadCode)),
+ // but the nss layer encountered an error remember the error.
+ if (NS_SUCCEEDED(mFilterReadCode)) {
+ mFilterReadCode = ErrorAccordingToNSPR(code);
+ LOG(("TLSFilterTransaction::OnWriteSegment %p nss error %" PRIx32 ".\n",
+ this, static_cast<uint32_t>(mFilterReadCode)));
+ }
+ return mFilterReadCode;
+ }
+ *outCountRead = bytesRead;
+
+ if (NS_SUCCEEDED(mFilterReadCode) && !bytesRead) {
+ LOG(
+ ("TLSFilterTransaction::OnWriteSegment %p "
+ "Second layer of TLS stripping results in STREAM_CLOSED\n",
+ this));
+ mFilterReadCode = NS_BASE_STREAM_CLOSED;
+ }
+
+ LOG(("TLSFilterTransaction::OnWriteSegment %p rv=%" PRIx32 " didread=%d "
+ "2 layers of ssl stripped to plaintext\n",
+ this, static_cast<uint32_t>(mFilterReadCode), bytesRead));
+ return mFilterReadCode;
+}
+
+int32_t TLSFilterTransaction::FilterInput(char* aBuf, int32_t aAmount) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(mSegmentWriter);
+ LOG(("TLSFilterTransaction::FilterInput max=%d\n", aAmount));
+
+ uint32_t outCountRead = 0;
+ mFilterReadCode =
+ mSegmentWriter->OnWriteSegment(aBuf, aAmount, &outCountRead);
+ if (NS_SUCCEEDED(mFilterReadCode) && outCountRead) {
+ LOG(("TLSFilterTransaction::FilterInput rv=%" PRIx32
+ " read=%d input from net "
+ "1 layer stripped, 1 still on\n",
+ static_cast<uint32_t>(mFilterReadCode), outCountRead));
+ if (mReadSegmentReturnValue == NS_BASE_STREAM_WOULD_BLOCK) {
+ mNudgeCounter = 0;
+ }
+
+ mFilterReadAmount += outCountRead;
+ }
+ if (mFilterReadCode == NS_BASE_STREAM_WOULD_BLOCK) {
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ return -1;
+ }
+ return outCountRead;
+}
+
+nsresult TLSFilterTransaction::ReadSegments(nsAHttpSegmentReader* aReader,
+ uint32_t aCount,
+ uint32_t* outCountRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("TLSFilterTransaction::ReadSegments %p max=%d\n", this, aCount));
+
+ if (!mTransaction) {
+ return mCloseReason;
+ }
+
+ mReadSegmentReturnValue = NS_OK;
+ mSegmentReader = aReader;
+ nsresult rv = mTransaction->ReadSegments(this, aCount, outCountRead);
+
+ // mSegmentReader is left assigned (not nullified) because we want to be able
+ // to call OnReadSegment directly, it expects mSegmentReader be non-null.
+
+ LOG(("TLSFilterTransaction %p called trans->ReadSegments rv=%" PRIx32 " %d\n",
+ this, static_cast<uint32_t>(rv), *outCountRead));
+ if (NS_SUCCEEDED(rv) &&
+ (mReadSegmentReturnValue == NS_BASE_STREAM_WOULD_BLOCK)) {
+ LOG(("TLSFilterTransaction %p read segment blocked found rv=%" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv)));
+ if (Connection()) {
+ Unused << Connection()->ForceSend();
+ }
+ }
+
+ return NS_SUCCEEDED(rv) ? mReadSegmentReturnValue : rv;
+}
+
+nsresult TLSFilterTransaction::WriteSegmentsAgain(nsAHttpSegmentWriter* aWriter,
+ uint32_t aCount,
+ uint32_t* outCountWritten,
+ bool* again) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("TLSFilterTransaction::WriteSegmentsAgain %p max=%d\n", this, aCount));
+
+ if (!mTransaction) {
+ return mCloseReason;
+ }
+
+ mSegmentWriter = aWriter;
+
+ nsresult rv =
+ mTransaction->WriteSegmentsAgain(this, aCount, outCountWritten, again);
+
+ if (NS_SUCCEEDED(rv) && !(*outCountWritten) && NS_FAILED(mFilterReadCode)) {
+ // nsPipe turns failures into silent OK.. undo that!
+ rv = mFilterReadCode;
+ if (Connection() && (mFilterReadCode == NS_BASE_STREAM_WOULD_BLOCK)) {
+ Unused << Connection()->ResumeRecv();
+ }
+ }
+ LOG(("TLSFilterTransaction %p called trans->WriteSegments rv=%" PRIx32
+ " %d\n",
+ this, static_cast<uint32_t>(rv), *outCountWritten));
+ return rv;
+}
+
+nsresult TLSFilterTransaction::WriteSegments(nsAHttpSegmentWriter* aWriter,
+ uint32_t aCount,
+ uint32_t* outCountWritten) {
+ bool again = false;
+ return WriteSegmentsAgain(aWriter, aCount, outCountWritten, &again);
+}
+
+nsresult TLSFilterTransaction::GetTransactionSecurityInfo(
+ nsISupports** outSecInfo) {
+ if (!mSecInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISupports> temp(mSecInfo);
+ temp.forget(outSecInfo);
+ return NS_OK;
+}
+
+nsresult TLSFilterTransaction::NudgeTunnel(NudgeTunnelCallback* aCallback) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("TLSFilterTransaction %p NudgeTunnel\n", this));
+ mNudgeCallback = nullptr;
+
+ if (!mSecInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISSLSocketControl> ssl(do_QueryInterface(mSecInfo));
+ nsresult rv = ssl ? ssl->DriveHandshake() : NS_ERROR_FAILURE;
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ // fatal handshake failure
+ LOG(("TLSFilterTransaction %p Fatal Handshake Failure: %d\n", this,
+ PR_GetError()));
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t notUsed;
+ Unused << OnReadSegment("", 0, &notUsed);
+
+ // The SSL Layer does some unusual things with PR_Poll that makes it a bad
+ // match for multiplexed SSL sessions. We work around this by manually polling
+ // for the moment during the brief handshake phase or otherwise blocked on
+ // write. Thankfully this is a pretty unusual state. NSPR doesn't help us here
+ // - asserting when polling without the NSPR IO layer on the bottom of the
+ // stack. As a follow-on we can do some NSPR and maybe libssl changes to make
+ // this more event driven, but this is acceptable for getting started.
+
+ uint32_t counter = mNudgeCounter++;
+ uint32_t delay;
+
+ if (!counter) {
+ delay = 0;
+ } else if (counter < 8) { // up to 48ms at 6
+ delay = 6;
+ } else if (counter < 34) { // up to 499 ms at 17ms
+ delay = 17;
+ } else { // after that at 51ms (3 old windows ticks)
+ delay = 51;
+ }
+
+ if (!mTimer) {
+ mTimer = NS_NewTimer();
+ }
+
+ mNudgeCallback = aCallback;
+ if (!mTimer || NS_FAILED(mTimer->InitWithCallback(this, delay,
+ nsITimer::TYPE_ONE_SHOT))) {
+ return StartTimerCallback();
+ }
+
+ LOG(("TLSFilterTransaction %p NudgeTunnel timer started\n", this));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSFilterTransaction::Notify(nsITimer* timer) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("TLSFilterTransaction %p NudgeTunnel notify\n", this));
+
+ if (timer != mTimer) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsresult rv = StartTimerCallback();
+ if (NS_FAILED(rv)) {
+ Close(rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TLSFilterTransaction::GetName(nsACString& aName) {
+ aName.AssignLiteral("TLSFilterTransaction");
+ return NS_OK;
+}
+
+nsresult TLSFilterTransaction::StartTimerCallback() {
+ LOG(("TLSFilterTransaction %p NudgeTunnel StartTimerCallback %p\n", this,
+ mNudgeCallback.get()));
+
+ if (mNudgeCallback) {
+ // This class can be called re-entrantly, so cleanup m* before ->on()
+ RefPtr<NudgeTunnelCallback> cb(mNudgeCallback);
+ mNudgeCallback = nullptr;
+ return cb->OnTunnelNudged(this);
+ }
+ return NS_OK;
+}
+
+bool TLSFilterTransaction::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;
+}
+
+PRStatus TLSFilterTransaction::GetPeerName(PRFileDesc* aFD, PRNetAddr* addr) {
+ NetAddr peeraddr;
+ TLSFilterTransaction* self =
+ reinterpret_cast<TLSFilterTransaction*>(aFD->secret);
+
+ if (!self->mTransaction ||
+ NS_FAILED(self->mTransaction->Connection()->Transport()->GetPeerAddr(
+ &peeraddr))) {
+ return PR_FAILURE;
+ }
+ NetAddrToPRNetAddr(&peeraddr, addr);
+ return PR_SUCCESS;
+}
+
+PRStatus TLSFilterTransaction::GetSocketOption(PRFileDesc* aFD,
+ PRSocketOptionData* aOpt) {
+ if (aOpt->option == PR_SockOpt_Nonblocking) {
+ aOpt->value.non_blocking = PR_TRUE;
+ return PR_SUCCESS;
+ }
+ return PR_FAILURE;
+}
+
+PRStatus TLSFilterTransaction::SetSocketOption(PRFileDesc* aFD,
+ const PRSocketOptionData* aOpt) {
+ return PR_FAILURE;
+}
+
+PRStatus TLSFilterTransaction::FilterClose(PRFileDesc* aFD) {
+ return PR_SUCCESS;
+}
+
+int32_t TLSFilterTransaction::FilterWrite(PRFileDesc* aFD, const void* aBuf,
+ int32_t aAmount) {
+ TLSFilterTransaction* self =
+ reinterpret_cast<TLSFilterTransaction*>(aFD->secret);
+ return self->FilterOutput(static_cast<const char*>(aBuf), aAmount);
+}
+
+int32_t TLSFilterTransaction::FilterSend(PRFileDesc* aFD, const void* aBuf,
+ int32_t aAmount, int, PRIntervalTime) {
+ return FilterWrite(aFD, aBuf, aAmount);
+}
+
+int32_t TLSFilterTransaction::FilterRead(PRFileDesc* aFD, void* aBuf,
+ int32_t aAmount) {
+ TLSFilterTransaction* self =
+ reinterpret_cast<TLSFilterTransaction*>(aFD->secret);
+ return self->FilterInput(static_cast<char*>(aBuf), aAmount);
+}
+
+int32_t TLSFilterTransaction::FilterRecv(PRFileDesc* aFD, void* aBuf,
+ int32_t aAmount, int, PRIntervalTime) {
+ return FilterRead(aFD, aBuf, aAmount);
+}
+
+/////
+// The other methods of TLSFilterTransaction just call mTransaction->method
+/////
+
+void TLSFilterTransaction::SetConnection(nsAHttpConnection* aConnection) {
+ if (!mTransaction) {
+ return;
+ }
+
+ mTransaction->SetConnection(aConnection);
+}
+
+nsAHttpConnection* TLSFilterTransaction::Connection() {
+ if (!mTransaction) {
+ return nullptr;
+ }
+ return mTransaction->Connection();
+}
+
+void TLSFilterTransaction::GetSecurityCallbacks(nsIInterfaceRequestor** outCB) {
+ if (!mTransaction) {
+ return;
+ }
+ mTransaction->GetSecurityCallbacks(outCB);
+}
+
+void TLSFilterTransaction::OnTransportStatus(nsITransport* aTransport,
+ nsresult aStatus,
+ int64_t aProgress) {
+ if (!mTransaction) {
+ return;
+ }
+ mTransaction->OnTransportStatus(aTransport, aStatus, aProgress);
+}
+
+nsHttpConnectionInfo* TLSFilterTransaction::ConnectionInfo() {
+ if (!mTransaction) {
+ return nullptr;
+ }
+ return mTransaction->ConnectionInfo();
+}
+
+bool TLSFilterTransaction::IsDone() {
+ if (!mTransaction) {
+ return true;
+ }
+ return mTransaction->IsDone();
+}
+
+nsresult TLSFilterTransaction::Status() {
+ if (!mTransaction) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return mTransaction->Status();
+}
+
+uint32_t TLSFilterTransaction::Caps() {
+ if (!mTransaction) {
+ return 0;
+ }
+
+ return mTransaction->Caps();
+}
+
+void TLSFilterTransaction::SetProxyConnectFailed() {
+ if (!mTransaction) {
+ return;
+ }
+
+ mTransaction->SetProxyConnectFailed();
+}
+
+nsHttpRequestHead* TLSFilterTransaction::RequestHead() {
+ if (!mTransaction) {
+ return nullptr;
+ }
+
+ return mTransaction->RequestHead();
+}
+
+uint32_t TLSFilterTransaction::Http1xTransactionCount() {
+ if (!mTransaction) {
+ return 0;
+ }
+
+ return mTransaction->Http1xTransactionCount();
+}
+
+nsresult TLSFilterTransaction::TakeSubTransactions(
+ nsTArray<RefPtr<nsAHttpTransaction> >& outTransactions) {
+ LOG(("TLSFilterTransaction::TakeSubTransactions [this=%p] mTransaction %p\n",
+ this, mTransaction.get()));
+
+ if (!mTransaction) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mTransaction->TakeSubTransactions(outTransactions) ==
+ NS_ERROR_NOT_IMPLEMENTED) {
+ outTransactions.AppendElement(mTransaction);
+ }
+ mTransaction = nullptr;
+
+ return NS_OK;
+}
+
+nsresult TLSFilterTransaction::SetProxiedTransaction(
+ nsAHttpTransaction* aTrans, nsAHttpTransaction* aSpdyConnectTransaction) {
+ LOG(
+ ("TLSFilterTransaction::SetProxiedTransaction [this=%p] aTrans=%p, "
+ "aSpdyConnectTransaction=%p\n",
+ this, aTrans, aSpdyConnectTransaction));
+
+ mTransaction = aTrans;
+
+ // Reverting mCloseReason to the default value for consistency to indicate we
+ // are no longer in closed state.
+ mCloseReason = NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));
+ nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(mSecInfo));
+ if (secCtrl && callbacks) {
+ secCtrl->SetNotificationCallbacks(callbacks);
+ }
+
+ mWeakTrans = do_GetWeakReference(aSpdyConnectTransaction);
+
+ return NS_OK;
+}
+
+bool TLSFilterTransaction::IsNullTransaction() {
+ if (!mTransaction) {
+ return false;
+ }
+ return mTransaction->IsNullTransaction();
+}
+
+NullHttpTransaction* TLSFilterTransaction::QueryNullTransaction() {
+ if (!mTransaction) {
+ return nullptr;
+ }
+ return mTransaction->QueryNullTransaction();
+}
+
+nsHttpTransaction* TLSFilterTransaction::QueryHttpTransaction() {
+ if (!mTransaction) {
+ return nullptr;
+ }
+ return mTransaction->QueryHttpTransaction();
+}
+
+class SocketInWrapper : public nsIAsyncInputStream,
+ public nsAHttpSegmentWriter {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_FORWARD_NSIASYNCINPUTSTREAM(mStream->)
+
+ SocketInWrapper(nsIAsyncInputStream* aWrapped, TLSFilterTransaction* aFilter)
+ : mStream(aWrapped), mTLSFilter(aFilter) {}
+
+ NS_IMETHOD Close() override {
+ mTLSFilter = nullptr;
+ return mStream->Close();
+ }
+
+ NS_IMETHOD Available(uint64_t* _retval) override {
+ return mStream->Available(_retval);
+ }
+
+ NS_IMETHOD IsNonBlocking(bool* _retval) override {
+ return mStream->IsNonBlocking(_retval);
+ }
+
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) override {
+ return mStream->ReadSegments(aWriter, aClosure, aCount, _retval);
+ }
+
+ // finally, ones that don't get forwarded :)
+ NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* _retval) override;
+ virtual nsresult OnWriteSegment(char* segment, uint32_t count,
+ uint32_t* countWritten) override;
+
+ private:
+ virtual ~SocketInWrapper() = default;
+ ;
+
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+ RefPtr<TLSFilterTransaction> mTLSFilter;
+};
+
+nsresult SocketInWrapper::OnWriteSegment(char* segment, uint32_t count,
+ uint32_t* countWritten) {
+ LOG(("SocketInWrapper OnWriteSegment %d %p filter=%p\n", count, this,
+ mTLSFilter.get()));
+
+ nsresult rv = mStream->Read(segment, count, countWritten);
+ LOG(("SocketInWrapper OnWriteSegment %p wrapped read %" PRIx32 " %d\n", this,
+ static_cast<uint32_t>(rv), *countWritten));
+ return rv;
+}
+
+NS_IMETHODIMP
+SocketInWrapper::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
+ LOG(("SocketInWrapper Read %d %p filter=%p\n", aCount, this,
+ mTLSFilter.get()));
+
+ if (!mTLSFilter) {
+ return NS_ERROR_UNEXPECTED; // protect potentially dangling mTLSFilter
+ }
+
+ // mTLSFilter->mSegmentWriter MUST be this at ctor time
+ return mTLSFilter->OnWriteSegment(aBuf, aCount, _retval);
+}
+
+class SocketOutWrapper : public nsIAsyncOutputStream,
+ public nsAHttpSegmentReader {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_FORWARD_NSIASYNCOUTPUTSTREAM(mStream->)
+
+ SocketOutWrapper(nsIAsyncOutputStream* aWrapped,
+ TLSFilterTransaction* aFilter)
+ : mStream(aWrapped), mTLSFilter(aFilter) {}
+
+ NS_IMETHOD Close() override {
+ mTLSFilter = nullptr;
+ return mStream->Close();
+ }
+
+ NS_IMETHOD Flush() override { return mStream->Flush(); }
+
+ NS_IMETHOD IsNonBlocking(bool* _retval) override {
+ return mStream->IsNonBlocking(_retval);
+ }
+
+ NS_IMETHOD WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) override {
+ return mStream->WriteSegments(aReader, aClosure, aCount, _retval);
+ }
+
+ NS_IMETHOD WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* _retval) override {
+ return mStream->WriteFrom(aFromStream, aCount, _retval);
+ }
+
+ // finally, ones that don't get forwarded :)
+ NS_IMETHOD Write(const char* aBuf, uint32_t aCount,
+ uint32_t* _retval) override;
+ virtual nsresult OnReadSegment(const char* segment, uint32_t count,
+ uint32_t* countRead) override;
+
+ private:
+ virtual ~SocketOutWrapper() = default;
+ ;
+
+ nsCOMPtr<nsIAsyncOutputStream> mStream;
+ RefPtr<TLSFilterTransaction> mTLSFilter;
+};
+
+nsresult SocketOutWrapper::OnReadSegment(const char* segment, uint32_t count,
+ uint32_t* countWritten) {
+ return mStream->Write(segment, count, countWritten);
+}
+
+NS_IMETHODIMP
+SocketOutWrapper::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) {
+ LOG(("SocketOutWrapper Write %d %p filter=%p\n", aCount, this,
+ mTLSFilter.get()));
+
+ // mTLSFilter->mSegmentReader MUST be this at ctor time
+ if (!mTLSFilter) {
+ return NS_ERROR_UNEXPECTED; // protect potentially dangling mTLSFilter
+ }
+
+ return mTLSFilter->OnReadSegment(aBuf, aCount, _retval);
+}
+
+void TLSFilterTransaction::newIODriver(nsIAsyncInputStream* aSocketIn,
+ nsIAsyncOutputStream* aSocketOut,
+ nsIAsyncInputStream** outSocketIn,
+ nsIAsyncOutputStream** outSocketOut) {
+ SocketInWrapper* inputWrapper = new SocketInWrapper(aSocketIn, this);
+ mSegmentWriter = inputWrapper;
+ nsCOMPtr<nsIAsyncInputStream> newIn(inputWrapper);
+ newIn.forget(outSocketIn);
+
+ SocketOutWrapper* outputWrapper = new SocketOutWrapper(aSocketOut, this);
+ mSegmentReader = outputWrapper;
+ nsCOMPtr<nsIAsyncOutputStream> newOut(outputWrapper);
+ newOut.forget(outSocketOut);
+}
+
+SpdyConnectTransaction* TLSFilterTransaction::QuerySpdyConnectTransaction() {
+ if (!mTransaction) {
+ return nullptr;
+ }
+ return mTransaction->QuerySpdyConnectTransaction();
+}
+
+class WeakTransProxy final : public nsISupports {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit WeakTransProxy(SpdyConnectTransaction* aTrans) {
+ MOZ_ASSERT(OnSocketThread());
+ mWeakTrans = do_GetWeakReference(aTrans);
+ }
+
+ already_AddRefed<NullHttpTransaction> QueryTransaction() {
+ MOZ_ASSERT(OnSocketThread());
+ RefPtr<NullHttpTransaction> trans = do_QueryReferent(mWeakTrans);
+ return trans.forget();
+ }
+
+ private:
+ ~WeakTransProxy() { MOZ_ASSERT(OnSocketThread()); }
+
+ nsWeakPtr mWeakTrans;
+};
+
+NS_IMPL_ISUPPORTS(WeakTransProxy, nsISupports)
+
+class WeakTransFreeProxy final : public Runnable {
+ public:
+ explicit WeakTransFreeProxy(WeakTransProxy* proxy)
+ : Runnable("WeakTransFreeProxy"), mProxy(proxy) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(OnSocketThread());
+ mProxy = nullptr;
+ return NS_OK;
+ }
+
+ void Dispatch() {
+ MOZ_ASSERT(!OnSocketThread());
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ Unused << sts->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+ }
+
+ private:
+ RefPtr<WeakTransProxy> mProxy;
+};
+
+class SocketTransportShim : public nsISocketTransport {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORT
+ NS_DECL_NSISOCKETTRANSPORT
+
+ explicit SocketTransportShim(SpdyConnectTransaction* aTrans,
+ nsISocketTransport* aWrapped, bool aIsWebsocket)
+ : mWrapped(aWrapped), mIsWebsocket(aIsWebsocket) {
+ mWeakTrans = new WeakTransProxy(aTrans);
+ }
+
+ private:
+ virtual ~SocketTransportShim() {
+ if (!OnSocketThread()) {
+ RefPtr<WeakTransFreeProxy> p = new WeakTransFreeProxy(mWeakTrans);
+ mWeakTrans = nullptr;
+ p->Dispatch();
+ }
+ }
+
+ nsCOMPtr<nsISocketTransport> mWrapped;
+ bool mIsWebsocket;
+ nsCOMPtr<nsIInterfaceRequestor> mSecurityCallbacks;
+ RefPtr<WeakTransProxy> mWeakTrans; // SpdyConnectTransaction *
+};
+
+class OutputStreamShim : public nsIAsyncOutputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+
+ friend class SpdyConnectTransaction;
+ friend class WebsocketHasDataToWrite;
+ friend class OutputCloseTransaction;
+
+ OutputStreamShim(SpdyConnectTransaction* aTrans, bool aIsWebsocket)
+ : mCallback(nullptr),
+ mStatus(NS_OK),
+ mMutex("OutputStreamShim"),
+ mIsWebsocket(aIsWebsocket) {
+ mWeakTrans = new WeakTransProxy(aTrans);
+ }
+
+ already_AddRefed<nsIOutputStreamCallback> TakeCallback();
+
+ private:
+ virtual ~OutputStreamShim() {
+ if (!OnSocketThread()) {
+ RefPtr<WeakTransFreeProxy> p = new WeakTransFreeProxy(mWeakTrans);
+ mWeakTrans = nullptr;
+ p->Dispatch();
+ }
+ }
+
+ RefPtr<WeakTransProxy> mWeakTrans; // SpdyConnectTransaction *
+ nsCOMPtr<nsIOutputStreamCallback> mCallback;
+ nsresult mStatus;
+ mozilla::Mutex mMutex;
+
+ // Websockets
+ bool mIsWebsocket;
+ nsresult CallTransactionHasDataToWrite();
+ nsresult CloseTransaction(nsresult reason);
+};
+
+class InputStreamShim : public nsIAsyncInputStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ friend class SpdyConnectTransaction;
+ friend class InputCloseTransaction;
+
+ InputStreamShim(SpdyConnectTransaction* aTrans, bool aIsWebsocket)
+ : mCallback(nullptr),
+ mStatus(NS_OK),
+ mMutex("InputStreamShim"),
+ mIsWebsocket(aIsWebsocket) {
+ mWeakTrans = new WeakTransProxy(aTrans);
+ }
+
+ already_AddRefed<nsIInputStreamCallback> TakeCallback();
+ bool HasCallback();
+
+ private:
+ virtual ~InputStreamShim() {
+ if (!OnSocketThread()) {
+ RefPtr<WeakTransFreeProxy> p = new WeakTransFreeProxy(mWeakTrans);
+ mWeakTrans = nullptr;
+ p->Dispatch();
+ }
+ }
+
+ RefPtr<WeakTransProxy> mWeakTrans; // SpdyConnectTransaction *
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsresult mStatus;
+ mozilla::Mutex mMutex;
+
+ // Websockets
+ bool mIsWebsocket;
+ nsresult CloseTransaction(nsresult reason);
+};
+
+SpdyConnectTransaction::SpdyConnectTransaction(
+ nsHttpConnectionInfo* ci, nsIInterfaceRequestor* callbacks, uint32_t caps,
+ nsHttpTransaction* trans, nsAHttpConnection* session, bool isWebsocket)
+ : NullHttpTransaction(ci, callbacks, caps | NS_HTTP_ALLOW_KEEPALIVE),
+ mConnectStringOffset(0),
+ mSession(session),
+ mSegmentReader(nullptr),
+ mInputDataSize(0),
+ mInputDataUsed(0),
+ mInputDataOffset(0),
+ mOutputDataSize(0),
+ mOutputDataUsed(0),
+ mOutputDataOffset(0),
+ mForcePlainText(false),
+ mIsWebsocket(isWebsocket),
+ mConnRefTaken(false),
+ mCreateShimErrorCalled(false) {
+ LOG(("SpdyConnectTransaction ctor %p\n", this));
+
+ mTimestampSyn = TimeStamp::Now();
+ mRequestHead = new nsHttpRequestHead();
+ if (mIsWebsocket) {
+ // Ensure our request head has all the websocket headers duplicated from the
+ // original transaction before calling the boilerplate stuff to create the
+ // rest of the CONNECT headers.
+ trans->RequestHead()->Enter();
+ mRequestHead->SetHeaders(trans->RequestHead()->Headers());
+ trans->RequestHead()->Exit();
+ }
+ DebugOnly<nsresult> rv = nsHttpConnection::MakeConnectString(
+ trans, mRequestHead, mConnectString, mIsWebsocket);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mDrivingTransaction = trans;
+}
+
+SpdyConnectTransaction::~SpdyConnectTransaction() {
+ LOG(("SpdyConnectTransaction dtor %p\n", this));
+
+ MOZ_ASSERT(OnSocketThread());
+
+ if (mDrivingTransaction) {
+ // requeue it I guess. This should be gone.
+ mDrivingTransaction->SetH2WSTransaction(nullptr);
+ Unused << gHttpHandler->InitiateTransaction(
+ mDrivingTransaction, mDrivingTransaction->Priority());
+ }
+}
+
+void SpdyConnectTransaction::ForcePlainText() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mInputDataUsed && !mInputDataSize && !mInputDataOffset);
+ MOZ_ASSERT(!mForcePlainText);
+ MOZ_ASSERT(!mTunnelTransport, "call before mapstreamtohttpconnection");
+ MOZ_ASSERT(!mIsWebsocket);
+
+ mForcePlainText = true;
+}
+
+bool SpdyConnectTransaction::MapStreamToHttpConnection(
+ nsISocketTransport* aTransport, nsHttpConnectionInfo* aConnInfo,
+ const nsACString& aFlat407Headers, int32_t aHttpResponseCode) {
+ MOZ_ASSERT(OnSocketThread());
+
+ if (aHttpResponseCode >= 100 && aHttpResponseCode < 200) {
+ LOG(
+ ("SpdyConnectTransaction::MapStreamToHttpConnection %p skip "
+ "pre-response with response code %d",
+ this, aHttpResponseCode));
+ return false;
+ }
+
+ mTunnelTransport = new SocketTransportShim(this, aTransport, mIsWebsocket);
+ mTunnelStreamIn = new InputStreamShim(this, mIsWebsocket);
+ mTunnelStreamOut = new OutputStreamShim(this, mIsWebsocket);
+ mTunneledConn = new nsHttpConnection();
+
+ // If aHttpResponseCode is -1, it means that proxy connect is not used. We
+ // should not call HttpProxyResponseToErrorCode(), since this will create a
+ // shim error.
+ if (aHttpResponseCode > 0 && aHttpResponseCode != 200) {
+ nsresult err = HttpProxyResponseToErrorCode(aHttpResponseCode);
+ if (NS_FAILED(err)) {
+ CreateShimError(err);
+ }
+ }
+
+ // this new http connection has a specific hashkey (i.e. to a particular
+ // host via the tunnel) and is associated with the tunnel streams
+ LOG(("SpdyConnectTransaction %p new httpconnection %p %s\n", this,
+ mTunneledConn.get(), aConnInfo->HashKey().get()));
+
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ GetSecurityCallbacks(getter_AddRefs(callbacks));
+ mTunneledConn->SetTransactionCaps(Caps());
+ MOZ_ASSERT(aConnInfo->UsingHttpsProxy() || mIsWebsocket);
+ TimeDuration rtt = TimeStamp::Now() - mTimestampSyn;
+ DebugOnly<nsresult> rv = mTunneledConn->Init(
+ aConnInfo, gHttpHandler->ConnMgr()->MaxRequestDelay(), mTunnelTransport,
+ mTunnelStreamIn, mTunnelStreamOut, true, callbacks,
+ PR_MillisecondsToInterval(static_cast<uint32_t>(rtt.ToMilliseconds())));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (mForcePlainText) {
+ mTunneledConn->ForcePlainText();
+ } else if (mIsWebsocket) {
+ LOG(("SpdyConnectTransaction::MapStreamToHttpConnection %p websocket",
+ this));
+ // Let the root transaction know about us, so it can pass our own conn
+ // to the websocket.
+ mDrivingTransaction->SetH2WSTransaction(this);
+ } else {
+ mTunneledConn->SetupSecondaryTLS(this);
+ mTunneledConn->SetInSpdyTunnel(true);
+ }
+
+ // make the originating transaction stick to the tunneled conn
+ RefPtr<nsAHttpConnection> wrappedConn =
+ gHttpHandler->ConnMgr()->MakeConnectionHandle(mTunneledConn);
+ mDrivingTransaction->SetConnection(wrappedConn);
+ mDrivingTransaction->MakeSticky();
+
+ if (!mIsWebsocket) {
+ mDrivingTransaction->OnProxyConnectComplete(aHttpResponseCode);
+
+ if (aHttpResponseCode == 407) {
+ mDrivingTransaction->SetFlat407Headers(aFlat407Headers);
+ mDrivingTransaction->SetProxyConnectFailed();
+ }
+
+ // jump the priority and start the dispatcher
+ Unused << gHttpHandler->InitiateTransaction(
+ mDrivingTransaction, nsISupportsPriority::PRIORITY_HIGHEST - 60);
+ mDrivingTransaction = nullptr;
+ }
+
+ return true;
+}
+
+nsresult SpdyConnectTransaction::Flush(uint32_t count, uint32_t* countRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("SpdyConnectTransaction::Flush %p count %d avail %d\n", this, count,
+ mOutputDataUsed - mOutputDataOffset));
+
+ if (!mSegmentReader) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *countRead = 0;
+ count = std::min(count, (mOutputDataUsed - mOutputDataOffset));
+ if (count) {
+ nsresult rv;
+ rv = mSegmentReader->OnReadSegment(&mOutputData[mOutputDataOffset], count,
+ countRead);
+ if (NS_FAILED(rv) && (rv != NS_BASE_STREAM_WOULD_BLOCK)) {
+ LOG(("SpdyConnectTransaction::Flush %p Error %" PRIx32 "\n", this,
+ static_cast<uint32_t>(rv)));
+ CreateShimError(rv);
+ return rv;
+ }
+ }
+
+ mOutputDataOffset += *countRead;
+ if (mOutputDataOffset == mOutputDataUsed) {
+ mOutputDataOffset = mOutputDataUsed = 0;
+ }
+ if (!(*countRead)) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ if (mOutputDataUsed != mOutputDataOffset) {
+ LOG(("SpdyConnectTransaction::Flush %p Incomplete %d\n", this,
+ mOutputDataUsed - mOutputDataOffset));
+ mSession->TransactionHasDataToWrite(this);
+ }
+
+ return NS_OK;
+}
+
+nsresult SpdyConnectTransaction::ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count,
+ uint32_t* countRead) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("SpdyConnectTransaction::ReadSegments %p count %d conn %p\n", this,
+ count, mTunneledConn.get()));
+
+ mSegmentReader = reader;
+
+ // spdy stream carrying tunnel is not setup yet.
+ if (!mTunneledConn) {
+ uint32_t toWrite = mConnectString.Length() - mConnectStringOffset;
+ toWrite = std::min(toWrite, count);
+ *countRead = toWrite;
+ if (toWrite) {
+ nsresult rv = mSegmentReader->OnReadSegment(
+ mConnectString.BeginReading() + mConnectStringOffset, toWrite,
+ countRead);
+ if (NS_FAILED(rv) && (rv != NS_BASE_STREAM_WOULD_BLOCK)) {
+ LOG(
+ ("SpdyConnectTransaction::ReadSegments %p OnReadSegmentError "
+ "%" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv)));
+ CreateShimError(rv);
+ } else {
+ mConnectStringOffset += toWrite;
+ if (mConnectString.Length() == mConnectStringOffset) {
+ mConnectString.Truncate();
+ mConnectStringOffset = 0;
+ }
+ }
+ return rv;
+ }
+
+ LOG(("SpdyConnectTransaciton::ReadSegments %p connect request consumed",
+ this));
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ if (mForcePlainText) {
+ // this path just ignores sending the request so that we can
+ // send a synthetic reply in writesegments()
+ LOG(
+ ("SpdyConnectTransaciton::ReadSegments %p dropping %d output bytes "
+ "due to synthetic reply\n",
+ this, mOutputDataUsed - mOutputDataOffset));
+ *countRead = mOutputDataUsed - mOutputDataOffset;
+ mOutputDataOffset = mOutputDataUsed = 0;
+ mTunneledConn->DontReuse();
+ return NS_OK;
+ }
+
+ *countRead = 0;
+ nsresult rv = Flush(count, countRead);
+ if (!(*countRead)) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ nsCOMPtr<nsIOutputStreamCallback> cb = mTunnelStreamOut->TakeCallback();
+ if (!cb) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // See if there is any more data available
+ rv = cb->OnOutputStreamReady(mTunnelStreamOut);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Write out anything that may have come out of the stream just above
+ uint32_t subtotal;
+ count -= *countRead;
+ rv = Flush(count, &subtotal);
+ *countRead += subtotal;
+
+ return rv;
+}
+
+void SpdyConnectTransaction::CreateShimError(nsresult code) {
+ LOG(("SpdyConnectTransaction::CreateShimError %p 0x%08" PRIx32, this,
+ static_cast<uint32_t>(code)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(NS_FAILED(code));
+
+ MOZ_ASSERT(!mCreateShimErrorCalled);
+ if (mCreateShimErrorCalled) {
+ return;
+ }
+ mCreateShimErrorCalled = true;
+
+ if (mTunnelStreamOut && NS_SUCCEEDED(mTunnelStreamOut->mStatus)) {
+ mTunnelStreamOut->mStatus = code;
+ }
+
+ if (mTunnelStreamIn && NS_SUCCEEDED(mTunnelStreamIn->mStatus)) {
+ mTunnelStreamIn->mStatus = code;
+ }
+
+ if (mTunnelStreamIn) {
+ nsCOMPtr<nsIInputStreamCallback> cb = mTunnelStreamIn->TakeCallback();
+ if (cb) {
+ cb->OnInputStreamReady(mTunnelStreamIn);
+ }
+ }
+
+ if (mTunnelStreamOut) {
+ nsCOMPtr<nsIOutputStreamCallback> cb = mTunnelStreamOut->TakeCallback();
+ if (cb) {
+ cb->OnOutputStreamReady(mTunnelStreamOut);
+ }
+ }
+ mCreateShimErrorCalled = false;
+}
+
+nsresult SpdyConnectTransaction::WriteDataToBuffer(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ EnsureBuffer(mInputData, mInputDataUsed + count, mInputDataUsed,
+ mInputDataSize);
+ nsresult rv =
+ writer->OnWriteSegment(&mInputData[mInputDataUsed], count, countWritten);
+ if (NS_FAILED(rv)) {
+ if (rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ LOG(
+ ("SpdyConnectTransaction::WriteSegments wrapped writer %p Error "
+ "%" PRIx32 "\n",
+ this, static_cast<uint32_t>(rv)));
+ CreateShimError(rv);
+ }
+ return rv;
+ }
+ mInputDataUsed += *countWritten;
+ LOG(
+ ("SpdyConnectTransaction %p %d new bytes [%d total] of ciphered data "
+ "buffered\n",
+ this, *countWritten, mInputDataUsed - mInputDataOffset));
+
+ return rv;
+}
+
+nsresult SpdyConnectTransaction::WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("SpdyConnectTransaction::WriteSegments %p max=%d", this, count));
+
+ // For websockets, we need to forward the initial response through to the base
+ // transaction so the normal websocket plumbing can do all the things it needs
+ // to do.
+ if (mIsWebsocket) {
+ return WebsocketWriteSegments(writer, count, countWritten);
+ }
+
+ // first call into the tunnel stream to get the demux'd data out of the
+ // spdy session.
+ nsresult rv = WriteDataToBuffer(writer, count, countWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIInputStreamCallback> cb =
+ mTunneledConn ? mTunnelStreamIn->TakeCallback() : nullptr;
+ LOG(("SpdyConnectTransaction::WriteSegments %p cb=%p", this, cb.get()));
+
+ if (!cb) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ rv = cb->OnInputStreamReady(mTunnelStreamIn);
+ LOG(
+ ("SpdyConnectTransaction::WriteSegments %p "
+ "after InputStreamReady callback %d total of ciphered data buffered "
+ "rv=%" PRIx32 "\n",
+ this, mInputDataUsed - mInputDataOffset, static_cast<uint32_t>(rv)));
+ LOG(
+ ("SpdyConnectTransaction::WriteSegments %p "
+ "goodput %p out %" PRId64 "\n",
+ this, mTunneledConn.get(), mTunneledConn->ContentBytesWritten()));
+ if (NS_SUCCEEDED(rv) && !mTunneledConn->ContentBytesWritten()) {
+ nsCOMPtr<nsIOutputStreamCallback> ocb = mTunnelStreamOut->TakeCallback();
+ mTunnelStreamOut->AsyncWait(ocb, 0, 0, nullptr);
+ }
+ return rv;
+}
+
+nsresult SpdyConnectTransaction::WebsocketWriteSegments(
+ nsAHttpSegmentWriter* writer, uint32_t count, uint32_t* countWritten) {
+ MOZ_ASSERT(OnSocketThread());
+ MOZ_ASSERT(mIsWebsocket);
+ LOG(("SpdyConnectTransaction::WebsocketWriteSegments %p max=%d", this,
+ count));
+
+ if (mDrivingTransaction && !mDrivingTransaction->IsDone()) {
+ // Transaction hasn't received end of headers yet, so keep passing data to
+ // it until it has. Then we can take over.
+ nsresult rv =
+ mDrivingTransaction->WriteSegments(writer, count, countWritten);
+ if (NS_SUCCEEDED(rv) && mDrivingTransaction->IsDone() && !mConnRefTaken) {
+ mDrivingTransaction->Close(NS_OK);
+ }
+ }
+
+ if (!mConnRefTaken) {
+ // Force driving transaction to finish so the websocket channel can get its
+ // notifications correctly and start driving.
+ MOZ_ASSERT(mDrivingTransaction);
+ mDrivingTransaction->Close(NS_OK);
+ }
+
+ nsresult rv = WriteDataToBuffer(writer, count, countWritten);
+ if (NS_SUCCEEDED(rv)) {
+ if (!mTunneledConn) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ nsCOMPtr<nsIInputStreamCallback> cb = mTunnelStreamIn->TakeCallback();
+ if (!cb) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+ rv = cb->OnInputStreamReady(mTunnelStreamIn);
+ }
+
+ return rv;
+}
+
+bool SpdyConnectTransaction::ConnectedReadyForInput() {
+ return mTunneledConn && mTunnelStreamIn->HasCallback();
+}
+
+nsHttpRequestHead* SpdyConnectTransaction::RequestHead() {
+ return mRequestHead;
+}
+
+void SpdyConnectTransaction::Close(nsresult code) {
+ LOG(("SpdyConnectTransaction close %p %" PRIx32 "\n", this,
+ static_cast<uint32_t>(code)));
+
+ MOZ_ASSERT(OnSocketThread());
+
+ if (mIsWebsocket && mDrivingTransaction) {
+ mDrivingTransaction->SetH2WSTransaction(nullptr);
+ if (!mConnRefTaken) {
+ // This indicates that the websocket failed to set up, so just close down
+ // the transaction as usual.
+ mDrivingTransaction->Close(code);
+ mDrivingTransaction = nullptr;
+ }
+ }
+ NullHttpTransaction::Close(code);
+ if (NS_FAILED(code) && (code != NS_BASE_STREAM_WOULD_BLOCK)) {
+ CreateShimError(code);
+ } else {
+ CreateShimError(NS_BASE_STREAM_CLOSED);
+ }
+}
+
+void SpdyConnectTransaction::SetConnRefTaken() {
+ MOZ_ASSERT(OnSocketThread());
+
+ mConnRefTaken = true;
+ mDrivingTransaction = nullptr; // Just in case
+}
+
+already_AddRefed<nsIOutputStreamCallback> OutputStreamShim::TakeCallback() {
+ mozilla::MutexAutoLock lock(mMutex);
+ return mCallback.forget();
+}
+
+class WebsocketHasDataToWrite final : public Runnable {
+ public:
+ explicit WebsocketHasDataToWrite(OutputStreamShim* shim)
+ : Runnable("WebsocketHasDataToWrite"), mShim(shim) {}
+
+ ~WebsocketHasDataToWrite() = default;
+
+ NS_IMETHOD Run() override { return mShim->CallTransactionHasDataToWrite(); }
+
+ [[nodiscard]] nsresult Dispatch() {
+ if (OnSocketThread()) {
+ return Run();
+ }
+
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ return sts->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+ }
+
+ private:
+ RefPtr<OutputStreamShim> mShim;
+};
+
+NS_IMETHODIMP
+OutputStreamShim::AsyncWait(nsIOutputStreamCallback* callback,
+ unsigned int flags, unsigned int requestedCount,
+ nsIEventTarget* target) {
+ if (mIsWebsocket) {
+ // With websockets, AsyncWait may be called from the main thread, but the
+ // target is on the socket thread. That's all we really care about.
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT((!target && !callback) || (target == sts));
+ if (target && (target != sts)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ bool currentThread;
+
+ if (target && (NS_FAILED(target->IsOnCurrentThread(&currentThread)) ||
+ !currentThread)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ LOG(("OutputStreamShim::AsyncWait %p callback %p\n", this, callback));
+
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ mCallback = callback;
+ }
+
+ RefPtr<WebsocketHasDataToWrite> wsdw = new WebsocketHasDataToWrite(this);
+ Unused << wsdw->Dispatch();
+
+ return NS_OK;
+}
+
+class OutputCloseTransaction final : public Runnable {
+ public:
+ OutputCloseTransaction(OutputStreamShim* shim, nsresult reason)
+ : Runnable("OutputCloseTransaction"), mShim(shim), mReason(reason) {}
+
+ ~OutputCloseTransaction() = default;
+
+ NS_IMETHOD Run() override { return mShim->CloseTransaction(mReason); }
+
+ private:
+ RefPtr<OutputStreamShim> mShim;
+ nsresult mReason;
+};
+
+NS_IMETHODIMP
+OutputStreamShim::CloseWithStatus(nsresult reason) {
+ if (!OnSocketThread()) {
+ RefPtr<OutputCloseTransaction> oct =
+ new OutputCloseTransaction(this, reason);
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ return sts->Dispatch(oct, nsIEventTarget::DISPATCH_NORMAL);
+ }
+
+ return CloseTransaction(reason);
+}
+
+nsresult OutputStreamShim::CloseTransaction(nsresult reason) {
+ MOZ_ASSERT(OnSocketThread());
+ RefPtr<NullHttpTransaction> baseTrans = mWeakTrans->QueryTransaction();
+ if (!baseTrans) {
+ return NS_ERROR_FAILURE;
+ }
+ SpdyConnectTransaction* trans = baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ trans->mSession->CloseTransaction(trans, reason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::Close() { return CloseWithStatus(NS_OK); }
+
+NS_IMETHODIMP
+OutputStreamShim::Flush() {
+ MOZ_ASSERT(OnSocketThread());
+ RefPtr<NullHttpTransaction> baseTrans = mWeakTrans->QueryTransaction();
+ if (!baseTrans) {
+ return NS_ERROR_FAILURE;
+ }
+ SpdyConnectTransaction* trans = baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ uint32_t count = trans->mOutputDataUsed - trans->mOutputDataOffset;
+ if (!count) {
+ return NS_OK;
+ }
+
+ uint32_t countRead;
+ nsresult rv = trans->Flush(count, &countRead);
+ LOG(("OutputStreamShim::Flush %p before %d after %d\n", this, count,
+ trans->mOutputDataUsed - trans->mOutputDataOffset));
+ return rv;
+}
+
+nsresult OutputStreamShim::CallTransactionHasDataToWrite() {
+ MOZ_ASSERT(OnSocketThread());
+ RefPtr<NullHttpTransaction> baseTrans = mWeakTrans->QueryTransaction();
+ if (!baseTrans) {
+ return NS_ERROR_FAILURE;
+ }
+ SpdyConnectTransaction* trans = baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ trans->mSession->TransactionHasDataToWrite(trans);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ RefPtr<NullHttpTransaction> baseTrans = mWeakTrans->QueryTransaction();
+ if (!baseTrans) {
+ return NS_ERROR_FAILURE;
+ }
+ SpdyConnectTransaction* trans = baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if ((trans->mOutputDataUsed + aCount) >= 512000) {
+ *_retval = 0;
+ // time for some flow control;
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ EnsureBuffer(trans->mOutputData, trans->mOutputDataUsed + aCount,
+ trans->mOutputDataUsed, trans->mOutputDataSize);
+ memcpy(&trans->mOutputData[trans->mOutputDataUsed], aBuf, aCount);
+ trans->mOutputDataUsed += aCount;
+ *_retval = aCount;
+ LOG(("OutputStreamShim::Write %p new %d total %d\n", this, aCount,
+ trans->mOutputDataUsed));
+
+ trans->mSession->TransactionHasDataToWrite(trans);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount,
+ uint32_t* _retval) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: OutputStreamShim::WriteFrom %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: OutputStreamShim::WriteSegments %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+OutputStreamShim::IsNonBlocking(bool* _retval) {
+ *_retval = true;
+ return NS_OK;
+}
+
+already_AddRefed<nsIInputStreamCallback> InputStreamShim::TakeCallback() {
+ mozilla::MutexAutoLock lock(mMutex);
+ return mCallback.forget();
+}
+
+bool InputStreamShim::HasCallback() {
+ mozilla::MutexAutoLock lock(mMutex);
+ return mCallback != nullptr;
+}
+
+class CheckAvailData final : public Runnable {
+ public:
+ explicit CheckAvailData(InputStreamShim* shim)
+ : Runnable("CheckAvailData"), mShim(shim) {}
+
+ ~CheckAvailData() = default;
+
+ NS_IMETHOD Run() override {
+ uint64_t avail = 0;
+ if (NS_SUCCEEDED(mShim->Available(&avail)) && avail) {
+ nsCOMPtr<nsIInputStreamCallback> cb = mShim->TakeCallback();
+ if (cb) {
+ cb->OnInputStreamReady(mShim);
+ }
+ }
+ return NS_OK;
+ }
+
+ [[nodiscard]] nsresult Dispatch() {
+ // Dispatch the event even if we're on socket thread to avoid closing and
+ // destructing Http2Session in case this call is comming from
+ // Http2Session::ReadSegments() and the callback closes the transaction in
+ // OnInputStreamRead().
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ return sts->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
+ }
+
+ private:
+ RefPtr<InputStreamShim> mShim;
+};
+
+NS_IMETHODIMP
+InputStreamShim::AsyncWait(nsIInputStreamCallback* callback, unsigned int flags,
+ unsigned int requestedCount,
+ nsIEventTarget* target) {
+ if (mIsWebsocket) {
+ // With websockets, AsyncWait may be called from the main thread, but the
+ // target is on the socket thread. That's all we really care about.
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT((!target && !callback) || (target == sts));
+ if (target && (target != sts)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ bool currentThread;
+
+ if (target && (NS_FAILED(target->IsOnCurrentThread(&currentThread)) ||
+ !currentThread)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ LOG(("InputStreamShim::AsyncWait %p callback %p\n", this, callback));
+
+ {
+ mozilla::MutexAutoLock lock(mMutex);
+ mCallback = callback;
+ }
+
+ if (callback) {
+ RefPtr<CheckAvailData> cad = new CheckAvailData(this);
+ Unused << cad->Dispatch();
+ }
+
+ return NS_OK;
+}
+
+class InputCloseTransaction final : public Runnable {
+ public:
+ InputCloseTransaction(InputStreamShim* shim, nsresult reason)
+ : Runnable("InputCloseTransaction"), mShim(shim), mReason(reason) {}
+
+ ~InputCloseTransaction() = default;
+
+ NS_IMETHOD Run() override { return mShim->CloseTransaction(mReason); }
+
+ private:
+ RefPtr<InputStreamShim> mShim;
+ nsresult mReason;
+};
+
+NS_IMETHODIMP
+InputStreamShim::CloseWithStatus(nsresult reason) {
+ if (!OnSocketThread()) {
+ RefPtr<InputCloseTransaction> ict = new InputCloseTransaction(this, reason);
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ return sts->Dispatch(ict, nsIEventTarget::DISPATCH_NORMAL);
+ }
+
+ return CloseTransaction(reason);
+}
+
+nsresult InputStreamShim::CloseTransaction(nsresult reason) {
+ MOZ_ASSERT(OnSocketThread());
+ RefPtr<NullHttpTransaction> baseTrans = mWeakTrans->QueryTransaction();
+ if (!baseTrans) {
+ return NS_ERROR_FAILURE;
+ }
+ SpdyConnectTransaction* trans = baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ trans->mSession->CloseTransaction(trans, reason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::Close() { return CloseWithStatus(NS_OK); }
+
+NS_IMETHODIMP
+InputStreamShim::Available(uint64_t* _retval) {
+ RefPtr<NullHttpTransaction> baseTrans = mWeakTrans->QueryTransaction();
+ if (!baseTrans) {
+ return NS_ERROR_FAILURE;
+ }
+ SpdyConnectTransaction* trans = baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ *_retval = trans->mInputDataUsed - trans->mInputDataOffset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ RefPtr<NullHttpTransaction> baseTrans = mWeakTrans->QueryTransaction();
+ if (!baseTrans) {
+ return NS_ERROR_FAILURE;
+ }
+ SpdyConnectTransaction* trans = baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ uint32_t avail = trans->mInputDataUsed - trans->mInputDataOffset;
+ uint32_t tocopy = std::min(aCount, avail);
+ *_retval = tocopy;
+ memcpy(aBuf, &trans->mInputData[trans->mInputDataOffset], tocopy);
+ trans->mInputDataOffset += tocopy;
+ if (trans->mInputDataOffset == trans->mInputDataUsed) {
+ trans->mInputDataOffset = trans->mInputDataUsed = 0;
+ }
+
+ return tocopy ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+}
+
+NS_IMETHODIMP
+InputStreamShim::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: InputStreamShim::ReadSegments %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InputStreamShim::IsNonBlocking(bool* _retval) {
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetKeepaliveEnabled(bool aKeepaliveEnabled) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::SetKeepaliveEnabled %p called", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetKeepaliveVals(int32_t keepaliveIdleTime,
+ int32_t keepaliveRetryInterval) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::SetKeepaliveVals %p called", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetSecurityCallbacks(
+ nsIInterfaceRequestor** aSecurityCallbacks) {
+ if (mIsWebsocket) {
+ nsCOMPtr<nsIInterfaceRequestor> out(mSecurityCallbacks);
+ *aSecurityCallbacks = out.forget().take();
+ return NS_OK;
+ }
+
+ return mWrapped->GetSecurityCallbacks(aSecurityCallbacks);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetSecurityCallbacks(
+ nsIInterfaceRequestor* aSecurityCallbacks) {
+ if (mIsWebsocket) {
+ mSecurityCallbacks = aSecurityCallbacks;
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIInputStream** _retval) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::OpenInputStream %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIOutputStream** _retval) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::OpenOutputStream %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::Close(nsresult aReason) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::Close %p", this));
+ } else {
+ LOG(("SocketTransportShim::Close %p", this));
+ }
+
+ if (gHttpHandler->Bug1563538()) {
+ // Must always post, because mSession->CloseTransaction releases the
+ // Http2Stream which is still on stack.
+ RefPtr<SocketTransportShim> self(this);
+
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ Unused << sts->Dispatch(NS_NewRunnableFunction(
+ "SocketTransportShim::Close", [self = std::move(self), aReason]() {
+ RefPtr<NullHttpTransaction> baseTrans =
+ self->mWeakTrans->QueryTransaction();
+ if (!baseTrans) {
+ return;
+ }
+ SpdyConnectTransaction* trans =
+ baseTrans->QuerySpdyConnectTransaction();
+ MOZ_ASSERT(trans);
+ if (!trans) {
+ return;
+ }
+
+ trans->mSession->CloseTransaction(trans, aReason);
+ }));
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetEventSink(nsITransportEventSink* aSink,
+ nsIEventTarget* aEventTarget) {
+ if (mIsWebsocket) {
+ // Need to pretend, since websockets expect this to work
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::Bind(NetAddr* aLocalAddr) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::Bind %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetFirstRetryError(nsresult* aFirstRetryError) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::GetFirstRetryError %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetEchConfigUsed(bool* aEchConfigUsed) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::GetEchConfigUsed %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetEchConfig(const nsACString& aEchConfig) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::SetEchConfig %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SocketTransportShim::ResolvedByTRR(bool* aResolvedByTRR) {
+ if (mIsWebsocket) {
+ LOG3(("WARNING: SocketTransportShim::IsTRR %p", this));
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#define FWD_TS_PTR(fx, ts) \
+ NS_IMETHODIMP \
+ SocketTransportShim::fx(ts* arg) { return mWrapped->fx(arg); }
+
+#define FWD_TS_ADDREF(fx, ts) \
+ NS_IMETHODIMP \
+ SocketTransportShim::fx(ts** arg) { return mWrapped->fx(arg); }
+
+#define FWD_TS(fx, ts) \
+ NS_IMETHODIMP \
+ SocketTransportShim::fx(ts arg) { return mWrapped->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_ADDREF(GetSecurityInfo, nsISupports);
+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 SocketTransportShim::GetOriginAttributes(
+ mozilla::OriginAttributes* aOriginAttributes) {
+ return mWrapped->GetOriginAttributes(aOriginAttributes);
+}
+
+nsresult SocketTransportShim::SetOriginAttributes(
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ return mWrapped->SetOriginAttributes(aOriginAttributes);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
+ return mWrapped->GetScriptableOriginAttributes(aCx, aOriginAttributes);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetScriptableOriginAttributes(
+ JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) {
+ return mWrapped->SetScriptableOriginAttributes(aCx, aOriginAttributes);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetHost(nsACString& aHost) {
+ return mWrapped->GetHost(aHost);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetTimeout(uint32_t aType, uint32_t* _retval) {
+ return mWrapped->GetTimeout(aType, _retval);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetTimeout(uint32_t aType, uint32_t aValue) {
+ return mWrapped->SetTimeout(aType, aValue);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetReuseAddrPort(bool aReuseAddrPort) {
+ return mWrapped->SetReuseAddrPort(aReuseAddrPort);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetLinger(bool aPolarity, int16_t aTimeout) {
+ return mWrapped->SetLinger(aPolarity, aTimeout);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::GetQoSBits(uint8_t* aQoSBits) {
+ return mWrapped->GetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetQoSBits(uint8_t aQoSBits) {
+ return mWrapped->SetQoSBits(aQoSBits);
+}
+
+NS_IMETHODIMP
+SocketTransportShim::SetFastOpenCallback(TCPFastOpen* aFastOpen) {
+ return mWrapped->SetFastOpenCallback(aFastOpen);
+}
+
+NS_IMPL_ISUPPORTS(TLSFilterTransaction, nsITimerCallback, nsINamed)
+NS_IMPL_ISUPPORTS(SocketTransportShim, nsISocketTransport, nsITransport)
+NS_IMPL_ISUPPORTS(InputStreamShim, nsIInputStream, nsIAsyncInputStream)
+NS_IMPL_ISUPPORTS(OutputStreamShim, nsIOutputStream, nsIAsyncOutputStream)
+NS_IMPL_ISUPPORTS(SocketInWrapper, nsIAsyncInputStream)
+NS_IMPL_ISUPPORTS(SocketOutWrapper, nsIAsyncOutputStream)
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/TunnelUtils.h b/netwerk/protocol/http/TunnelUtils.h
new file mode 100644
index 0000000000..834d0888c3
--- /dev/null
+++ b/netwerk/protocol/http/TunnelUtils.h
@@ -0,0 +1,302 @@
+/* -*- 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_TLSFilterTransaction_h
+#define mozilla_net_TLSFilterTransaction_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsAHttpTransaction.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsINamed.h"
+#include "nsISocketTransport.h"
+#include "nsITimer.h"
+#include "NullHttpTransaction.h"
+#include "mozilla/TimeStamp.h"
+#include "prio.h"
+
+// a TLSFilterTransaction wraps another nsAHttpTransaction but
+// applies a encode/decode filter of TLS onto the ReadSegments
+// and WriteSegments data. It is not used for basic https://
+// but it is used for supplemental TLS tunnels - such as those
+// needed by CONNECT tunnels in HTTP/2 or even CONNECT tunnels when
+// the underlying proxy connection is already running TLS
+//
+// HTTP/2 CONNECT tunnels cannot use pushed IO layers because of
+// the multiplexing involved on the base stream. i.e. the base stream
+// once it is decrypted may have parts that are encrypted with a
+// variety of keys, or none at all
+
+/* ************************************************************************
+The input path of http over a spdy CONNECT tunnel once it is established as a
+stream
+
+note the "real http transaction" can be either a http/1 transaction or another
+spdy session inside the tunnel.
+
+ nsHttpConnection::OnInputStreamReady (real socket)
+ nsHttpConnection::OnSocketReadable()
+ SpdySession::WriteSegment()
+ SpdyStream::WriteSegment (tunnel stream)
+ SpdyConnectTransaction::WriteSegment
+ SpdyStream::OnWriteSegment(tunnel stream)
+ SpdySession::OnWriteSegment()
+ SpdySession::NetworkRead()
+ nsHttpConnection::OnWriteSegment (real socket)
+ realSocketIn->Read() return data from network
+
+now pop the stack back up to SpdyConnectTransaction::WriteSegment, the data
+that has been read is stored mInputData
+
+ SpdyConnectTransaction.mTunneledConn::OnInputStreamReady(mTunnelStreamIn)
+ SpdyConnectTransaction.mTunneledConn::OnSocketReadable()
+ TLSFilterTransaction::WriteSegment()
+ nsHttpTransaction::WriteSegment(real http transaction)
+ TLSFilterTransaction::OnWriteSegment() removes tls on way back up stack
+ SpdyConnectTransaction.mTunneledConn::OnWriteSegment()
+ // gets data from mInputData
+ SpdyConnectTransaction.mTunneledConn.mTunnelStreamIn->Read()
+
+The output path works similarly:
+ nsHttpConnection::OnOutputStreamReady (real socket)
+ nsHttpConnection::OnSocketWritable()
+ SpdySession::ReadSegments (locates tunnel)
+ SpdyStream::ReadSegments (tunnel stream)
+ SpdyConnectTransaction::ReadSegments()
+ SpdyConnectTransaction.mTunneledConn::OnOutputStreamReady (tunnel connection)
+ SpdyConnectTransaction.mTunneledConn::OnSocketWritable (tunnel connection)
+ TLSFilterTransaction::ReadSegment()
+ nsHttpTransaction::ReadSegment (real http transaction generates plaintext on
+ way down)
+ TLSFilterTransaction::OnReadSegment (BUF and LEN gets encrypted here on way
+ down)
+ SpdyConnectTransaction.mTunneledConn::OnReadSegment (BUF and LEN)
+ (tunnel connection)
+ SpdyConnectTransaction.mTunneledConn.mTunnelStreamOut->Write(BUF, LEN) ..
+ get stored in mOutputData
+
+Now pop the stack back up to SpdyConnectTransaction::ReadSegment(), where it has
+the encrypted text available in mOutputData
+
+ SpdyStream->OnReadSegment(BUF,LEN) from mOutputData. Tunnel stream
+ SpdySession->OnReadSegment() // encrypted data gets put in a data frame
+ nsHttpConnection->OnReadSegment()
+ realSocketOut->write() writes data to network
+
+**************************************************************************/
+
+struct PRSocketOptionData;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpRequestHead;
+class NullHttpTransaction;
+class TLSFilterTransaction;
+
+class NudgeTunnelCallback : public nsISupports {
+ public:
+ virtual nsresult OnTunnelNudged(TLSFilterTransaction*) = 0;
+};
+
+#define NS_DECL_NUDGETUNNELCALLBACK \
+ nsresult OnTunnelNudged(TLSFilterTransaction*) override;
+
+class TLSFilterTransaction final : public nsAHttpTransaction,
+ public nsAHttpSegmentReader,
+ public nsAHttpSegmentWriter,
+ public nsITimerCallback,
+ public nsINamed {
+ ~TLSFilterTransaction();
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_NSAHTTPSEGMENTREADER
+ NS_DECL_NSAHTTPSEGMENTWRITER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ TLSFilterTransaction(nsAHttpTransaction* aWrappedTransaction,
+ const char* tlsHost, int32_t tlsPort,
+ nsAHttpSegmentReader* reader,
+ nsAHttpSegmentWriter* writer);
+
+ const nsAHttpTransaction* Transaction() const { return mTransaction.get(); }
+ [[nodiscard]] nsresult CommitToSegmentSize(uint32_t size,
+ bool forceCommitment) override;
+ [[nodiscard]] nsresult GetTransactionSecurityInfo(nsISupports**) override;
+ [[nodiscard]] nsresult NudgeTunnel(NudgeTunnelCallback* callback);
+ [[nodiscard]] nsresult SetProxiedTransaction(
+ nsAHttpTransaction* aTrans,
+ nsAHttpTransaction* aSpdyConnectTransaction = nullptr);
+ void newIODriver(nsIAsyncInputStream* aSocketIn,
+ nsIAsyncOutputStream* aSocketOut,
+ nsIAsyncInputStream** outSocketIn,
+ nsIAsyncOutputStream** outSocketOut);
+
+ // nsAHttpTransaction overloads
+ bool IsNullTransaction() override;
+ NullHttpTransaction* QueryNullTransaction() override;
+ nsHttpTransaction* QueryHttpTransaction() override;
+ SpdyConnectTransaction* QuerySpdyConnectTransaction() override;
+ [[nodiscard]] nsresult WriteSegmentsAgain(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten,
+ bool* again) override;
+
+ bool HasDataToRecv();
+
+ private:
+ [[nodiscard]] nsresult StartTimerCallback();
+ void Cleanup();
+ int32_t FilterOutput(const char* aBuf, int32_t aAmount);
+ int32_t FilterInput(char* aBuf, int32_t aAmount);
+
+ static PRStatus GetPeerName(PRFileDesc* fd, PRNetAddr* addr);
+ static PRStatus GetSocketOption(PRFileDesc* fd, PRSocketOptionData* data);
+ static PRStatus SetSocketOption(PRFileDesc* fd,
+ const PRSocketOptionData* data);
+ static int32_t FilterWrite(PRFileDesc* fd, const void* buf, int32_t amount);
+ static int32_t FilterRead(PRFileDesc* fd, void* buf, int32_t amount);
+ static int32_t FilterSend(PRFileDesc* fd, const void* buf, int32_t amount,
+ int flags, PRIntervalTime timeout);
+ static int32_t FilterRecv(PRFileDesc* fd, void* buf, int32_t amount,
+ int flags, PRIntervalTime timeout);
+ static PRStatus FilterClose(PRFileDesc* fd);
+
+ private:
+ RefPtr<nsAHttpTransaction> mTransaction;
+ nsWeakPtr mWeakTrans; // SpdyConnectTransaction *
+ nsCOMPtr<nsISupports> mSecInfo;
+ nsCOMPtr<nsITimer> mTimer;
+ RefPtr<NudgeTunnelCallback> mNudgeCallback;
+
+ // buffered network output, after encryption
+ UniquePtr<char[]> mEncryptedText;
+ uint32_t mEncryptedTextUsed;
+ uint32_t mEncryptedTextSize;
+
+ PRFileDesc* mFD;
+ nsAHttpSegmentReader* mSegmentReader;
+ nsAHttpSegmentWriter* mSegmentWriter;
+
+ nsresult mFilterReadCode;
+ int32_t mFilterReadAmount;
+ // Set only when we are calling PR_Write from inside OnReadSegment. Prevents
+ // calling back to OnReadSegment from inside FilterOutput.
+ bool mInOnReadSegment;
+ bool mForce;
+ nsresult mReadSegmentReturnValue;
+ // Before Close() is called this is NS_ERROR_UNEXPECTED, in Close() we either
+ // take the reason, if it is a failure, or we change to
+ // NS_ERROR_BASE_STREAM_CLOSE. This is returned when Write/ReadSegments is
+ // called after Close, when we don't have mTransaction any more.
+ nsresult mCloseReason;
+ uint32_t mNudgeCounter;
+};
+
+class SocketTransportShim;
+class InputStreamShim;
+class OutputStreamShim;
+class nsHttpConnection;
+
+class SpdyConnectTransaction final : public NullHttpTransaction {
+ public:
+ SpdyConnectTransaction(nsHttpConnectionInfo* ci,
+ nsIInterfaceRequestor* callbacks, uint32_t caps,
+ nsHttpTransaction* trans, nsAHttpConnection* session,
+ bool isWebsocket);
+ ~SpdyConnectTransaction();
+
+ SpdyConnectTransaction* QuerySpdyConnectTransaction() override {
+ return this;
+ }
+
+ // A transaction 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();
+ // True if we successfully map stream to a nsHttpConnection. Currently we skip
+ // 1xx response only.
+ bool MapStreamToHttpConnection(nsISocketTransport* aTransport,
+ nsHttpConnectionInfo* aConnInfo,
+ const nsACString& aFlat407Headers,
+ int32_t aHttpResponseCode);
+
+ [[nodiscard]] nsresult ReadSegments(nsAHttpSegmentReader* reader,
+ uint32_t count,
+ uint32_t* countRead) final;
+ [[nodiscard]] nsresult WriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten) final;
+ nsHttpRequestHead* RequestHead() final;
+ void Close(nsresult reason) final;
+
+ // ConnectedReadyForInput() tests whether the spdy connect transaction is
+ // attached to an nsHttpConnection that can properly deal with flow control,
+ // etc..
+ bool ConnectedReadyForInput();
+
+ bool IsWebsocket() { return mIsWebsocket; }
+ void SetConnRefTaken();
+
+ private:
+ friend class SocketTransportShim;
+ friend class InputStreamShim;
+ friend class OutputStreamShim;
+
+ [[nodiscard]] nsresult Flush(uint32_t count, uint32_t* countRead);
+ void CreateShimError(nsresult code);
+
+ nsCString mConnectString;
+ uint32_t mConnectStringOffset;
+
+ nsAHttpConnection* mSession;
+ nsAHttpSegmentReader* mSegmentReader;
+
+ UniquePtr<char[]> mInputData;
+ uint32_t mInputDataSize;
+ uint32_t mInputDataUsed;
+ uint32_t mInputDataOffset;
+
+ UniquePtr<char[]> mOutputData;
+ uint32_t mOutputDataSize;
+ uint32_t mOutputDataUsed;
+ uint32_t mOutputDataOffset;
+
+ bool mForcePlainText;
+ TimeStamp mTimestampSyn;
+
+ // mTunneledConn, mTunnelTransport, mTunnelStreamIn, mTunnelStreamOut
+ // are the connectors to the "real" http connection. They are created
+ // together when the tunnel setup is complete and a static reference is held
+ // for the lifetime of the tunnel.
+ RefPtr<nsHttpConnection> mTunneledConn;
+ RefPtr<SocketTransportShim> mTunnelTransport;
+ RefPtr<InputStreamShim> mTunnelStreamIn;
+ RefPtr<OutputStreamShim> mTunnelStreamOut;
+ RefPtr<nsHttpTransaction> mDrivingTransaction;
+
+ // This is all for websocket support
+ bool mIsWebsocket;
+ bool mConnRefTaken;
+ nsCOMPtr<nsIAsyncOutputStream> mInputShimPipe;
+ nsCOMPtr<nsIAsyncInputStream> mOutputShimPipe;
+ nsresult WriteDataToBuffer(nsAHttpSegmentWriter* writer, uint32_t count,
+ uint32_t* countWritten);
+ [[nodiscard]] nsresult WebsocketWriteSegments(nsAHttpSegmentWriter* writer,
+ uint32_t count,
+ uint32_t* countWritten);
+
+ bool mCreateShimErrorCalled;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_net_TLSFilterTransaction_h
diff --git a/netwerk/protocol/http/WellKnownOpportunisticUtils.jsm b/netwerk/protocol/http/WellKnownOpportunisticUtils.jsm
new file mode 100644
index 0000000000..cf3c7e78ce
--- /dev/null
+++ b/netwerk/protocol/http/WellKnownOpportunisticUtils.jsm
@@ -0,0 +1,30 @@
+/* -*- 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 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;
+ },
+};
+
+var EXPORTED_SYMBOLS = ["WellKnownOpportunisticUtils"];
diff --git a/netwerk/protocol/http/components.conf b/netwerk/protocol/http/components.conf
new file mode 100644
index 0000000000..d7af273d16
--- /dev/null
+++ b/netwerk/protocol/http/components.conf
@@ -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/.
+
+Classes = [
+ {
+ 'cid': '{b4f96c89-5238-450c-8bda-e12c26f1d150}',
+ 'contract_ids': ['@mozilla.org/network/well-known-opportunistic-utils;1'],
+ 'jsm': 'resource://gre/modules/WellKnownOpportunisticUtils.jsm',
+ 'constructor': 'WellKnownOpportunisticUtils',
+ },
+]
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/moz.build b/netwerk/protocol/http/moz.build
new file mode 100644
index 0000000000..056feb224f
--- /dev/null
+++ b/netwerk/protocol/http/moz.build
@@ -0,0 +1,199 @@
+# -*- 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",
+ "nsIHttpActivityObserver.idl",
+ "nsIHttpAuthenticableChannel.idl",
+ "nsIHttpAuthenticator.idl",
+ "nsIHttpAuthManager.idl",
+ "nsIHttpChannel.idl",
+ "nsIHttpChannelAuthProvider.idl",
+ "nsIHttpChannelChild.idl",
+ "nsIHttpChannelInternal.idl",
+ "nsIHttpHeaderVisitor.idl",
+ "nsIHttpProtocolHandler.idl",
+ "nsIRaceCacheWithNetwork.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",
+ "ClassifierDummyChannel.h",
+ "ClassifierDummyChannelChild.h",
+ "ClassifierDummyChannelParent.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",
+ "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",
+ "CacheControlParser.cpp",
+ "CachePushChecker.cpp",
+ "ClassifierDummyChannel.cpp",
+ "ClassifierDummyChannelChild.cpp",
+ "ClassifierDummyChannelParent.cpp",
+ "ConnectionDiagnostics.cpp",
+ "ConnectionEntry.cpp",
+ "ConnectionHandle.cpp",
+ "DelayHttpChannelQueue.cpp",
+ "HalfOpenSocket.cpp",
+ "Http2Compression.cpp",
+ "Http2Push.cpp",
+ "Http2Session.cpp",
+ "Http2Stream.cpp",
+ "Http3Session.cpp",
+ "Http3Stream.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",
+ "InterceptedChannel.cpp",
+ "InterceptedHttpChannel.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",
+ "ParentChannelListener.cpp",
+ "PendingTransactionInfo.cpp",
+ "PendingTransactionQueue.cpp",
+ "QuicSocketControl.cpp",
+ "SpeculativeTransaction.cpp",
+ "TRRServiceChannel.cpp",
+ "TunnelUtils.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",
+ "PClassifierDummyChannel.ipdl",
+ "PHttpBackgroundChannel.ipdl",
+ "PHttpChannel.ipdl",
+ "PHttpConnectionMgr.ipdl",
+ "PHttpTransaction.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/extensions/auth",
+ "/netwerk/base",
+ "/netwerk/cookie",
+ "/netwerk/dns",
+ "/netwerk/ipc",
+ "/netwerk/socket/neqo_glue",
+ "/netwerk/url-classifier",
+]
+
+EXTRA_JS_MODULES += [
+ "WellKnownOpportunisticUtils.jsm",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+if CONFIG["OS_TARGET"] == "Darwin":
+ if not CONFIG["HOST_MAJOR_VERSION"]:
+ DEFINES["HAS_CONNECTX"] = True
+ elif CONFIG["HOST_MAJOR_VERSION"] >= "15":
+ DEFINES["HAS_CONNECTX"] = True
+
+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..49ca1f8d95
--- /dev/null
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -0,0 +1,248 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsHttp.h"
+#include "nsISupports.h"
+#include "nsAHttpTransaction.h"
+#include "HttpTrafficAnalyzer.h"
+
+class nsISocketTransport;
+class nsIAsyncInputStream;
+class nsIAsyncOutputStream;
+
+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;
+
+ // called by a transaction to get the security info from the socket.
+ virtual void GetSecurityInfo(nsISupports**) = 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 TopLevelOuterContentWindowIdChanged(uint64_t windowId) = 0;
+
+ // categories set by nsHttpTransaction to identify how this connection is
+ // being used.
+ virtual void SetTrafficCategory(HttpTrafficCategory) = 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; \
+ 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 TopLevelOuterContentWindowIdChanged(uint64_t windowId) 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 GetSecurityInfo(nsISupports** result) override { \
+ if (!(fwdObject)) { \
+ *result = nullptr; \
+ return; \
+ } \
+ return (fwdObject)->GetSecurityInfo(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); \
+ }
+
+// 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..36992e2b98
--- /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 "nsISupports.h"
+#include "nsTArray.h"
+#include "nsWeakReference.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 nsISVCBRecord;
+class nsITransport;
+class nsIRequestContext;
+
+namespace mozilla {
+namespace net {
+
+class nsAHttpConnection;
+class nsAHttpSegmentReader;
+class nsAHttpSegmentWriter;
+class nsHttpTransaction;
+class nsHttpRequestHead;
+class nsHttpConnectionInfo;
+class NullHttpTransaction;
+class SpdyConnectTransaction;
+
+//----------------------------------------------------------------------------
+// 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; }
+
+ // If we used rtti this would be the result of doing
+ // dynamic_cast<SpdyConnectTransaction *>(this).. i.e. it can be nullptr for
+ // other types
+ virtual SpdyConnectTransaction* QuerySpdyConnectTransaction() {
+ 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 security info is part of the connection, but sometimes
+ // in the case of TLS tunneled within TLS the transaction might present
+ // a more specific security info 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 GetTransactionSecurityInfo(nsISupports**) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ virtual void DisableSpdy() {}
+ virtual void DisableHttp3() {}
+ virtual void MakeNonSticky() {}
+ virtual void ReuseConnectionOnRestartOK(bool) {}
+
+ // 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() {}
+
+ // Returns true if early-data or fast open is possible.
+ [[nodiscard]] virtual bool CanDo0RTT() { return false; }
+ // 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;
+ }
+
+ [[nodiscard]] virtual nsresult RestartOnFastOpenError() {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ virtual uint64_t TopLevelOuterContentWindowId() {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+
+ virtual void SetFastOpenStatus(uint8_t aStatus) {}
+
+ 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..2912bacee2
--- /dev/null
+++ b/netwerk/protocol/http/nsCORSListenerProxy.cpp
@@ -0,0 +1,1599 @@
+/* -*- 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/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 "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/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 <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 void LogBlockedRequest(nsIRequest* aRequest, const char* aProperty,
+ const char16_t* aParam, uint32_t aBlockingReason,
+ nsIHttpChannel* aCreatingChannel) {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+
+ 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);
+ 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);
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Preflight cache
+
+class nsPreflightCache {
+ public:
+ struct TokenTime {
+ nsCString token;
+ TimeStamp expirationTime;
+ };
+
+ struct CacheEntry : public LinkedListElement<CacheEntry> {
+ explicit CacheEntry(nsCString& aKey) : mKey(aKey) {
+ MOZ_COUNT_CTOR(nsPreflightCache::CacheEntry);
+ }
+
+ ~CacheEntry() { MOZ_COUNT_DTOR(nsPreflightCache::CacheEntry); }
+
+ void PurgeExpired(TimeStamp now);
+ bool CheckRequest(const nsCString& aMethod,
+ const nsTArray<nsCString>& aCustomHeaders);
+
+ nsCString mKey;
+ 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 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::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.
+ CacheEntry* newEntry = new CacheEntry(key);
+ if (!newEntry) {
+ NS_WARNING("Failed to allocate new cache entry!");
+ return nullptr;
+ }
+
+ 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!");
+ }
+ }
+
+ mTable.Put(key, newEntry);
+ mList.insertFront(newEntry);
+
+ return newEntry;
+}
+
+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;
+}
+
+nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter,
+ nsIPrincipal* aRequestingPrincipal,
+ bool aWithCredentials)
+ : mOuterListener(aOuter),
+ mRequestingPrincipal(aRequestingPrincipal),
+ mOriginHeaderPrincipal(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() : mHeaderCount(0) {}
+
+ 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;
+
+ ~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, "CORSDidNotSucceed", 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, "CORSDidNotSucceed", nullptr,
+ nsILoadInfo::BLOCKING_REASON_CORSDIDNOTSUCCEED,
+ topChannel);
+ }
+ return status;
+ }
+
+ // Test that things worked on a HTTP level
+ nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
+ if (!http) {
+ 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;
+ }
+ if (loadInfo->GetBypassCORSChecks()) {
+ // This flag gets set if a WebExtention redirects a channel
+ // @onBeforeRequest. At this point no request has been made so we don't have
+ // the "Access-Control-Allow-Origin" header yet and the redirect would fail.
+ // So we're skipping the CORS check in that case.
+ 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)) {
+ LogBlockedRequest(aRequest, "CORSMissingAllowOrigin", nullptr,
+ 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(!nsContentUtils::IsExpandedPrincipal(mOriginHeaderPrincipal));
+ nsAutoCString origin;
+ mOriginHeaderPrincipal->GetAsciiOrigin(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);
+}
+
+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 {
+ // 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_SUCCEEDED(rv)) {
+ bool equal;
+ rv = oldChannelPrincipal->Equals(newChannelPrincipal, &equal);
+ if (NS_SUCCEEDED(rv) && !equal) {
+ // Spec says to set our source origin to a unique origin.
+ mOriginHeaderPrincipal =
+ NullPrincipal::CreateWithInheritedAttributes(oldChannelPrincipal);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ aOldChannel->Cancel(rv);
+ return rv;
+ }
+ }
+
+ 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();
+
+ // 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->SetCorsMode(nsIHttpChannelInternal::CORS_MODE_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 (!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;
+
+ nsCString 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->GetAsciiOrigin(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(nsDependentCString(net::nsHttp::Origin), 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;
+ 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) {
+ 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) {
+ LogBlockedRequest(aChannel, "CORSDidNotSucceed", nullptr,
+ 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) {
+ LogBlockedRequest(aRequest, "CORSPreflightDidNotSucceed2", nullptr,
+ 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 allowAllHeaders = 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) {
+ allowAllHeaders = true;
+ } else {
+ headers.AppendElement(header);
+ }
+ }
+
+ if (!allowAllHeaders) {
+ for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) {
+ const auto& comparator = nsCaseInsensitiveCStringArrayComparator();
+ if (!headers.Contains(mPreflightHeaders[i], comparator)) {
+ LogBlockedRequest(
+ aRequest, "CORSMissingAllowHeaderFromPreflight2",
+ NS_ConvertUTF8toUTF16(mPreflightHeaders[i]).get(),
+ nsILoadInfo::BLOCKING_REASON_CORSMISSINGALLOWHEADERFROMPREFLIGHT,
+ parentHttpChannel);
+ return NS_ERROR_DOM_BAD_URI;
+ }
+ }
+ }
+
+ 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;
+
+ // Disable cache if devtools says so.
+ bool disableCache = Preferences::GetBool("devtools.cache.disabled");
+
+ 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);
+
+ nsLoadFlags loadFlags;
+ rv = aRequestChannel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 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;
+
+ 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) {
+ 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;
+ }
+
+ // 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
+ nsIScriptError::errorFlag,
+ aCategory, aInnerWindowID);
+ } else {
+ nsCString category = PromiseFlatCString(aCategory);
+ rv = scriptError->Init(aMessage,
+ u""_ns, // sourceName
+ u""_ns, // sourceLine
+ 0, // lineNumber
+ 0, // columnNumber
+ nsIScriptError::errorFlag, category.get(),
+ 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..ea74f470e5
--- /dev/null
+++ b/netwerk/protocol/http/nsCORSListenerProxy.h
@@ -0,0 +1,127 @@
+/* -*- 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 nsIStreamListener,
+ 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();
+
+ [[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);
+
+ 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>& aACUnsafeHeaders, 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;
+ // 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;
+};
+
+#endif
diff --git a/netwerk/protocol/http/nsHttp.cpp b/netwerk/protocol/http/nsHttp.cpp
new file mode 100644
index 0000000000..c8346ef459
--- /dev/null
+++ b/netwerk/protocol/http/nsHttp.cpp
@@ -0,0 +1,1045 @@
+/* -*- 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/Mutex.h"
+#include "mozilla/HashFunctions.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 "nsJSUtils.h"
+#include <errno.h>
+#include <functional>
+
+namespace mozilla {
+namespace net {
+
+const uint32_t kHttp3VersionCount = 4;
+const nsCString kHttp3Versions[] = {"h3-27"_ns, "h3-28"_ns, "h3-29"_ns,
+ "h3-30"_ns};
+
+// define storage for all atoms
+namespace nsHttp {
+#define HTTP_ATOM(_name, _value) nsHttpAtom _name(_value);
+#include "nsHttpAtomList.h"
+#undef HTTP_ATOM
+} // namespace nsHttp
+
+// find out how many atoms we have
+#define HTTP_ATOM(_name, _value) Unused_##_name,
+enum {
+#include "nsHttpAtomList.h"
+ NUM_HTTP_ATOMS
+};
+#undef HTTP_ATOM
+
+// we keep a linked list of atoms allocated on the heap for easy clean up when
+// the atom table is destroyed. The structure and value string are allocated
+// as one contiguous block.
+
+struct HttpHeapAtom {
+ struct HttpHeapAtom* next;
+ char value[1];
+};
+
+static PLDHashTable* sAtomTable;
+static struct HttpHeapAtom* sHeapAtoms = nullptr;
+static Mutex* sLock = nullptr;
+
+HttpHeapAtom* NewHeapAtom(const char* value) {
+ int len = strlen(value);
+
+ HttpHeapAtom* a = reinterpret_cast<HttpHeapAtom*>(malloc(sizeof(*a) + len));
+ if (!a) return nullptr;
+ memcpy(a->value, value, len + 1);
+
+ // add this heap atom to the list of all heap atoms
+ a->next = sHeapAtoms;
+ sHeapAtoms = a;
+
+ return a;
+}
+
+// Hash string ignore case, based on PL_HashString
+static PLDHashNumber StringHash(const void* key) {
+ PLDHashNumber h = 0;
+ for (const char* s = reinterpret_cast<const char*>(key); *s; ++s)
+ h = AddToHash(h, nsCRT::ToLower(*s));
+ return h;
+}
+
+static bool StringCompare(const PLDHashEntryHdr* entry, const void* testKey) {
+ const void* entryKey = reinterpret_cast<const PLDHashEntryStub*>(entry)->key;
+
+ return PL_strcasecmp(reinterpret_cast<const char*>(entryKey),
+ reinterpret_cast<const char*>(testKey)) == 0;
+}
+
+static const PLDHashTableOps ops = {StringHash, StringCompare,
+ PLDHashTable::MoveEntryStub,
+ PLDHashTable::ClearEntryStub, nullptr};
+
+// We put the atoms in a hash table for speedy lookup.. see ResolveAtom.
+namespace nsHttp {
+nsresult CreateAtomTable() {
+ MOZ_ASSERT(!sAtomTable, "atom table already initialized");
+
+ if (!sLock) {
+ sLock = new Mutex("nsHttp.sLock");
+ }
+
+ // The initial length for this table is a value greater than the number of
+ // known atoms (NUM_HTTP_ATOMS) because we expect to encounter a few random
+ // headers right off the bat.
+ sAtomTable =
+ new PLDHashTable(&ops, sizeof(PLDHashEntryStub), NUM_HTTP_ATOMS + 10);
+
+ // fill the table with our known atoms
+ const char* const atoms[] = {
+#define HTTP_ATOM(_name, _value) _name._val,
+#include "nsHttpAtomList.h"
+#undef HTTP_ATOM
+ nullptr};
+
+ for (int i = 0; atoms[i]; ++i) {
+ auto stub =
+ static_cast<PLDHashEntryStub*>(sAtomTable->Add(atoms[i], fallible));
+ if (!stub) return NS_ERROR_OUT_OF_MEMORY;
+
+ MOZ_ASSERT(!stub->key, "duplicate static atom");
+ stub->key = atoms[i];
+ }
+
+ return NS_OK;
+}
+
+void DestroyAtomTable() {
+ delete sAtomTable;
+ sAtomTable = nullptr;
+
+ while (sHeapAtoms) {
+ HttpHeapAtom* next = sHeapAtoms->next;
+ free(sHeapAtoms);
+ sHeapAtoms = next;
+ }
+
+ delete sLock;
+ sLock = nullptr;
+}
+
+Mutex* GetLock() { return sLock; }
+
+// this function may be called from multiple threads
+nsHttpAtom ResolveAtom(const char* str) {
+ nsHttpAtom atom;
+
+ if (!str || !sAtomTable) return atom;
+
+ MutexAutoLock lock(*sLock);
+
+ auto stub = static_cast<PLDHashEntryStub*>(sAtomTable->Add(str, fallible));
+ if (!stub) return atom; // out of memory
+
+ if (stub->key) {
+ atom._val = reinterpret_cast<const char*>(stub->key);
+ return atom;
+ }
+
+ // if the atom could not be found in the atom table, then we'll go
+ // and allocate a new atom on the heap.
+ HttpHeapAtom* heapAtom = NewHeapAtom(str);
+ if (!heapAtom) return atom; // out of memory
+
+ stub->key = atom._val = heapAtom->value;
+ return atom;
+}
+
+//
+// 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 (PL_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 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) && !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 const 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) {
+ if (chr < 33 || chr == 127 || chr == '(' || chr == ')' || chr == '<' ||
+ chr == '>' || chr == '@' || chr == ',' || chr == ';' || chr == ':' ||
+ chr == '"' || chr == '/' || chr == '[' || chr == ']' || chr == '?' ||
+ chr == '=' || chr == '{' || chr == '}' || chr == '\\') {
+ return false;
+ }
+ return true;
+}
+
+ParsedHeaderPair::ParsedHeaderPair(const char* name, int32_t nameLen,
+ const char* val, int32_t valLen,
+ bool isQuotedValue)
+ : mName(nsDependentCSubstring(nullptr, 0u)),
+ mValue(nsDependentCSubstring(nullptr, 0u)),
+ 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);
+}
+
+void LogCallingScriptLocation(void* instance) {
+ if (!LOG4_ENABLED()) {
+ return;
+ }
+
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ if (!cx) {
+ return;
+ }
+
+ nsAutoCString fileNameString;
+ uint32_t line = 0, col = 0;
+ if (!nsJSUtils::GetCallingLocation(cx, fileNameString, &line, &col)) {
+ return;
+ }
+
+ LOG(("%p called from script: %s:%u:%u", instance, fileNameString.get(), line,
+ col));
+}
+
+void LogHeaders(const char* lineStart) {
+ nsAutoCString buf;
+ char* endOfLine;
+ while ((endOfLine = PL_strstr(lineStart, "\r\n"))) {
+ buf.Assign(lineStart, endOfLine - lineStart);
+ if (StaticPrefs::network_http_sanitize_headers_in_logs() &&
+ (PL_strcasestr(buf.get(), "authorization: ") ||
+ PL_strcasestr(buf.get(), "proxy-authorization: "))) {
+ char* p = PL_strchr(buf.get(), ' ');
+ 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;
+}
+
+Tuple<nsCString, bool> SelectAlpnFromAlpnList(
+ const nsTArray<nsCString>& aAlpnList, bool aNoHttp2, bool aNoHttp3) {
+ nsCString h3Value;
+ nsCString h2Value;
+ nsCString h1Value;
+ for (const auto& npnToken : aAlpnList) {
+ bool isHttp3 = gHttpHandler->IsHttp3VersionSupported(npnToken);
+ if (isHttp3 && h3Value.IsEmpty()) {
+ h3Value.Assign(npnToken);
+ }
+
+ uint32_t spdyIndex;
+ SpdyInformation* spdyInfo = gHttpHandler->SpdyInfo();
+ if (NS_SUCCEEDED(spdyInfo->GetNPNIndex(npnToken, &spdyIndex)) &&
+ spdyInfo->ProtocolEnabled(spdyIndex) && h2Value.IsEmpty()) {
+ h2Value.Assign(npnToken);
+ }
+
+ if (npnToken.LowerCaseEqualsASCII("http/1.1") && h1Value.IsEmpty()) {
+ h1Value.Assign(npnToken);
+ }
+ }
+
+ if (!h3Value.IsEmpty() && gHttpHandler->IsHttp3Enabled() && !aNoHttp3) {
+ return MakeTuple(h3Value, true);
+ }
+
+ if (!h2Value.IsEmpty() && gHttpHandler->IsSpdyEnabled() && !aNoHttp2) {
+ return MakeTuple(h2Value, false);
+ }
+
+ if (!h1Value.IsEmpty()) {
+ return MakeTuple(h1Value, false);
+ }
+
+ // If we are here, there is no supported alpn can be used.
+ return MakeTuple(EmptyCString(), false);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h
new file mode 100644
index 0000000000..182e8df9d2
--- /dev/null
+++ b/netwerk/protocol/http/nsHttp.h
@@ -0,0 +1,388 @@
+/* -*- 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/TimeStamp.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/UniquePtr.h"
+
+class nsICacheEntry;
+
+namespace mozilla {
+
+class Mutex;
+
+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 };
+
+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))
+
+// The connection could bring the peeked data for sniffing
+#define NS_HTTP_CALL_CONTENT_SNIFFER (1 << 21)
+
+// 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 << 22)
+
+// Force a transaction to stay in pending queue until the HTTPSSVC record is
+// available.
+#define NS_HTTP_WAIT_HTTPSSVC_RESULT (1 << 23)
+
+#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 {
+ nsHttpAtom() : _val(nullptr){};
+ explicit nsHttpAtom(const char* val) : _val(val) {}
+ nsHttpAtom(const nsHttpAtom& other) = default;
+
+ operator const char*() const { return _val; }
+ const char* get() const { return _val; }
+
+ void operator=(const char* v) { _val = v; }
+ void operator=(const nsHttpAtom& a) { _val = a._val; }
+
+ // private
+ const char* _val;
+};
+
+namespace nsHttp {
+[[nodiscard]] nsresult CreateAtomTable();
+void DestroyAtomTable();
+
+// The mutex is valid any time the Atom Table is valid
+// This mutex is used in the unusual case that the network thread and
+// main thread might access the same data
+Mutex* GetLock();
+
+// will dynamically add atoms to the table if they don't already exist
+nsHttpAtom ResolveAtom(const char*);
+inline nsHttpAtom ResolveAtom(const nsACString& s) {
+ return ResolveAtom(PromiseFlatCString(s).get());
+}
+
+// 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* separators);
+
+// 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 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 const GetLastActiveTabLoadOptimizationHit();
+void SetLastActiveTabLoadOptimizationHit(TimeStamp const& when);
+bool IsBeforeLastActiveTabLoadOptimization(TimeStamp const& when);
+
+// 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) extern nsHttpAtom _name;
+#include "nsHttpAtomList.h"
+#undef HTTP_ATOM
+
+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
+
+//-----------------------------------------------------------------------------
+// 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& txt,
+ 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);
+
+// Given a list of alpn-id, this function returns a supported alpn-id. If both
+// h3 and h2 are enabled, h3 alpn is preferred. This function returns a
+// Tuple<alpn-id, isHttp3>. The first element is the alpn-id and the second one
+// is a boolean to indicate if this alpn-id is for http3. If no supported
+// alpn-id is found, the first element would be a n empty string.
+Tuple<nsCString, bool> SelectAlpnFromAlpnList(
+ const nsTArray<nsCString>& aAlpnList, bool aNoHttp2, bool aNoHttp3);
+
+} // 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..702ecfa713
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpActivityDistributor.cpp
@@ -0,0 +1,198 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 {
+
+typedef nsMainThreadPtrHolder<nsIHttpActivityObserver> ObserverHolder;
+typedef nsMainThreadPtrHandle<nsIHttpActivityObserver> ObserverHandle;
+
+NS_IMPL_ISUPPORTS(nsHttpActivityDistributor, nsIHttpActivityDistributor,
+ nsIHttpActivityObserver)
+
+nsHttpActivityDistributor::nsHttpActivityDistributor()
+ : mLock("nsHttpActivityDistributor.mLock"), mActivated(false) {}
+
+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::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(
+ nsCOMPtr<nsISupports>(do_QueryObject(channel)), aActivityType,
+ aActivitySubtype, aTimestamp, aExtraSizeData, extraStringData);
+ }
+ };
+
+ if (!NS_IsMainThread()) {
+ return NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "net::nsHttpActivityDistributor::ObserveActivityWithArgs", task));
+ }
+
+ task();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpActivityDistributor::GetIsActive(bool* isActive) {
+ NS_ENSURE_ARG_POINTER(isActive);
+ MutexAutoLock lock(mLock);
+ if (XRE_IsSocketProcess()) {
+ *isActive = mActivated;
+ return NS_OK;
+ }
+
+ *isActive = !!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());
+
+ 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 (nsIOService::UseSocketProcess() && wasEmpty) {
+ 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());
+
+ ObserverHandle observer(
+ new ObserverHolder("nsIHttpActivityObserver", aObserver));
+
+ bool isEmpty = false;
+ {
+ MutexAutoLock lock(mLock);
+ if (!mObservers.RemoveElement(observer)) return NS_ERROR_FAILURE;
+ isEmpty = mObservers.IsEmpty();
+ }
+
+ if (nsIOService::UseSocketProcess() && isEmpty) {
+ auto task = []() {
+ SocketProcessParent* parent = SocketProcessParent::GetSingleton();
+ if (parent && parent->CanSend()) {
+ Unused << parent->SendOnHttpActivityDistributorActivated(false);
+ }
+ };
+ 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..81ff3bf251
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpActivityDistributor.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 nsHttpActivityDistributor_h__
+#define nsHttpActivityDistributor_h__
+
+#include "nsIHttpActivityObserver.h"
+#include "nsTArray.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Mutex.h"
+
+namespace mozilla {
+namespace net {
+
+class nsHttpActivityDistributor : public nsIHttpActivityDistributor {
+ public:
+ typedef nsTArray<nsMainThreadPtrHandle<nsIHttpActivityObserver> >
+ ObserverArray;
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIHTTPACTIVITYOBSERVER
+ NS_DECL_NSIHTTPACTIVITYDISTRIBUTOR
+
+ nsHttpActivityDistributor();
+
+ protected:
+ virtual ~nsHttpActivityDistributor() = default;
+
+ ObserverArray mObservers;
+ Mutex mLock;
+ bool mActivated;
+};
+
+} // 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..1619b7a94d
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAtomList.h
@@ -0,0 +1,104 @@
+/* -*- 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_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(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(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(Retry_After, "Retry-After")
+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_TCP_Fast_Open, "X-Firefox-TCP-Fast-Open")
+HTTP_ATOM(X_Firefox_Http3, "X-Firefox-Http3")
+
+// 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..6022a2ee48
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthCache.cpp
@@ -0,0 +1,462 @@
+/* -*- 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 char* scheme, const char* 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);
+}
+
+// return true if the two strings are equal or both empty. an empty string
+// is either null or zero length.
+static bool StrEquivalent(const char16_t* a, const char16_t* b) {
+ static const char16_t emptyStr[] = {0};
+
+ if (!a) a = emptyStr;
+ if (!b) b = emptyStr;
+
+ return nsCRT::strcmp(a, b) == 0;
+}
+
+//-----------------------------------------------------------------------------
+// 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 char* scheme,
+ const char* host, int32_t port,
+ const char* path,
+ nsACString const& originSuffix,
+ nsHttpAuthEntry** entry) {
+ LOG(("nsHttpAuthCache::GetAuthEntryForPath %p [path=%s]\n", this, path));
+
+ 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 char* scheme,
+ const char* host, int32_t port,
+ const char* realm,
+ nsACString const& originSuffix,
+ nsHttpAuthEntry** entry)
+
+{
+ LOG(("nsHttpAuthCache::GetAuthEntryForDomain %p [realm=%s]\n", this, realm));
+
+ 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 char* scheme, const char* host,
+ int32_t port, const char* path,
+ const char* realm, const char* creds,
+ const char* challenge,
+ nsACString const& originSuffix,
+ const nsHttpAuthIdentity* ident,
+ nsISupports* metadata) {
+ nsresult rv;
+
+ LOG(("nsHttpAuthCache::SetAuthEntry %p [realm=%s path=%s metadata=%p]\n",
+ this, realm, path, metadata));
+
+ nsAutoCString key;
+ nsHttpAuthNode* node = LookupAuthNode(scheme, host, port, originSuffix, key);
+
+ if (!node) {
+ // create a new entry node and set the given entry
+ node = new nsHttpAuthNode();
+ LOG((" new nsHttpAuthNode %p for key='%s'", node, key.get()));
+ rv = node->SetAuthEntry(path, realm, creds, challenge, ident, metadata);
+ if (NS_FAILED(rv))
+ delete node;
+ else
+ mDB.Put(key, node);
+ return rv;
+ }
+
+ return node->SetAuthEntry(path, realm, creds, challenge, ident, metadata);
+}
+
+void nsHttpAuthCache::ClearAuthEntry(const char* scheme, const char* host,
+ int32_t port, const char* 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 char* scheme,
+ const char* 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) {
+ for (auto iter = mDB.Iter(); !iter.Done(); iter.Next()) {
+ aValue.AppendElement(iter.Key());
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthIdentity
+//-----------------------------------------------------------------------------
+
+nsresult nsHttpAuthIdentity::Set(const char16_t* domain, const char16_t* user,
+ const char16_t* pass) {
+ char16_t *newUser, *newPass, *newDomain;
+
+ int domainLen = domain ? NS_strlen(domain) : 0;
+ int userLen = user ? NS_strlen(user) : 0;
+ int passLen = pass ? NS_strlen(pass) : 0;
+
+ int len = userLen + 1 + passLen + 1 + domainLen + 1;
+ newUser = (char16_t*)malloc(len * sizeof(char16_t));
+ if (!newUser) return NS_ERROR_OUT_OF_MEMORY;
+
+ if (user) memcpy(newUser, user, userLen * sizeof(char16_t));
+ newUser[userLen] = 0;
+
+ newPass = &newUser[userLen + 1];
+ if (pass) memcpy(newPass, pass, passLen * sizeof(char16_t));
+ newPass[passLen] = 0;
+
+ newDomain = &newPass[passLen + 1];
+ if (domain) memcpy(newDomain, domain, domainLen * sizeof(char16_t));
+ newDomain[domainLen] = 0;
+
+ // wait until the end to clear member vars in case input params
+ // reference our members!
+ if (mUser) free(mUser);
+ mUser = newUser;
+ mPass = newPass;
+ mDomain = newDomain;
+ return NS_OK;
+}
+
+void nsHttpAuthIdentity::Clear() {
+ if (mUser) {
+ free(mUser);
+ mUser = nullptr;
+ mPass = nullptr;
+ mDomain = nullptr;
+ }
+}
+
+bool nsHttpAuthIdentity::Equals(const nsHttpAuthIdentity& ident) const {
+ // we could probably optimize this with a single loop, but why bother?
+ return StrEquivalent(mUser, ident.mUser) &&
+ StrEquivalent(mPass, ident.mPass) &&
+ StrEquivalent(mDomain, ident.mDomain);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthEntry
+//-----------------------------------------------------------------------------
+
+nsHttpAuthEntry::~nsHttpAuthEntry() {
+ if (mRealm) free(mRealm);
+
+ while (mRoot) {
+ nsHttpAuthPath* ap = mRoot;
+ mRoot = mRoot->mNext;
+ free(ap);
+ }
+}
+
+nsresult nsHttpAuthEntry::AddPath(const char* aPath) {
+ // null path matches empty path
+ if (!aPath) aPath = "";
+
+ nsHttpAuthPath* tempPtr = mRoot;
+ while (tempPtr) {
+ const char* curpath = tempPtr->mPath;
+ if (strncmp(aPath, curpath, strlen(curpath)) == 0)
+ return NS_OK; // subpath already exists in the list
+
+ tempPtr = tempPtr->mNext;
+ }
+
+ // Append the aPath
+ nsHttpAuthPath* newAuthPath;
+ int newpathLen = strlen(aPath);
+ newAuthPath = (nsHttpAuthPath*)malloc(sizeof(nsHttpAuthPath) + newpathLen);
+ if (!newAuthPath) return NS_ERROR_OUT_OF_MEMORY;
+
+ memcpy(newAuthPath->mPath, aPath, newpathLen + 1);
+ newAuthPath->mNext = nullptr;
+
+ if (!mRoot)
+ mRoot = newAuthPath; // first entry
+ else
+ mTail->mNext = newAuthPath; // Append newAuthPath
+
+ // update the tail pointer.
+ mTail = newAuthPath;
+ return NS_OK;
+}
+
+nsresult nsHttpAuthEntry::Set(const char* path, const char* realm,
+ const char* creds, const char* chall,
+ const nsHttpAuthIdentity* ident,
+ nsISupports* metadata) {
+ char *newRealm, *newCreds, *newChall;
+
+ int realmLen = realm ? strlen(realm) : 0;
+ int credsLen = creds ? strlen(creds) : 0;
+ int challLen = chall ? strlen(chall) : 0;
+
+ int len = realmLen + 1 + credsLen + 1 + challLen + 1;
+ newRealm = (char*)malloc(len);
+ if (!newRealm) return NS_ERROR_OUT_OF_MEMORY;
+
+ if (realm) memcpy(newRealm, realm, realmLen);
+ newRealm[realmLen] = 0;
+
+ newCreds = &newRealm[realmLen + 1];
+ if (creds) memcpy(newCreds, creds, credsLen);
+ newCreds[credsLen] = 0;
+
+ newChall = &newCreds[credsLen + 1];
+ if (chall) memcpy(newChall, chall, challLen);
+ newChall[challLen] = 0;
+
+ nsresult rv = NS_OK;
+ if (ident) {
+ rv = mIdent.Set(*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.
+ rv = mIdent.Set(nullptr, nullptr, nullptr);
+ }
+ if (NS_FAILED(rv)) {
+ free(newRealm);
+ return rv;
+ }
+
+ rv = AddPath(path);
+ if (NS_FAILED(rv)) {
+ free(newRealm);
+ return rv;
+ }
+
+ // wait until the end to clear member vars in case input params
+ // reference our members!
+ if (mRealm) free(mRealm);
+
+ mRealm = newRealm;
+ mCreds = newCreds;
+ mChallenge = newChall;
+ 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 char* path) {
+ // null path matches empty path
+ if (!path) path = "";
+
+ // 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];
+ nsHttpAuthPath* authPath = entry->RootPath();
+ while (authPath) {
+ const char* entryPath = authPath->mPath;
+ // proxy auth entries have no path, so require exact match on
+ // empty path string.
+ if (entryPath[0] == '\0') {
+ if (path[0] == '\0') {
+ return entry.get();
+ }
+ } else if (strncmp(path, entryPath, strlen(entryPath)) == 0) {
+ return entry.get();
+ }
+
+ authPath = authPath->mNext;
+ }
+ }
+ return nullptr;
+}
+
+nsHttpAuthNode::EntryList::const_iterator nsHttpAuthNode::LookupEntryItrByRealm(
+ const char* realm) const {
+ // null realm matches empty realm
+ if (!realm) realm = "";
+
+ return std::find_if(mList.cbegin(), mList.cend(), [&realm](const auto& val) {
+ return strcmp(realm, val->Realm()) == 0;
+ });
+}
+
+nsHttpAuthEntry* nsHttpAuthNode::LookupEntryByRealm(const char* realm) {
+ auto itr = LookupEntryItrByRealm(realm);
+ if (itr != mList.cend()) {
+ return itr->get();
+ }
+
+ return nullptr;
+}
+
+nsresult nsHttpAuthNode::SetAuthEntry(const char* path, const char* realm,
+ const char* creds, const char* 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 char* 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..3834c08178
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthCache.h
@@ -0,0 +1,232 @@
+/* -*- 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 {
+
+struct nsHttpAuthPath {
+ struct nsHttpAuthPath* mNext;
+ char mPath[1];
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthIdentity
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthIdentity {
+ public:
+ nsHttpAuthIdentity() : mUser(nullptr), mPass(nullptr), mDomain(nullptr) {}
+ nsHttpAuthIdentity(const char16_t* domain, const char16_t* user,
+ const char16_t* password)
+ : mUser(nullptr), mPass{nullptr}, mDomain{nullptr} {
+ DebugOnly<nsresult> rv = Set(domain, user, password);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ ~nsHttpAuthIdentity() { Clear(); }
+
+ const char16_t* Domain() const { return mDomain; }
+ const char16_t* User() const { return mUser; }
+ const char16_t* Password() const { return mPass; }
+
+ [[nodiscard]] nsresult Set(const char16_t* domain, const char16_t* user,
+ const char16_t* password);
+ [[nodiscard]] nsresult Set(const nsHttpAuthIdentity& other) {
+ return Set(other.mDomain, other.mUser, other.mPass);
+ }
+ void Clear();
+
+ bool Equals(const nsHttpAuthIdentity& other) const;
+ bool IsEmpty() const { return !mUser; }
+
+ private:
+ // allocated as one contiguous blob, starting at mUser.
+ char16_t* mUser;
+ char16_t* mPass;
+ char16_t* mDomain;
+};
+
+//-----------------------------------------------------------------------------
+// nsHttpAuthEntry
+//-----------------------------------------------------------------------------
+
+class nsHttpAuthEntry {
+ public:
+ const char* Realm() const { return mRealm; }
+ const char* Creds() const { return mCreds; }
+ const char* Challenge() const { return mChallenge; }
+ const char16_t* Domain() const { return mIdent.Domain(); }
+ const char16_t* User() const { return mIdent.User(); }
+ const char16_t* Pass() const { return mIdent.Password(); }
+ nsHttpAuthPath* RootPath() { return mRoot; }
+
+ const nsHttpAuthIdentity& Identity() const { return mIdent; }
+
+ [[nodiscard]] nsresult AddPath(const char* aPath);
+
+ nsCOMPtr<nsISupports> mMetaData;
+
+ private:
+ nsHttpAuthEntry(const char* path, const char* realm, const char* creds,
+ const char* challenge, const nsHttpAuthIdentity* ident,
+ nsISupports* metadata)
+ : mRoot(nullptr),
+ mTail(nullptr),
+ mRealm(nullptr),
+ mCreds{nullptr},
+ mChallenge{nullptr} {
+ DebugOnly<nsresult> rv =
+ Set(path, realm, creds, challenge, ident, metadata);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ ~nsHttpAuthEntry();
+
+ [[nodiscard]] nsresult Set(const char* path, const char* realm,
+ const char* creds, const char* challenge,
+ const nsHttpAuthIdentity* ident,
+ nsISupports* metadata);
+
+ nsHttpAuthIdentity mIdent;
+
+ nsHttpAuthPath* mRoot; // root pointer
+ nsHttpAuthPath* mTail; // tail pointer
+
+ // allocated together in one blob, starting with mRealm.
+ char* mRealm;
+ char* mCreds;
+ char* 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 char* path);
+
+ // realm must not be null
+ nsHttpAuthEntry* LookupEntryByRealm(const char* realm);
+ EntryList::const_iterator LookupEntryItrByRealm(const char* realm) const;
+
+ // if a matching entry is found, then credentials will be changed.
+ [[nodiscard]] nsresult SetAuthEntry(const char* path, const char* realm,
+ const char* credentials,
+ const char* challenge,
+ const nsHttpAuthIdentity* ident,
+ nsISupports* metadata);
+
+ void ClearAuthEntry(const char* 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 char* scheme,
+ const char* host, int32_t port,
+ const char* 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 char* scheme,
+ const char* host, int32_t port,
+ const char* 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 char* scheme, const char* host, int32_t port, const char* directory,
+ const char* realm, const char* credentials, const char* challenge,
+ nsACString const& originSuffix, const nsHttpAuthIdentity* ident,
+ nsISupports* metadata);
+
+ void ClearAuthEntry(const char* scheme, const char* host, int32_t port,
+ const char* 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 char* scheme, const char* 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..a08c16139e
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpAuthManager.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/. */
+
+// 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)
+
+nsHttpAuthManager::nsHttpAuthManager()
+ : mAuthCache(nullptr), mPrivateAuthCache(nullptr) {}
+
+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(
+ PromiseFlatCString(aScheme).get(), PromiseFlatCString(aHost).get(),
+ aPort, PromiseFlatCString(aPath).get(), originSuffix, &entry);
+ else
+ rv = auth_cache->GetAuthEntryForDomain(
+ PromiseFlatCString(aScheme).get(), PromiseFlatCString(aHost).get(),
+ aPort, PromiseFlatCString(aRealm).get(), 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(PromiseFlatString(aUserDomain).get(),
+ PromiseFlatString(aUserName).get(),
+ PromiseFlatString(aUserPassword).get());
+
+ nsAutoCString originSuffix;
+ if (aPrincipal) {
+ aPrincipal->OriginAttributesRef().CreateSuffix(originSuffix);
+ }
+
+ nsHttpAuthCache* auth_cache = aIsPrivate ? mPrivateAuthCache : mAuthCache;
+ return auth_cache->SetAuthEntry(
+ PromiseFlatCString(aScheme).get(), PromiseFlatCString(aHost).get(), aPort,
+ PromiseFlatCString(aPath).get(), PromiseFlatCString(aRealm).get(),
+ nullptr, // credentials
+ nullptr, // 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..d28bacbef8
--- /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();
+ [[nodiscard]] nsresult Init();
+
+ protected:
+ virtual ~nsHttpAuthManager() = default;
+
+ nsHttpAuthCache* mAuthCache;
+ nsHttpAuthCache* mPrivateAuthCache;
+};
+
+} // 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..c2e631e6a5
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpBasicAuth.cpp
@@ -0,0 +1,103 @@
+/* -*- 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 "plstr.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 char* 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 char* challenge,
+ bool isProxyAuth, const char16_t* domain, const char16_t* username,
+ const char16_t* password, nsISupports* sessionState,
+ nsISupports* continuationState, nsICancelable** aCancellable) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpBasicAuth::GenerateCredentials(
+ nsIHttpAuthenticableChannel* authChannel, const char* challenge,
+ bool isProxyAuth, const char16_t* domain, const char16_t* user,
+ const char16_t* password, nsISupports** sessionState,
+ nsISupports** continuationState, uint32_t* aFlags, char** creds)
+
+{
+ LOG(("nsHttpBasicAuth::GenerateCredentials [challenge=%s]\n", challenge));
+
+ NS_ENSURE_ARG_POINTER(creds);
+
+ *aFlags = 0;
+
+ // we only know how to deal with Basic auth for http.
+ bool isBasicAuth = !PL_strncasecmp(challenge, "basic", 5);
+ NS_ENSURE_TRUE(isBasicAuth, NS_ERROR_UNEXPECTED);
+
+ // we work with UTF-8 around here
+ nsAutoCString userpass;
+ CopyUTF16toUTF8(mozilla::MakeStringSpan(user), userpass);
+ userpass.Append(':'); // always send a ':' (see bug 129565)
+ AppendUTF16toUTF8(mozilla::MakeStringSpan(password), userpass);
+
+ nsAutoCString authString{"Basic "_ns};
+ nsresult rv = Base64EncodeAppend(userpass, authString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *creds = ToNewCString(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..775af08373
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -0,0 +1,10154 @@
+/* -*- 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/MozPromiseInlines.h" // For MozPromise::FromDomPromise
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/nsCSPService.h"
+#include "mozilla/StoragePrincipalHelper.h"
+
+#include "nsHttp.h"
+#include "nsHttpChannel.h"
+#include "nsHttpChannelAuthProvider.h"
+#include "nsHttpHandler.h"
+#include "nsString.h"
+#include "nsIApplicationCacheContainer.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 "nsMimeTypes.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsIURL.h"
+#include "nsIStreamTransportService.h"
+#include "prnetdb.h"
+#include "nsEscape.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 "GeckoProfiler.h"
+#include "nsIConsoleService.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ContentBlocking.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.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/Telemetry.h"
+#include "AlternateServices.h"
+#include "InterceptedChannel.h"
+#include "nsIHttpPushListener.h"
+#include "nsIX509Cert.h"
+#include "ScopedNSSTypes.h"
+#include "nsIDeprecationWarner.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 "InterceptedHttpChannel.h"
+#include "../../cache2/CacheFileUtils.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsINetworkLinkService.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/dom/nsHTTPSOnlyStreamListener.h"
+#include "mozilla/net/AsyncUrlChannelClassifier.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/net/NeckoChannelParams.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"
+
+#ifdef MOZ_TASK_TRACER
+# include "GeckoTaskTracer.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;
+}
+
+bool IsInSubpathOfAppCacheManifest(nsIApplicationCache* cache,
+ nsACString const& uriSpec) {
+ MOZ_ASSERT(cache);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri, &rv));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsAutoCString directory;
+ rv = url->GetDirectory(directory);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> manifestURI;
+ rv = cache->GetManifestURI(getter_AddRefs(manifestURI));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURL> manifestURL(do_QueryInterface(manifestURI, &rv));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsAutoCString manifestDirectory;
+ rv = manifestURL->GetDirectory(manifestDirectory);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return StringBeginsWith(directory, manifestDirectory);
+}
+
+} // 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 AutoRedirectVetoNotifier {
+ public:
+ explicit AutoRedirectVetoNotifier(nsHttpChannel* channel)
+ : mChannel(channel) {
+ if (mChannel->LoadHasAutoRedirectVetoNotifier()) {
+ MOZ_CRASH("Nested AutoRedirectVetoNotifier on the stack");
+ mChannel = nullptr;
+ return;
+ }
+
+ mChannel->StoreHasAutoRedirectVetoNotifier(true);
+ }
+ ~AutoRedirectVetoNotifier() { ReportRedirectResult(false); }
+ void RedirectSucceeded() { ReportRedirectResult(true); }
+
+ private:
+ nsHttpChannel* mChannel;
+ void ReportRedirectResult(bool succeeded);
+};
+
+void AutoRedirectVetoNotifier::ReportRedirectResult(bool succeeded) {
+ if (!mChannel) return;
+
+ mChannel->mRedirectChannel = nullptr;
+
+ if (succeeded) {
+ mChannel->RemoveAsNonTailRequest();
+ }
+
+ nsCOMPtr<nsIRedirectResultListener> vetoHook;
+ NS_QueryNotificationCallbacks(mChannel, NS_GET_IID(nsIRedirectResultListener),
+ getter_AddRefs(vetoHook));
+
+ nsHttpChannel* channel = mChannel;
+ mChannel = nullptr;
+
+ if (vetoHook) vetoHook->OnRedirectResult(succeeded);
+
+ // Drop after the notification
+ channel->StoreHasAutoRedirectVetoNotifier(false);
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel <public>
+//-----------------------------------------------------------------------------
+
+nsHttpChannel::nsHttpChannel()
+ : HttpAsyncAborter<nsHttpChannel>(this),
+ mCacheDisposition(kCacheUnresolved),
+ mLogicalOffset(0),
+ mPostID(0),
+ mRequestTime(0),
+ mOfflineCacheLastModifiedTime(0),
+ mSuspendTotalTime(0),
+ mRedirectType(0),
+ mCacheOpenWithPriority(false),
+ mCacheQueueSizeWhenOpen(0),
+ mCachedContentIsValid(false),
+ mIsAuthChannel(false),
+ mAuthRetryPending(false),
+ mPushedStreamId(0),
+ mLocalBlocklist(false),
+ mOnTailUnblock(nullptr),
+ mWarningReporter(nullptr),
+ mIsReadingFromCache(false),
+ mFirstResponseSource(RESPONSE_PENDING),
+ mRaceCacheWithNetwork(false),
+ mRaceDelay(0),
+ mIgnoreCacheEntry(false),
+ mRCWNLock("nsHttpChannel.mRCWNLock"),
+ mProxyConnectResponseCode(0),
+ mDidReval(false) {
+ LOG(("Creating nsHttpChannel [this=%p]\n", this));
+ mChannelCreationTime = PR_Now();
+ mChannelCreationTimestamp = TimeStamp::Now();
+}
+
+nsHttpChannel::~nsHttpChannel() {
+ LOG(("Destroying nsHttpChannel [this=%p]\n", this));
+
+ 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(mApplicationCacheForWrite.forget());
+ arrayToRelease.AppendElement(mAuthProvider.forget());
+ arrayToRelease.AppendElement(mRedirectChannel.forget());
+ arrayToRelease.AppendElement(mPreflightChannel.forget());
+ arrayToRelease.AppendElement(mDNSPrefetch.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) {
+ nsresult rv = HttpBaseChannel::Init(uri, caps, proxyInfo, proxyResolveFlags,
+ proxyURI, channelId, aContentPolicyType);
+ 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) {
+ if (mWarningReporter) {
+ return mWarningReporter->LogBlockedCORSRequest(aMessage, aCategory);
+ }
+ 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));
+
+ AddCookiesToRequest();
+
+ // notify "http-on-modify-request" observers
+ CallOnModifyRequestObservers();
+
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (mSuspendCount) {
+ // We abandon the connection here if there was one.
+ LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
+ MOZ_ASSERT(!mCallOnResume);
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->HandleOnBeforeConnect();
+ return NS_OK;
+ };
+ return NS_OK;
+ }
+
+ return 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);
+}
+
+void nsHttpChannel::HandleOnBeforeConnect() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+ nsresult rv;
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->HandleOnBeforeConnect();
+ return NS_OK;
+ };
+ return;
+ }
+
+ LOG(("nsHttpChannel::HandleOnBeforeConnect [this=%p]\n", this));
+ rv = OnBeforeConnect();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(rv);
+ }
+}
+
+nsresult nsHttpChannel::OnBeforeConnect() {
+ nsresult rv;
+
+ // 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);
+ }
+
+ SecFetch::AddSecFetchHeader(this);
+
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ if (!mURI->SchemeIs("https")) {
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(resultPrincipal));
+ }
+
+ // 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,
+ mPrivateBrowsing, 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);
+ }
+
+ if (mHTTPSSVCRecord.isSome()) {
+ LOG((
+ "nsHttpChannel::MaybeUseHTTPSRRForUpgrade [%p] mHTTPSSVCRecord is some",
+ this));
+ StoreWaitHTTPSSVCRecord(false);
+ bool hasHTTPSRR = (mHTTPSSVCRecord.ref() != nullptr);
+ return ContinueOnBeforeConnect(hasHTTPSRR, aStatus);
+ }
+
+ LOG(("nsHttpChannel::MaybeUseHTTPSRRForUpgrade [%p] wait for HTTPS RR",
+ this));
+ StoreWaitHTTPSSVCRecord(true);
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::ContinueOnBeforeConnect(bool aShouldUpgrade,
+ nsresult aStatus) {
+ 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) {
+ 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") &&
+ gHttpHandler->IsH2WebsocketsEnabled()) {
+ // 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;
+ }
+
+ if (LoadIsTRRServiceChannel()) {
+ mCaps |= NS_HTTP_LARGE_KEEPALIVE;
+ }
+
+ mCaps |= NS_HTTP_TRR_FLAGS_FROM_MODE(nsIRequest::GetTRRMode());
+
+ // Finalize ConnectionInfo flags before SpeculativeConnect
+ mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
+ mConnectionInfo->SetPrivate(mPrivateBrowsing);
+ mConnectionInfo->SetIsolated(IsIsolated());
+ 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);
+
+ // notify "http-on-before-connect" observers
+ gHttpHandler->OnBeforeConnect(this);
+
+ // Check if request was cancelled during http-on-before-connect.
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (mSuspendCount) {
+ // We abandon the connection here if there was one.
+ LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
+ MOZ_ASSERT(!mCallOnResume);
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->OnBeforeConnectContinue();
+ return NS_OK;
+ };
+ return NS_OK;
+ }
+
+ return Connect();
+}
+
+void nsHttpChannel::OnBeforeConnectContinue() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+ nsresult rv;
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->OnBeforeConnectContinue();
+ return NS_OK;
+ };
+ return;
+ }
+
+ LOG(("nsHttpChannel::OnBeforeConnectContinue [this=%p]\n", this));
+ rv = Connect();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(rv);
+ }
+}
+
+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();
+ }
+
+ bool isTrackingResource = IsThirdPartyTrackingResource();
+ LOG(("nsHttpChannel %p tracking resource=%d, cos=%u", this,
+ isTrackingResource, mClassOfService));
+
+ 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) {
+ // If we have a fallback URI (and we're not already
+ // falling back), process the fallback asynchronously.
+ if (!LoadFallbackChannel() && !mFallbackKey.IsEmpty()) {
+ return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
+ }
+ 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) {
+ // If we have a fallback URI (and we're not already
+ // falling back), process the fallback asynchronously.
+ if (!LoadFallbackChannel() && !mFallbackKey.IsEmpty()) {
+ return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
+ }
+ 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...
+ return DoConnect();
+}
+
+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 on uses of the offline application cache,
+ // 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).
+ if (mApplicationCache || gIOService->IsOffline() ||
+ mUpgradeProtocolCallback || !(mCaps & NS_HTTP_ALLOW_KEEPALIVE))
+ return;
+
+ // LOAD_ONLY_FROM_CACHE and LOAD_NO_NETWORK_IO must not hit network.
+ // LOAD_FROM_CACHE and LOAD_CHECK_OFFLINE_CACHE are unlikely to hit network,
+ // so skip preconnects for them.
+ if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE |
+ LOAD_NO_NETWORK_IO | LOAD_CHECK_OFFLINE_CACHE))
+ 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),
+ gHttpHandler->UseHTTPSRRForSpeculativeConnection());
+}
+
+void nsHttpChannel::DoNotifyListenerCleanup() {
+ // We don't need this info anymore
+ CleanRedirectCacheChainIfNecessary();
+}
+
+void nsHttpChannel::ReleaseListeners() {
+ HttpBaseChannel::ReleaseListeners();
+ mChannelClassifier = nullptr;
+ mWarningReporter = 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);
+}
+
+void nsHttpChannel::HandleAsyncFallback() {
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async fallback [this=%p]\n", this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->HandleAsyncFallback();
+ return NS_OK;
+ };
+ return;
+ }
+
+ nsresult rv = NS_OK;
+
+ LOG(("nsHttpChannel::HandleAsyncFallback [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 fallback.
+ if (!mCanceled) {
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
+ bool waitingForRedirectCallback;
+ rv = ProcessFallback(&waitingForRedirectCallback);
+ if (waitingForRedirectCallback) return;
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
+ }
+
+ rv = ContinueHandleAsyncFallback(rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+nsresult nsHttpChannel::ContinueHandleAsyncFallback(nsresult rv) {
+ if (!mCanceled && (NS_FAILED(rv) || !LoadFallingBack())) {
+ // If ProcessFallback fails, then we have to send out the
+ // OnStart/OnStop notifications.
+ LOG(("ProcessFallback failed [rv=%" PRIx32 ", %d]\n",
+ static_cast<uint32_t>(rv), LoadFallingBack()));
+ mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
+ DoNotifyListener();
+ }
+
+ StoreIsPending(false);
+
+ if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+
+ return rv;
+}
+
+nsresult nsHttpChannel::SetupTransaction() {
+ LOG(("nsHttpChannel::SetupTransaction [this=%p, cos=%u, prio=%d]\n", this,
+ mClassOfService, 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;
+ }
+
+ // 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()) {
+ MOZ_ASSERT(gIOService->SocketProcessReady(),
+ "Socket process should be ready.");
+
+ 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);
+
+ SocketProcessParent* socketProcess = SocketProcessParent::GetSingleton();
+ if (socketProcess) {
+ Unused << 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 (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
+ mCaps |= NS_HTTP_CALL_CONTENT_SNIFFER;
+ }
+
+ 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;
+ }
+
+ 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;
+ };
+ }
+
+ EnsureTopLevelOuterContentWindowId();
+ 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());
+ };
+ }
+ rv = mTransaction->Init(
+ mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mReqContentLength,
+ LoadUploadStreamHasHeaders(), GetCurrentEventTarget(), callbacks, this,
+ mTopLevelOuterContentWindowId, 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 & 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 =
+ nsContentUtils::IsThirdPartyWindowOrChannel(nullptr, this, mURI);
+ 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");
+
+ 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;
+ }
+
+ // 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!
+ nsCOMPtr<nsIStreamConverterService> serv;
+ rv = gHttpHandler->GetStreamConverterService(getter_AddRefs(serv));
+ // If we failed, we just fall through to the "normal" case
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIStreamListener> converter;
+ rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE, "*/*", mListener,
+ nullptr, getter_AddRefs(converter));
+ if (NS_SUCCEEDED(rv)) {
+ mListener = converter;
+ unknownDecoderStarted = true;
+ }
+ }
+ }
+ }
+
+ // 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(
+ base::GetCurrentProcId(), request.mChildProcessId, &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(request.mChildProcessId)
+ ->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);
+ }
+ }
+
+ if (!mCanceled) {
+ // create offline cache entry if offline caching was requested
+ if (ShouldUpdateOfflineCacheEntry()) {
+ LOG(("writing to the offline cache"));
+ rv = InitOfflineCacheEntry();
+ if (NS_FAILED(rv)) return rv;
+
+ // InitOfflineCacheEntry may have closed mOfflineCacheEntry
+ if (mOfflineCacheEntry) {
+ rv = InstallOfflineCacheListener();
+ if (NS_FAILED(rv)) return rv;
+ }
+ } else if (mApplicationCacheForWrite) {
+ LOG(("offline cache is up to date, not updating"));
+ CloseOfflineCacheEntry();
+ }
+ }
+
+ 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_UNTRUSTWORTHY_CONNECTION:
+ consoleErrorTag = u"STSUntrustworthyConnection"_ns;
+ break;
+ 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 a single security header. Only one type is supported: HSTS
+ */
+nsresult nsHttpChannel::ProcessSingleSecurityHeader(
+ uint32_t aType, nsITransportSecurityInfo* aSecInfo, uint32_t aFlags) {
+ nsHttpAtom atom;
+ switch (aType) {
+ case nsISiteSecurityService::HEADER_HSTS:
+ atom = nsHttp::ResolveAtom("Strict-Transport-Security");
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid security header type");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString securityHeader;
+ nsresult rv = mResponseHead->GetHeader(atom, securityHeader);
+ if (NS_SUCCEEDED(rv)) {
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
+ // Process header will now discard the headers itself if the channel
+ // wasn't secure (whereas before it had to be checked manually)
+ OriginAttributes originAttributes;
+ if (NS_WARN_IF(!StoragePrincipalHelper::GetOriginAttributesForHSTS(
+ this, originAttributes))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t failureResult;
+ uint32_t headerSource = nsISiteSecurityService::SOURCE_ORGANIC_REQUEST;
+ rv = sss->ProcessHeader(aType, mURI, securityHeader, aSecInfo, aFlags,
+ headerSource, originAttributes, nullptr, nullptr,
+ &failureResult);
+ if (NS_FAILED(rv)) {
+ nsAutoString consoleErrorCategory;
+ nsAutoString consoleErrorTag;
+ switch (aType) {
+ case nsISiteSecurityService::HEADER_HSTS:
+ GetSTSConsoleErrorTag(failureResult, consoleErrorTag);
+ consoleErrorCategory = u"Invalid HSTS Headers"_ns;
+ break;
+ default:
+ return NS_ERROR_FAILURE;
+ }
+ Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
+ LOG(("nsHttpChannel: Failed to parse %s header, continuing load.\n",
+ atom.get()));
+ }
+ } else {
+ if (rv != NS_ERROR_NOT_AVAILABLE) {
+ // All other errors are fatal
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ LOG(("nsHttpChannel: No %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
+ PRNetAddr hostAddr;
+ if (PR_SUCCESS == PR_StringToNetAddr(asciiHost.get(), &hostAddr))
+ 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);
+
+ uint32_t flags =
+ NS_UsePrivateBrowsing(this) ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
+
+ // Get the TransportSecurityInfo
+ nsCOMPtr<nsITransportSecurityInfo> transSecInfo =
+ do_QueryInterface(mSecurityInfo);
+ NS_ENSURE_TRUE(transSecInfo, NS_ERROR_FAILURE);
+
+ rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HSTS,
+ transSecInfo, flags);
+ 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;
+
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo =
+ do_QueryInterface(mSecurityInfo);
+ if (!securityInfo) return;
+
+ uint32_t state;
+ if (securityInfo && NS_SUCCEEDED(securityInfo->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);
+ }
+ }
+
+ // Send (SHA-1) signature algorithm errors to the web console
+ nsCOMPtr<nsIX509Cert> cert;
+ securityInfo->GetServerCert(getter_AddRefs(cert));
+ if (cert) {
+ UniqueCERTCertificate nssCert(cert->GetCert());
+ if (nssCert) {
+ SECOidTag tag = SECOID_GetAlgorithmTag(&nssCert->signature);
+ LOG(("Checking certificate signature: The OID tag is %i [this=%p]\n", tag,
+ this));
+ // Check to see if the signature is sha-1 based.
+ // Not including checks for SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE
+ // from http://tools.ietf.org/html/rfc2437#section-8 since I
+ // can't see reference to it outside this spec
+ if (tag == SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION ||
+ tag == SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST ||
+ tag == SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE) {
+ nsString consoleErrorTag = u"SHA1Sig"_ns;
+ nsString consoleErrorMessage = u"SHA-1 Signature"_ns;
+ Unused << AddSecurityMessage(consoleErrorTag, consoleErrorMessage);
+ }
+ }
+ }
+
+ uint16_t tlsVersion;
+ nsresult rv = securityInfo->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 (!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, GetTopWindowOrigin(),
+ mPrivateBrowsing, IsIsolated(), 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 (PL_strstr(alt_service.get(), "h3-")) {
+ saw_quic = 1;
+ } else if (PL_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;
+
+ 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;
+ }
+
+ rv = ProcessCrossOriginResourcePolicyHeader();
+ if (NS_FAILED(rv)) {
+ mStatus = NS_ERROR_DOM_CORP_FAILED;
+ HandleAsyncAbort();
+ return NS_OK;
+ }
+
+ rv = ComputeCrossOriginOpenerPolicyMismatch();
+ if (rv == NS_ERROR_BLOCKED_BY_POLICY) {
+ // this navigates the doc's browsing context to a network error.
+ mStatus = NS_ERROR_BLOCKED_BY_POLICY;
+ HandleAsyncAbort();
+ 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)) {
+ 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"));
+ }
+
+ rv = ProcessCrossOriginEmbedderPolicyHeader();
+ if (NS_FAILED(rv)) {
+ mStatus = NS_ERROR_BLOCKED_BY_POLICY;
+ HandleAsyncAbort();
+ return NS_OK;
+ }
+
+ // 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 creadentials as invalid.
+ mAuthProvider->ClearProxyIdent();
+ }
+ if (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 {
+ 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)) {
+ LOG(("CheckForSuperfluousAuth failed [rv=%x]\n",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ rv = ProcessNormal();
+ } else {
+ mIsAuthChannel = true;
+ mAuthRetryPending = true; // see DoAuthRetry
+ }
+ break;
+
+ case 425:
+ case 429:
+ // Do not cache 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;
+}
+
+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;
+
+ Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_VERSION,
+ static_cast<uint32_t>(mResponseHead->Version()));
+
+ 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);
+ }
+ }
+}
+
+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);
+
+ if (mApplicationCacheForWrite) {
+ // Store response in the offline cache
+ Unused << InitOfflineCacheEntry();
+ CloseOfflineCacheEntry();
+ }
+ 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() {
+ nsresult rv;
+
+ LOG(("nsHttpChannel::ProcessNormal [this=%p]\n", this));
+
+ bool succeeded;
+ rv = GetRequestSucceeded(&succeeded);
+ if (NS_SUCCEEDED(rv) && !succeeded) {
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
+ bool waitingForRedirectCallback;
+ Unused << ProcessFallback(&waitingForRedirectCallback);
+ if (waitingForRedirectCallback) {
+ // The transaction has been suspended by ProcessFallback.
+ return NS_OK;
+ }
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
+ }
+
+ 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;
+ }
+
+ if (LoadFallingBack()) {
+ // Do not continue with normal processing, fallback is in
+ // progress now.
+ return NS_OK;
+ }
+
+ // 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);
+
+ ClearBogusContentEncodingIfNeeded();
+
+ 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));
+ if (NS_FAILED(rv)) return rv;
+
+ // 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));
+ }
+ }
+}
+
+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);
+
+ /* 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);
+
+ // 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::ContinueDoReplaceWithProxy);
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
+
+ if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this);
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannel::ContinueDoReplaceWithProxy(nsresult rv) {
+ AutoRedirectVetoNotifier notifier(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);
+
+ // open new channel
+ rv = mRedirectChannel->AsyncOpen(mListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = NS_BINDING_REDIRECTED;
+
+ notifier.RedirectSucceeded();
+
+ ReleaseListeners();
+
+ 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);
+
+ // Make sure to clear bogus content-encodings before looking at the header
+ ClearBogusContentEncodingIfNeeded();
+
+ // 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 (PL_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;
+
+ if (mOfflineCacheEntry) {
+ rv = InstallOfflineCacheListener(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);
+ });
+}
+
+nsresult nsHttpChannel::ProcessFallback(bool* waitingForRedirectCallback) {
+ LOG(("nsHttpChannel::ProcessFallback [this=%p]\n", this));
+ nsresult rv;
+
+ *waitingForRedirectCallback = false;
+ StoreFallingBack(false);
+
+ // At this point a load has failed (either due to network problems
+ // or an error returned on the server). Perform an application
+ // cache fallback if we have a URI to fall back to.
+ if (!mApplicationCache || mFallbackKey.IsEmpty() || LoadFallbackChannel()) {
+ LOG((" choosing not to fallback [%p,%s,%d]", mApplicationCache.get(),
+ mFallbackKey.get(), LoadFallbackChannel()));
+ return NS_OK;
+ }
+
+ // Make sure the fallback entry hasn't been marked as a foreign
+ // entry.
+ uint32_t fallbackEntryType;
+ rv = mApplicationCache->GetTypes(mFallbackKey, &fallbackEntryType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fallbackEntryType & nsIApplicationCache::ITEM_FOREIGN) {
+ // This cache points to a fallback that refers to a different
+ // manifest. Refuse to fall back.
+ return NS_OK;
+ }
+
+ if (!IsInSubpathOfAppCacheManifest(mApplicationCache, mFallbackKey)) {
+ // Refuse to fallback if the fallback key is not contained in the same
+ // path as the cache manifest.
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(fallbackEntryType & nsIApplicationCache::ITEM_FALLBACK,
+ "Fallback entry not marked correctly!");
+
+ // Kill any offline cache entry, and disable offline caching for the
+ // fallback.
+ if (mOfflineCacheEntry) {
+ mOfflineCacheEntry->AsyncDoom(nullptr);
+ mOfflineCacheEntry = nullptr;
+ }
+
+ mApplicationCacheForWrite = nullptr;
+ mOfflineCacheEntry = nullptr;
+
+ // Close the current cache entry.
+ CloseCacheEntry(true);
+
+ // Create a new channel to load the fallback entry.
+ RefPtr<nsIChannel> newChannel;
+ rv = gHttpHandler->NewChannel(mURI, mLoadInfo, getter_AddRefs(newChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
+ rv = SetupReplacementChannel(mURI, newChannel, true, redirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make sure the new channel loads from the fallback key.
+ nsCOMPtr<nsIHttpChannelInternal> httpInternal =
+ do_QueryInterface(newChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = httpInternal->SetupFallbackChannel(mFallbackKey.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // ... and fallbacks should only load from the cache.
+ uint32_t newLoadFlags = mLoadFlags | LOAD_REPLACE | LOAD_ONLY_FROM_CACHE;
+ rv = newChannel->SetLoadFlags(newLoadFlags);
+
+ // Inform consumers about this fake redirect
+ mRedirectChannel = newChannel;
+
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
+
+ if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
+
+ if (NS_FAILED(rv)) {
+ AutoRedirectVetoNotifier notifier(this);
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
+ return rv;
+ }
+
+ // Indicate we are now waiting for the asynchronous redirect callback
+ // if all went OK.
+ *waitingForRedirectCallback = true;
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::ContinueProcessFallback(nsresult rv) {
+ AutoRedirectVetoNotifier notifier(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);
+
+ rv = mRedirectChannel->AsyncOpen(mListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
+ MaybeWarnAboutAppCache();
+ }
+
+ // close down this channel
+ Cancel(NS_BINDING_REDIRECTED);
+
+ notifier.RedirectSucceeded();
+
+ ReleaseListeners();
+
+ StoreFallingBack(true);
+
+ return NS_OK;
+}
+
+// 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);
+
+ StoreLoadedFromApplicationCache(false);
+
+ 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;
+ }
+
+ // Pick up an application cache from the notification
+ // callbacks if available and if we are not an intercepted channel.
+ if (!mApplicationCache && LoadInheritApplicationCache()) {
+ nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
+ GetCallback(appCacheContainer);
+
+ if (appCacheContainer) {
+ appCacheContainer->GetApplicationCache(getter_AddRefs(mApplicationCache));
+ }
+ }
+
+ return OpenCacheEntryInternal(isHttps, mApplicationCache, true);
+}
+
+bool nsHttpChannel::IsIsolated() {
+ if (LoadHasBeenIsolatedChecked()) {
+ return LoadIsIsolated();
+ }
+ StoreIsIsolated(
+ StaticPrefs::browser_cache_cache_isolation() ||
+ (IsThirdPartyTrackingResource() &&
+ !ContentBlocking::ShouldAllowAccessFor(this, mURI, nullptr)));
+ StoreHasBeenIsolatedChecked(true);
+ return LoadIsIsolated();
+}
+
+const nsCString& nsHttpChannel::GetTopWindowOrigin() {
+ if (LoadTopWindowOriginComputed()) {
+ return mTopWindowOrigin;
+ }
+
+ nsCOMPtr<nsIURI> topWindowURI;
+ nsresult rv = GetTopWindowURI(getter_AddRefs(topWindowURI));
+ bool isDocument = false;
+ if (NS_FAILED(rv) && NS_SUCCEEDED(GetIsMainDocumentChannel(&isDocument)) &&
+ isDocument) {
+ // For top-level documents, use the document channel's origin to compute
+ // the unique storage space identifier instead of the top Window URI.
+ rv = NS_GetFinalChannelURI(this, getter_AddRefs(topWindowURI));
+ NS_ENSURE_SUCCESS(rv, mTopWindowOrigin);
+ }
+
+ rv = nsContentUtils::GetASCIIOrigin(topWindowURI ? topWindowURI : mURI,
+ mTopWindowOrigin);
+ NS_ENSURE_SUCCESS(rv, mTopWindowOrigin);
+
+ StoreTopWindowOriginComputed(true);
+
+ return mTopWindowOrigin;
+}
+
+nsresult nsHttpChannel::OpenCacheEntryInternal(
+ bool isHttps, nsIApplicationCache* applicationCache,
+ bool allowApplicationCache) {
+ MOZ_ASSERT_IF(!allowApplicationCache, !applicationCache);
+
+ 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 CacheEntriesToWaitFor
+ AutoCacheWaitFlags waitFlags(this);
+
+ nsAutoCString cacheKey;
+
+ nsCOMPtr<nsICacheStorageService> cacheStorageService(
+ services::GetCacheStorageService());
+ if (!cacheStorageService) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ if (!mFallbackKey.IsEmpty() && LoadFallbackChannel()) {
+ // This is a fallback channel, open fallback URI instead
+ rv = NS_NewURI(getter_AddRefs(mCacheEntryURI), mFallbackKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mCacheEntryURI = mURI;
+ }
+
+ RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
+ if (!info) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t cacheEntryOpenFlags;
+ bool offline = gIOService->IsOffline();
+
+ bool maybeRCWN = false;
+
+ nsAutoCString cacheControlRequestHeader;
+ Unused << mRequestHead.GetHeader(nsHttp::Cache_Control,
+ cacheControlRequestHeader);
+ CacheControlParser cacheControlRequest(cacheControlRequestHeader);
+ if (cacheControlRequest.NoStore()) {
+ goto bypassCacheEntryOpen;
+ }
+
+ if (offline || (mLoadFlags & INHIBIT_CACHING)) {
+ if (BYPASS_LOCAL_CACHE(mLoadFlags, LoadPreferCacheLoadOverBypass()) &&
+ !offline) {
+ goto bypassCacheEntryOpen;
+ }
+ cacheEntryOpenFlags = nsICacheStorage::OPEN_READONLY;
+ StoreCacheEntryIsReadOnly(true);
+ } else if (BYPASS_LOCAL_CACHE(mLoadFlags, LoadPreferCacheLoadOverBypass()) &&
+ !applicationCache) {
+ 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 (!mPostID && applicationCache) {
+ rv = cacheStorageService->AppCacheStorage(info, applicationCache,
+ getter_AddRefs(cacheStorage));
+ } else 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 {
+ bool lookupAppCache = (LoadChooseApplicationCache() ||
+ (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE)) &&
+ !mPostID && MOZ_LIKELY(allowApplicationCache);
+ // Try to race only if we use disk cache storage and we don't lookup
+ // app cache first
+ maybeRCWN = (!lookupAppCache) && mRequestHead.IsSafeMethod();
+ rv = cacheStorageService->DiskCacheStorage(info, lookupAppCache,
+ getter_AddRefs(cacheStorage));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if ((mClassOfService & 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");
+ }
+
+ if (IsIsolated()) {
+ auto& topWindowOrigin = GetTopWindowOrigin();
+ if (topWindowOrigin.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCacheIdExtension.Append("-unique:");
+ mCacheIdExtension.Append(topWindowOrigin);
+ }
+
+ mCacheOpenWithPriority = cacheEntryOpenFlags & nsICacheStorage::OPEN_PRIORITY;
+ mCacheQueueSizeWhenOpen =
+ CacheStorageService::CacheQueueSize(mCacheOpenWithPriority);
+
+ if (StaticPrefs::network_http_rcwn_enabled() && maybeRCWN &&
+ !mApplicationCacheForWrite) {
+ 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
+ NS_NewTimerWithCallback(getter_AddRefs(mCacheOpenTimer), this,
+ mCacheOpenDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ waitFlags.Keep(WAIT_FOR_CACHE_ENTRY);
+
+bypassCacheEntryOpen:
+ if (!mApplicationCacheForWrite || !allowApplicationCache) return NS_OK;
+
+ // If there is an app cache to write to, open the entry right now in parallel.
+
+ // make sure we're not abusing this function
+ MOZ_ASSERT(!mOfflineCacheEntry, "cache entry already open");
+
+ if (offline) {
+ // only put things in the offline cache while online
+ return NS_OK;
+ }
+
+ if (mLoadFlags & INHIBIT_CACHING) {
+ // respect demand not to cache
+ return NS_OK;
+ }
+
+ if (!mRequestHead.IsGet()) {
+ // only cache complete documents offline
+ return NS_OK;
+ }
+
+ rv = cacheStorageService->AppCacheStorage(info, mApplicationCacheForWrite,
+ getter_AddRefs(cacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = cacheStorage->AsyncOpenURI(mURI, ""_ns, nsICacheStorage::OPEN_TRUNCATE,
+ this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ waitFlags.Keep(WAIT_FOR_OFFLINE_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,
+ nsIApplicationCache* appCache,
+ 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;
+ } else 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 or because
+ // we're updating the offline cache.
+ // Don't bother to validate if this is a fallback entry.
+ if (!mApplicationCacheForWrite &&
+ (appCache || (LoadCacheEntryIsReadOnly() &&
+ !(mLoadFlags & nsIRequest::INHIBIT_CACHING)))) {
+ if (!appCache) {
+ 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, !!appCache);
+ 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, !!appCache);
+ 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);
+
+ 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;
+ } else {
+ doValidation = nsHttp::ValidationRequired(
+ isForcedValid, mCachedResponseHead.get(), mLoadFlags,
+ LoadAllowStaleCacheContent(), 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));
+ }
+
+ // 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));
+
+ if (!mRedirectedCachekeys)
+ mRedirectedCachekeys = MakeUnique<nsTArray<nsCString>>();
+ else if (mRedirectedCachekeys->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) mRedirectedCachekeys->AppendElement(cacheKey);
+ }
+
+ mCachedContentIsValid = !doValidation;
+
+ 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 or marked immutable
+ //
+ // do not override conditional headers when consumer has defined its own
+ if (!mCachedResponseHead->NoStore() &&
+ (mRequestHead.IsGet() || mRequestHead.IsHead()) &&
+ !LoadCustomConditionalRequest() && !weaklyFramed && !isImmutable &&
+ (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, !!appCache);
+ 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,
+ nsIApplicationCache* aAppCache,
+ nsresult status) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ LOG(
+ ("nsHttpChannel::OnCacheEntryAvailable [this=%p entry=%p "
+ "new=%d appcache=%p status=%" PRIx32
+ " mAppCache=%p mAppCacheForWrite=%p]\n",
+ this, entry, aNew, aAppCache, static_cast<uint32_t>(status),
+ mApplicationCache.get(), mApplicationCacheForWrite.get()));
+
+ // if the channel's already fired onStopRequest, then we should ignore
+ // this event.
+ if (!LoadIsPending()) {
+ mCacheInputStream.CloseAndRelease();
+ return NS_OK;
+ }
+
+ rv = OnCacheEntryAvailableInternal(entry, aNew, aAppCache, 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, nsIApplicationCache* aAppCache,
+ 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;
+ }
+
+ if (aAppCache) {
+ if (mApplicationCache == aAppCache && !mCacheEntry) {
+ rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
+ } else if (mApplicationCacheForWrite == aAppCache && aNew &&
+ !mOfflineCacheEntry) {
+ rv = OnOfflineCacheEntryForWritingAvailable(entry, aAppCache, status);
+ } else {
+ rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
+ }
+ } else {
+ rv = OnNormalCacheEntryAvailable(entry, aNew, status);
+ }
+
+ if (NS_FAILED(rv) && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
+ // If we have a fallback URI (and we're not already
+ // falling back), process the fallback asynchronously.
+ if (!LoadFallbackChannel() && !mFallbackKey.IsEmpty()) {
+ return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
+ }
+
+ 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) {
+ StoreCacheEntriesToWaitFor(LoadCacheEntriesToWaitFor() &
+ ~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);
+ }
+
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
+ Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD, false);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::OnOfflineCacheEntryAvailable(
+ nsICacheEntry* aEntry, bool aNew, nsIApplicationCache* aAppCache,
+ nsresult aEntryStatus) {
+ MOZ_ASSERT(!mApplicationCache || aAppCache == mApplicationCache);
+ MOZ_ASSERT(!aNew || !aEntry || mApplicationCacheForWrite);
+
+ StoreCacheEntriesToWaitFor(LoadCacheEntriesToWaitFor() &
+ ~WAIT_FOR_CACHE_ENTRY);
+
+ nsresult rv;
+
+ if (NS_SUCCEEDED(aEntryStatus)) {
+ if (!mApplicationCache) {
+ mApplicationCache = aAppCache;
+ }
+
+ // We successfully opened an offline cache session and the entry,
+ // so indicate we will load from the offline cache.
+ StoreLoadedFromApplicationCache(true);
+ StoreCacheEntryIsReadOnly(true);
+ mCacheEntry = aEntry;
+ StoreCacheEntryIsWriteOnly(false);
+
+ if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI && !mApplicationCacheForWrite) {
+ MaybeWarnAboutAppCache();
+ }
+
+ return NS_OK;
+ }
+
+ if (!mApplicationCacheForWrite && !LoadFallbackChannel()) {
+ if (!mApplicationCache) {
+ mApplicationCache = aAppCache;
+ }
+
+ // Check for namespace match.
+ nsCOMPtr<nsIApplicationCacheNamespace> namespaceEntry;
+ rv = mApplicationCache->GetMatchingNamespace(
+ mSpec, getter_AddRefs(namespaceEntry));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t namespaceType = 0;
+ if (!namespaceEntry ||
+ NS_FAILED(namespaceEntry->GetItemType(&namespaceType)) ||
+ (namespaceType & (nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
+ nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) ==
+ 0) {
+ // When loading from an application cache, only items
+ // on the whitelist or matching a
+ // fallback namespace should hit the network...
+ mLoadFlags |= LOAD_ONLY_FROM_CACHE;
+
+ // ... and if there were an application cache entry,
+ // we would have found it earlier.
+ return NS_ERROR_CACHE_KEY_NOT_FOUND;
+ }
+
+ if (namespaceType & nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
+ nsAutoCString namespaceSpec;
+ rv = namespaceEntry->GetNamespaceSpec(namespaceSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This prevents fallback attacks injected by an insecure subdirectory
+ // for the whole origin (or a parent directory).
+ if (!IsInSubpathOfAppCacheManifest(mApplicationCache, namespaceSpec)) {
+ return NS_OK;
+ }
+
+ rv = namespaceEntry->GetData(mFallbackKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (namespaceType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS) {
+ LOG(
+ ("nsHttpChannel::OnOfflineCacheEntryAvailable this=%p, URL matches "
+ "NETWORK,"
+ " looking for a regular cache entry",
+ this));
+
+ bool isHttps = mURI->SchemeIs("https");
+ rv = OpenCacheEntryInternal(isHttps, nullptr,
+ false /* don't allow appcache lookups */);
+ if (NS_FAILED(rv)) {
+ // Don't let this fail when cache entry can't be synchronously open.
+ // We want to go forward even without a regular cache entry.
+ return NS_OK;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::OnOfflineCacheEntryForWritingAvailable(
+ nsICacheEntry* aEntry, nsIApplicationCache* aAppCache,
+ nsresult aEntryStatus) {
+ MOZ_ASSERT(mApplicationCacheForWrite &&
+ aAppCache == mApplicationCacheForWrite);
+
+ StoreCacheEntriesToWaitFor(LoadCacheEntriesToWaitFor() &
+ ~WAIT_FOR_OFFLINE_CACHE_ENTRY);
+
+ if (NS_SUCCEEDED(aEntryStatus)) {
+ mOfflineCacheEntry = aEntry;
+ if (NS_FAILED(aEntry->GetLastModified(&mOfflineCacheLastModifiedTime))) {
+ mOfflineCacheLastModifiedTime = 0;
+ }
+ }
+
+ return aEntryStatus;
+}
+
+// Generates the proper cache-key for this instance of nsHttpChannel
+nsresult nsHttpChannel::GenerateCacheKey(uint32_t postID,
+ nsACString& cacheKey) {
+ AssembleCacheKey(LoadFallbackChannel() ? mFallbackKey.get() : 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);
+
+ if (mOfflineCacheEntry) {
+ rv = mOfflineCacheEntry->SetExpirationTime(expirationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+bool nsHttpChannel::ShouldUpdateOfflineCacheEntry() {
+ if (!mApplicationCacheForWrite || !mOfflineCacheEntry) {
+ return false;
+ }
+
+ // if we're updating the cache entry, update the offline cache entry too
+ if (mCacheEntry && LoadCacheEntryIsWriteOnly()) {
+ return true;
+ }
+
+ // if there's nothing in the offline cache, add it
+ if (mOfflineCacheEntry) {
+ return true;
+ }
+
+ // if the document is newer than the offline entry, update it
+ uint32_t docLastModifiedTime;
+ nsresult rv = mResponseHead->GetLastModifiedValue(&docLastModifiedTime);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+
+ if (mOfflineCacheLastModifiedTime == 0) {
+ return false;
+ }
+
+ if (docLastModifiedTime > mOfflineCacheLastModifiedTime) {
+ return true;
+ }
+
+ return false;
+}
+
+nsresult nsHttpChannel::OpenCacheInputStream(nsICacheEntry* cacheEntry,
+ bool startBuffering,
+ bool checkingAppCacheEntry) {
+ 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;
+ }
+
+ // XXX: We should not be skilling this check in the offline cache
+ // case, but we have to do so now to work around bug 794507.
+ bool mustHaveSecurityInfo =
+ !LoadLoadedFromApplicationCache() && !checkingAppCacheEntry;
+ MOZ_ASSERT(mCachedSecurityInfo || !mustHaveSecurityInfo);
+ if (!mCachedSecurityInfo && mustHaveSecurityInfo) {
+ 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.
+ if (!mApplicationCacheForWrite) {
+ LOG(
+ ("Will skip read from cache based on LOAD_ONLY_IF_MODIFIED "
+ "load flag\n"));
+ return NS_OK;
+ }
+
+ // If offline caching has been requested and the offline cache needs
+ // updating, we must complete the call even if the main cache entry
+ // is up to date. We don't know yet for sure whether the offline
+ // cache needs updating because at this point we haven't opened it
+ // for writing yet, so we have to start reading the cached entity now
+ // just in case.
+ LOG(
+ ("May skip read from cache based on LOAD_ONLY_IF_MODIFIED "
+ "load flag\n"));
+ }
+
+ // 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();
+ 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(
+ services::GetStreamTransportService());
+ 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()) {
+ if (!mApplicationCacheForWrite) {
+ 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);
+ }
+
+ if (!ShouldUpdateOfflineCacheEntry()) {
+ LOG(
+ ("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
+ "load flag (mApplicationCacheForWrite not null case)\n"));
+ mCacheInputStream.CloseAndRelease();
+ // 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::CloseOfflineCacheEntry() {
+ if (!mOfflineCacheEntry) return;
+
+ LOG(("nsHttpChannel::CloseOfflineCacheEntry [this=%p]", this));
+
+ if (NS_FAILED(mStatus)) {
+ mOfflineCacheEntry->AsyncDoom(nullptr);
+ } else {
+ bool succeeded;
+ if (NS_SUCCEEDED(GetRequestSucceeded(&succeeded)) && !succeeded)
+ mOfflineCacheEntry->AsyncDoom(nullptr);
+ }
+
+ mOfflineCacheEntry = nullptr;
+}
+
+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(
+ services::GetCacheStorageService());
+ if (!cacheStorageService) {
+ return;
+ }
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
+ Unused << cacheStorageService->DiskCacheStorage(info, false,
+ 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 nsHttpChannel::InitOfflineCacheEntry() {
+ // This function can be called even when we fail to connect (bug 551990)
+
+ if (!mOfflineCacheEntry) {
+ return NS_OK;
+ }
+
+ if (!mResponseHead || mResponseHead->NoStore()) {
+ if (mResponseHead && mResponseHead->NoStore()) {
+ mOfflineCacheEntry->AsyncDoom(nullptr);
+ }
+
+ CloseOfflineCacheEntry();
+
+ if (mResponseHead && mResponseHead->NoStore()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+ }
+
+ // This entry's expiration time should match the main entry's expiration
+ // time. UpdateExpirationTime() will keep it in sync once the offline
+ // cache entry has been created.
+ if (mCacheEntry) {
+ uint32_t expirationTime;
+ nsresult rv = mCacheEntry->GetExpirationTime(&expirationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOfflineCacheEntry->SetExpirationTime(expirationTime);
+ }
+
+ return AddCacheEntryHeaders(mOfflineCacheEntry);
+}
+
+nsresult DoAddCacheEntryHeaders(nsHttpChannel* self, nsICacheEntry* entry,
+ nsHttpRequestHead* requestHead,
+ nsHttpResponseHead* responseHead,
+ nsISupports* 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;
+}
+
+nsresult nsHttpChannel::InstallOfflineCacheListener(int64_t offset) {
+ nsresult rv;
+
+ LOG(("Preparing to write data into the offline cache [uri=%s]\n",
+ mSpec.get()));
+
+ MOZ_ASSERT(mOfflineCacheEntry);
+ MOZ_ASSERT(mListener);
+
+ nsCOMPtr<nsIOutputStream> out;
+ rv = mOfflineCacheEntry->OpenOutputStream(offset, -1, getter_AddRefs(out));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStreamListenerTee> tee =
+ do_CreateInstance(kStreamListenerTeeCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = tee->Init(mListener, out, nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ mListener = tee;
+
+ return NS_OK;
+}
+
+void nsHttpChannel::ClearBogusContentEncodingIfNeeded() {
+ // For .gz files, apache sends both a Content-Type: application/x-gzip
+ // as well as Content-Encoding: gzip, which is completely wrong. In
+ // this case, we choose to ignore the rogue Content-Encoding header. We
+ // must do this early on so as to prevent it from being seen up stream.
+ // The same problem exists for Content-Encoding: compress in default
+ // Apache installs.
+ nsAutoCString contentType;
+ mResponseHead->ContentType(contentType);
+ if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "gzip") &&
+ (contentType.EqualsLiteral(APPLICATION_GZIP) ||
+ contentType.EqualsLiteral(APPLICATION_GZIP2) ||
+ contentType.EqualsLiteral(APPLICATION_GZIP3))) {
+ // clear the Content-Encoding header
+ mResponseHead->ClearHeader(nsHttp::Content_Encoding);
+ } else if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding,
+ "compress") &&
+ (contentType.EqualsLiteral(APPLICATION_COMPRESS) ||
+ contentType.EqualsLiteral(APPLICATION_COMPRESS2))) {
+ // clear the Content-Encoding header
+ mResponseHead->ClearHeader(nsHttp::Content_Encoding);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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));
+
+ nsresult rv = HttpBaseChannel::SetupReplacementChannel(
+ newURI, newChannel, preserveMethod, redirectFlags);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = CheckRedirectLimit(redirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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));
+
+ 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)));
+
+ nsresult 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 (mApplicationCache) {
+ // if we are redirected to a different origin check if there is a fallback
+ // cache entry to fall back to. we don't care about file strict
+ // checking, at least mURI is not a file URI.
+ if (!NS_SecurityCompareURIs(mURI, mRedirectURI, false)) {
+ PushRedirectAsyncFunc(
+ &nsHttpChannel::ContinueProcessRedirectionAfterFallback);
+ bool waitingForRedirectCallback;
+ Unused << ProcessFallback(&waitingForRedirectCallback);
+ if (waitingForRedirectCallback) return NS_OK;
+ PopRedirectAsyncFunc(
+ &nsHttpChannel::ContinueProcessRedirectionAfterFallback);
+ }
+ }
+
+ return ContinueProcessRedirectionAfterFallback(NS_OK);
+}
+
+nsresult nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv) {
+ if (NS_SUCCEEDED(rv) && LoadFallingBack()) {
+ // do not continue with redirect processing, fallback is in
+ // progress now.
+ return NS_OK;
+ }
+
+ // 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;
+ }
+
+#ifdef MOZ_GECKO_PROFILER
+ if (profiler_can_accept_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ TimingStruct timings;
+ if (mTransaction) {
+ timings = mTransaction->Timings();
+ }
+
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId,
+ NetworkLoadType::LOAD_REDIRECT, mLastStatusReported, TimeStamp::Now(),
+ mLogicalOffset, mCacheDisposition, mLoadInfo->GetInnerWindowID(),
+ &timings, mRedirectURI, std::move(mSource),
+ Some(nsDependentCString(contentType.get())));
+ }
+#endif
+
+ nsCOMPtr<nsIIOService> ioService;
+ rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t redirectFlags;
+ if (nsHttp::IsPermanentRedirect(mRedirectType))
+ redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
+ else
+ redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(mRedirectURI, redirectFlags);
+ 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);
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
+ }
+
+ return rv;
+}
+
+nsresult nsHttpChannel::ContinueProcessRedirection(nsresult rv) {
+ AutoRedirectVetoNotifier notifier(this);
+
+ 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();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(bool userCancel) {
+ LOG(("nsHttpChannel::OnAuthCancelled [this=%p]", this));
+
+ 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);
+
+ // ensure call of OnStartRequest of the current listener here,
+ // it would not be called otherwise at all
+ nsresult 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(nsIApplicationCacheContainer)
+ NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
+ 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(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestTailUnblockCallback)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(nsHttpChannel)
+NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsIRequest
+//-----------------------------------------------------------------------------
+
+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 "]\n", this,
+ static_cast<uint32_t>(status)));
+ 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) {
+ bool channelClassifierCancellationPending =
+ !!LoadChannelClassifierCancellationPending();
+ if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status)) {
+ StoreChannelClassifierCancellationPending(0);
+ }
+
+ mCanceled = true;
+ mStatus = status;
+ 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);
+ Unused << AsyncAbort(status);
+ } else if (channelClassifierCancellationPending) {
+ // If we're coming from an asynchronous path when canceling a channel due
+ // to safe-browsing protection, we need to AsyncAbort the channel now.
+ 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);
+}
+
+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(nsISupports** securityInfo) {
+ NS_ENSURE_ARG_POINTER(securityInfo);
+ *securityInfo = mSecurityInfo;
+ NS_IF_ADDREF(*securityInfo);
+ 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));
+ LogCallingScriptLocation(this);
+
+#ifdef MOZ_TASK_TRACER
+ if (tasktracer::IsStartLogging()) {
+ uint64_t sourceEventId, parentTaskId;
+ tasktracer::SourceEventType sourceEventType;
+ GetCurTraceInfo(&sourceEventId, &parentTaskId, &sourceEventType);
+ nsAutoCString urispec;
+ mURI->GetSpec(urispec);
+ tasktracer::AddLabel("nsHttpChannel::AsyncOpen %s", urispec.get());
+ }
+#endif
+
+#ifdef MOZ_GECKO_PROFILER
+ mLastStatusReported =
+ TimeStamp::Now(); // in case we enable the profiler after AsyncOpen()
+ if (profiler_can_accept_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
+ mChannelCreationTimestamp, mLastStatusReported, 0, mCacheDisposition,
+ mLoadInfo->GetInnerWindowID(), nullptr, nullptr);
+ }
+#endif
+
+ 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 mStatus;
+ }
+
+ if (MaybeWaitForUploadStreamLength(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 (!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();
+ }
+
+ AntiTrackingUtils::UpdateAntiTrackingInfoForChannel(this);
+
+ 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;
+ }
+
+ // PauseTask/DelayHttpChannel queuing
+ if (!DelayHttpChannelQueue::AttemptQueueChannel(this)) {
+ // If fuzzyfox is disabled; or adding to the queue failed, the channel must
+ // continue.
+ AsyncOpenFinal(TimeStamp::Now());
+ }
+
+ return NS_OK;
+}
+
+nsresult nsHttpChannel::AsyncOpenFinal(TimeStamp aTimeStamp) {
+ // 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));
+
+ if (!NS_ShouldClassifyChannel(this)) {
+ return MaybeResolveProxyAndBeginConnect();
+ }
+
+ // 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.
+ RefPtr<nsHttpChannel> self = this;
+ bool willCallback = NS_SUCCEEDED(
+ AsyncUrlChannelClassifier::CheckChannel(this, [self]() -> void {
+ nsresult rv = self->MaybeResolveProxyAndBeginConnect();
+ if (NS_FAILED(rv)) {
+ // Since this error is thrown asynchronously so that the caller
+ // of BeginConnect() will not do clean up for us. We have to do
+ // it on our own.
+ self->CloseCacheEntry(false);
+ Unused << self->AsyncAbort(rv);
+ }
+ }));
+
+ 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.
+ return MaybeResolveProxyAndBeginConnect();
+ }
+
+ return NS_OK;
+}
+
+nsresult 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)) &&
+ NS_SUCCEEDED(ResolveProxy())) {
+ return NS_OK;
+ }
+
+ rv = BeginConnect();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(rv);
+ }
+
+ return NS_OK;
+}
+
+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 AsyncOpenFinal,
+// MaybeResolveProxyAndBeginConnect and OnProxyAvailable ever call
+// BeginConnect.
+nsresult nsHttpChannel::BeginConnect() {
+ LOG(("nsHttpChannel::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);
+
+ 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();
+
+ 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, GetTopWindowOrigin(),
+ mPrivateBrowsing, IsIsolated(),
+ mCallbacks, originAttributes);
+
+ RefPtr<nsHttpConnectionInfo> connInfo = new nsHttpConnectionInfo(
+ host, port, ""_ns, mUsername, GetTopWindowOrigin(), proxyInfo,
+ originAttributes, isHttps);
+ bool http2Allowed = !gHttpHandler->IsHttp2Excluded(connInfo);
+ if (!LoadAllowHttp3()) {
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ }
+ bool http3Allowed = !mUpgradeProtocolCallback && !mProxyInfo &&
+ !(mCaps & NS_HTTP_BE_CONSERVATIVE) &&
+ !LoadBeConservative() && LoadAllowHttp3();
+
+ // No need to lookup HTTPSSVC record if mHTTPSSVCRecord already contains a
+ // value.
+ StoreUseHTTPSSVC(StaticPrefs::network_dns_upgrade_with_https_rr() &&
+ mHTTPSSVCRecord.isNothing());
+
+ 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, IsIsolated(),
+ GetTopWindowOrigin(), 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());
+ }
+ rv = mRequestHead.SetHeader(nsHttp::Alternate_Service_Used, altUsedLine);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (consoleService) {
+ 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);
+
+ // Don't use HTTPSSVC record if we found altsvc mapping.
+ StoreUseHTTPSSVC(false);
+ } 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);
+ }
+
+ // 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);
+ }
+
+ 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 & nsIClassOfService::Leader) {
+ mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
+ }
+ if (mClassOfService & nsIClassOfService::Unblocked) {
+ mCaps |= NS_HTTP_LOAD_UNBLOCKED;
+ }
+ if (mClassOfService & 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;
+ }
+
+ bool shouldBeClassified = 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 = ContinueBeginConnectWithResult();
+ 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() {
+ bool httpssvcQueried = false;
+ // If https rr is not queried sucessfully, we have to reset mUseHTTPSSVC to
+ // false. Otherwise, this channel may wait https rr forever.
+ auto resetUsHTTPSSVC =
+ MakeScopeExit([&] { StoreUseHTTPSSVC(httpssvcQueried); });
+
+ // 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)) {
+ 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());
+ nsresult rv = mDNSPrefetch->PrefetchHigh(mCaps & NS_HTTP_REFRESH_DNS);
+
+ 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__);
+ }
+
+ // When LoadUseHTTPSSVC() is true, we should really "fetch" the HTTPS RR,
+ // not "prefetch", since DNS prefetch can be disabled by the pref.
+ if (LoadUseHTTPSSVC() ||
+ gHttpHandler->UseHTTPSRRForSpeculativeConnection()) {
+ 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)));
+ } else {
+ httpssvcQueried = true;
+ }
+ }
+ }
+
+ 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::SetupFallbackChannel(const char* aFallbackKey) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ LOG(("nsHttpChannel::SetupFallbackChannel [this=%p, key=%s]\n", this,
+ aFallbackKey));
+ StoreFallbackChannel(true);
+ mFallbackKey = aFallbackKey;
+
+ return NS_OK;
+}
+
+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(base::ProcessId aChildProcessId)
+ -> 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__);
+ request->mChildProcessId = aChildProcessId;
+ return request->mPromise;
+ }
+
+ mozilla::ipc::Endpoint<extensions::PStreamFilterParent> parent;
+ mozilla::ipc::Endpoint<extensions::PStreamFilterChild> child;
+ nsresult rv = extensions::PStreamFilter::CreateEndpoints(
+ ProcessId(), aChildProcessId, &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;
+}
+
+nsresult nsHttpChannel::ContinueBeginConnectWithResult() {
+ LOG(("nsHttpChannel::ContinueBeginConnectWithResult [this=%p]", this));
+ MOZ_ASSERT(!mCallOnResume, "How did that happen?");
+
+ nsresult rv;
+
+ if (mSuspendCount) {
+ LOG(("Waiting until resume to do async connect [this=%p]\n", this));
+ mCallOnResume = [](nsHttpChannel* self) {
+ self->ContinueBeginConnect();
+ return NS_OK;
+ };
+ rv = NS_OK;
+ } else if (mCanceled) {
+ // We may have been cancelled already, by nsChannelClassifier in that
+ // case, we should not send the request to the server
+ rv = mStatus;
+ } else {
+ rv = PrepareToConnect();
+ }
+
+ LOG(
+ ("nsHttpChannel::ContinueBeginConnectWithResult result [this=%p "
+ "rv=%" PRIx32 " mCanceled=%u]\n",
+ this, static_cast<uint32_t>(rv), static_cast<bool>(mCanceled)));
+ return rv;
+}
+
+void nsHttpChannel::ContinueBeginConnect() {
+ LOG(("nsHttpChannel::ContinueBeginConnect this=%p", this));
+
+ nsresult rv = ContinueBeginConnectWithResult();
+ if (NS_FAILED(rv)) {
+ CloseCacheEntry(false);
+ Unused << AsyncAbort(rv);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// HttpChannel::nsIClassOfService
+//-----------------------------------------------------------------------------
+
+void nsHttpChannel::OnClassOfServiceUpdated() {
+ LOG(("nsHttpChannel::OnClassOfServiceUpdated this=%p, cos=%u", this,
+ mClassOfService));
+
+ if (mTransaction) {
+ gHttpHandler->UpdateClassOfServiceOnTransaction(mTransaction,
+ mClassOfService);
+ }
+ if (EligibleForTailing()) {
+ RemoveAsNonTailRequest();
+ } else {
+ AddAsNonTailRequest();
+ }
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService;
+ mClassOfService = inFlags;
+ if (previous != mClassOfService) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::AddClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService;
+ mClassOfService |= inFlags;
+ if (previous != mClassOfService) {
+ OnClassOfServiceUpdated();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::ClearClassFlags(uint32_t inFlags) {
+ uint32_t previous = mClassOfService;
+ mClassOfService &= ~inFlags;
+ if (previous != mClassOfService) {
+ 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 = mProxyInfo;
+ else
+ *result = mConnectionInfo->ProxyInfo();
+ NS_IF_ADDREF(*result);
+ 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;
+}
+
+//-----------------------------------------------------------------------------
+// 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
+//-----------------------------------------------------------------------------
+
+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;
+ }
+
+ LOG(("nsHttpChannel::OnStartRequest [this=%p request=%p status=%" PRIx32
+ "]\n",
+ this, request, static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
+
+ Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_ONSTART_SUCCESS,
+ NS_SUCCEEDED(mStatus));
+
+ if (mTransaction) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP3_CHANNEL_ONSTART_SUCCESS,
+ (mTransaction->IsHttp3Used()) ? "http3"_ns : "no_http3"_ns,
+ NS_SUCCEEDED(mStatus));
+ }
+
+ if (gTRRService && gTRRService->IsConfirmed()) {
+ Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_ONSTART_SUCCESS_TRR,
+ TRRService::AutoDetectedKey(), NS_SUCCEEDED(mStatus));
+ }
+
+ 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()) {
+ if (stage != HTTPSSVC_NOT_USED) {
+ Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_RECORD_RECEIVING_STAGE,
+ stage);
+ }
+ }
+
+ if (HTTPS_RR_IS_USED(stage)) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_CHANNEL_ONSTART_SUCCESS_HTTPS_RR,
+ LoadEchConfigUsed() ? "echConfig-used"_ns : "echConfig-not-used"_ns,
+ NS_SUCCEEDED(mStatus));
+ }
+ }
+
+ // 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 = ProcessCrossOriginEmbedderPolicyHeader();
+ if (NS_FAILED(rv)) {
+ mStatus = NS_ERROR_BLOCKED_BY_POLICY;
+ HandleAsyncAbort();
+ return NS_OK;
+ }
+
+ rv = ProcessCrossOriginResourcePolicyHeader();
+ if (NS_FAILED(rv)) {
+ mStatus = NS_ERROR_DOM_CORP_FAILED;
+ HandleAsyncAbort();
+ return NS_OK;
+ }
+
+ // before we check for redirects, check if the load should be shifted into a
+ // new process.
+ rv = ComputeCrossOriginOpenerPolicyMismatch();
+
+ if (rv == NS_ERROR_BLOCKED_BY_POLICY) {
+ // this navigates the doc's browsing context to a network error.
+ mStatus = NS_ERROR_BLOCKED_BY_POLICY;
+ HandleAsyncAbort();
+ return NS_OK;
+ }
+
+ // 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)) {
+ 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;
+ }
+
+ // on other request errors, try to fall back
+ if (NS_FAILED(mStatus)) {
+ PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest4);
+ bool waitingForRedirectCallback;
+ Unused << ProcessFallback(&waitingForRedirectCallback);
+ if (waitingForRedirectCallback) return NS_OK;
+ PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest4);
+ }
+
+ return ContinueOnStartRequest4(NS_OK);
+}
+
+nsresult nsHttpChannel::ContinueOnStartRequest4(nsresult result) {
+ LOG(("nsHttpChannel::ContinueOnStartRequest4 [this=%p]", this));
+
+ if (LoadFallingBack()) return NS_OK;
+
+ if (NS_SUCCEEDED(mStatus) && mResponseHead && mAuthProvider) {
+ uint32_t httpStatus = mResponseHead->Status();
+ if (httpStatus != 401 && httpStatus != 407) {
+ nsresult rv = mAuthProvider->CheckForSuperfluousAuth();
+ if (NS_FAILED(rv)) {
+ LOG((" CheckForSuperfluousAuth failed (%08x)",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+
+ return CallOnStartRequest();
+}
+
+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 (WRONG_RACING_RESPONSE_SOURCE(request)) {
+ return NS_OK;
+ }
+
+ // 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);
+ 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 ((mAuthRetryPending || NS_FAILED(status)) && transactionWithStickyConn) {
+ if (NS_FAILED(status)) {
+ // Close (don't reuse) the sticky connection if it's in the middle
+ // of an NTLM negotiation and 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();
+
+ // 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();
+
+ // 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.");
+ nsCOMPtr<nsIStreamListener> listener(mListener);
+ StoreOnStartRequestCalled(true);
+ listener->OnStartRequest(this);
+ } else {
+ StoreOnStartRequestCalled(true);
+ NS_WARNING("OnStartRequest skipped because of null listener");
+ }
+ }
+
+ // 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) {
+ nsresult rv = gHttpHandler->CompleteUpgrade(aTransWithStickyConn,
+ mUpgradeProtocolCallback);
+ 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);
+
+ // If we upgraded because of 'security.mixed_content.upgrade_display_content',
+ // we collect telemetry if the upgrade was a success.
+ if (mLoadInfo->GetBrowserDidUpgradeInsecureRequests()) {
+ bool success = NS_SUCCEEDED(aStatus);
+ nsContentPolicyType type;
+ mLoadInfo->GetInternalContentPolicyType(&type);
+
+ switch (type) {
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON:
+ Telemetry::AccumulateCategorical(
+ success
+ ? Telemetry::LABELS_MIXED_CONTENT_UPGRADE_SUCCESS::ImageSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_UPGRADE_SUCCESS::ImageFailed);
+ break;
+
+ case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
+ Telemetry::AccumulateCategorical(
+ success
+ ? Telemetry::LABELS_MIXED_CONTENT_UPGRADE_SUCCESS::VideoSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_UPGRADE_SUCCESS::VideoFailed);
+ break;
+
+ case nsIContentPolicy::TYPE_INTERNAL_AUDIO:
+ case nsIContentPolicy::TYPE_INTERNAL_TRACK:
+ Telemetry::AccumulateCategorical(
+ success
+ ? Telemetry::LABELS_MIXED_CONTENT_UPGRADE_SUCCESS::AudioSuccess
+ : Telemetry::LABELS_MIXED_CONTENT_UPGRADE_SUCCESS::AudioFailed);
+ break;
+
+ default:
+ // upgrade_display_content only upgrades
+ // audio, video and images.
+ MOZ_ASSERT(false, "Unexpected content type.");
+ }
+ }
+
+ // 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);
+
+ // 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();
+
+#ifdef MOZ_GECKO_PROFILER
+ if (profiler_can_accept_markers() && !mRedirectURI) {
+ // Don't include this if we already redirected
+ // These do allocations/frees/etc; avoid if not active
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ nsCOMPtr<nsIURI> uri;
+ GetURI(getter_AddRefs(uri));
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+ profiler_add_network_marker(
+ uri, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_STOP,
+ mLastStatusReported, TimeStamp::Now(), mLogicalOffset,
+ mCacheDisposition, mLoadInfo->GetInnerWindowID(), &mTransactionTimings,
+ nullptr, std::move(mSource),
+ Some(nsDependentCString(contentType.get())));
+ }
+#endif
+
+ 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;
+
+ // notify "http-on-stop-connect" observers
+ gHttpHandler->OnStopRequest(this);
+
+ RemoveAsNonTailRequest();
+
+ // 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 (mOfflineCacheEntry) CloseOfflineCacheEntry();
+
+ 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;
+ }
+
+ 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(nsIEventTarget* 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<nsIEventTarget> main = GetMainThreadEventTarget();
+ NS_ENSURE_TRUE(main, NS_ERROR_UNEXPECTED);
+ rv = retargetableCachePump->RetargetDeliveryTo(main);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetDeliveryTarget(nsIEventTarget** 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;
+}
+
+//-----------------------------------------------------------------------------
+// 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,
+ echConfigUsed);
+ } else {
+ nsCOMPtr<nsISocketTransport> socketTransport = do_QueryInterface(trans);
+ if (socketTransport) {
+ socketTransport->GetSelfAddr(&mSelfAddr);
+ socketTransport->GetPeerAddr(&mPeerAddr);
+ socketTransport->ResolvedByTRR(&isTrr);
+ 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(int32_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::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,
+ bool 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::GetAltDataInputStream(const nsACString& aType,
+ nsIInputStreamReceiver* aReceiver) {
+ if (aReceiver == nullptr) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ nsCOMPtr<nsICacheEntry> cacheEntry =
+ mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
+ if (cacheEntry) {
+ nsresult rv = cacheEntry->OpenAlternativeInputStream(
+ aType, getter_AddRefs(inputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aReceiver->OnInputStreamReady(inputStream);
+ 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::GetOfflineCacheToken(nsISupports** token) {
+ NS_ENSURE_ARG_POINTER(token);
+ if (!mOfflineCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ return CallQueryInterface(mOfflineCacheEntry, token);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetOfflineCacheToken(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);
+ if (seekable) {
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ }
+ }
+
+ // 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::nsIApplicationCacheChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsHttpChannel::GetApplicationCache(nsIApplicationCache** out) {
+ NS_IF_ADDREF(*out = mApplicationCache);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetApplicationCache(nsIApplicationCache* appCache) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mApplicationCache = appCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetApplicationCacheForWrite(nsIApplicationCache** out) {
+ NS_IF_ADDREF(*out = mApplicationCacheForWrite);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetApplicationCacheForWrite(nsIApplicationCache* appCache) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mApplicationCacheForWrite = appCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetLoadedFromApplicationCache(
+ bool* aLoadedFromApplicationCache) {
+ *aLoadedFromApplicationCache = LoadLoadedFromApplicationCache();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetInheritApplicationCache(bool* aInherit) {
+ *aInherit = LoadInheritApplicationCache();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetInheritApplicationCache(bool aInherit) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ StoreInheritApplicationCache(aInherit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::GetChooseApplicationCache(bool* aChoose) {
+ *aChoose = LoadChooseApplicationCache();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::SetChooseApplicationCache(bool aChoose) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ StoreChooseApplicationCache(aChoose);
+ return NS_OK;
+}
+
+nsHttpChannel::OfflineCacheEntryAsForeignMarker*
+nsHttpChannel::GetOfflineCacheEntryAsForeignMarker() {
+ if (!mApplicationCache) return nullptr;
+
+ return new OfflineCacheEntryAsForeignMarker(mApplicationCache, mURI);
+}
+
+nsresult nsHttpChannel::OfflineCacheEntryAsForeignMarker::MarkAsForeign() {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> noRefURI;
+ rv = NS_GetURIWithoutRef(mCacheURI, getter_AddRefs(noRefURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString spec;
+ rv = noRefURI->GetAsciiSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mApplicationCache->MarkEntry(spec, nsIApplicationCache::ITEM_FOREIGN);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::MarkOfflineCacheEntryAsForeign() {
+ nsresult rv;
+
+ UniquePtr<OfflineCacheEntryAsForeignMarker> marker(
+ GetOfflineCacheEntryAsForeignMarker());
+
+ if (!marker) return NS_ERROR_NOT_AVAILABLE;
+
+ rv = marker->MarkAsForeign();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// 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.
+ 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));
+
+ MOZ_ASSERT(!mHTTPSSVCRecord);
+ 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);
+ 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, not the application cache.
+ // 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(
+ services::GetCacheStorageService());
+ rv = cacheStorageService ? NS_OK : NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
+ rv = cacheStorageService->DiskCacheStorage(info, false,
+ 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,
+ GetCurrentEventTarget(),
+ 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 LoadCacheEntriesToWaitFor() != 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);
+}
+
+void nsHttpChannel::SetConnectionInfo(nsHttpConnectionInfo* aCI) {
+ mConnectionInfo = aCI ? aCI->Clone() : nullptr;
+}
+
+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);
+}
+
+void nsHttpChannel::MaybeWarnAboutAppCache() {
+ // First, accumulate a telemetry ping about appcache usage.
+ Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD, true);
+
+ // Then, issue a deprecation warning.
+ nsCOMPtr<nsIDeprecationWarner> warner;
+ GetCallback(warner);
+ if (warner) {
+ warner->IssueWarning(static_cast<uint32_t>(DeprecatedOperations::eAppCache),
+ false);
+ }
+}
+
+// Step 10 of HTTP-network-or-cache fetch
+void nsHttpChannel::SetOriginHeader() {
+ if (mRequestHead.IsGet() || mRequestHead.IsHead()) {
+ return;
+ }
+ if (mLoadInfo->TriggeringPrincipal()->IsSystemPrincipal()) {
+ // Do not set origin header for system principal contexts:
+ return;
+ }
+ nsresult rv;
+
+ nsAutoCString existingHeader;
+ Unused << mRequestHead.GetHeader(nsHttp::Origin, existingHeader);
+ if (!existingHeader.IsEmpty()) {
+ LOG(("nsHttpChannel::SetOriginHeader Origin header already present"));
+ // In case we already have an Origin header, check with referrerInfo
+ // if we should "null" it.
+ Unused << mRequestHead.GetHeader(nsHttp::Origin, existingHeader);
+ auto const shouldNullifyOriginHeader =
+ [&existingHeader](nsHttpChannel* self) {
+ if (self->LoadTaintedOriginFlag()) {
+ return true;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), existingHeader);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ return ReferrerInfo::ShouldSetNullOriginHeader(self, uri);
+ };
+
+ if (shouldNullifyOriginHeader(this)) {
+ LOG(("nsHttpChannel::SetOriginHeader null Origin by Referrer-Policy"));
+ rv = mRequestHead.SetHeader(nsHttp::Origin, "null"_ns, false /* merge */);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ return;
+ }
+
+ nsCOMPtr<nsIURI> referrer;
+ auto* basePrin = BasePrincipal::Cast(mLoadInfo->TriggeringPrincipal());
+ rv = basePrin->GetURI(getter_AddRefs(referrer));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsAutoCString origin("null");
+
+ if (StaticPrefs::network_http_sendOriginHeader() != 0 && referrer &&
+ ReferrerInfo::IsReferrerSchemeAllowed(referrer) &&
+ !ReferrerInfo::ShouldSetNullOriginHeader(this, referrer) &&
+ !LoadTaintedOriginFlag()) {
+ nsContentUtils::GetASCIIOrigin(referrer, origin);
+
+ // Restrict Origin to same-origin loads if requested by user
+ if (StaticPrefs::network_http_sendOriginHeader() == 1) {
+ nsAutoCString currentOrigin;
+ nsContentUtils::GetASCIIOrigin(mURI, currentOrigin);
+ if (!origin.EqualsIgnoreCase(currentOrigin.get())) {
+ // Origin header suppressed by user setting
+ origin.AssignLiteral("null");
+ }
+ }
+ }
+
+ rv = mRequestHead.SetHeader(nsHttp::Origin, origin, false /* merge */);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+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::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) {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+ mCacheOpenDelay = aTimeout;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Test_triggerDelayedOpenCacheEntry() {
+ 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 (!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);
+ }
+
+ if (!mNetworkTriggerTimer) {
+ mNetworkTriggerTimer = NS_NewTimer();
+ }
+ mNetworkTriggerTimer->InitWithCallback(this, 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.
+ if (mCacheOpenFunc) {
+ mRaceCacheWithNetwork = true;
+ } else if (AwaitingCacheCallbacks()) {
+ mRaceCacheWithNetwork = StaticPrefs::network_http_rcwn_enabled();
+ }
+
+ LOG((" triggering network\n"));
+ return ContinueConnect();
+}
+
+nsresult nsHttpChannel::MaybeRaceCacheWithNetwork() {
+ nsresult rv;
+
+ nsCOMPtr<nsINetworkLinkService> netLinkSvc =
+ do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t linkType;
+ rv = netLinkSvc->GetLinkType(&linkType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!(linkType == nsINetworkLinkService::LINK_TYPE_UNKNOWN ||
+ linkType == nsINetworkLinkService::LINK_TYPE_ETHERNET ||
+ linkType == nsINetworkLinkService::LINK_TYPE_USB ||
+ linkType == nsINetworkLinkService::LINK_TYPE_WIFI)) {
+ return NS_OK;
+ }
+
+ // Don't trigger the network if the load flags say so.
+ if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_NO_NETWORK_IO)) {
+ return NS_OK;
+ }
+
+ // We must not race if the channel has a failure status code.
+ if (NS_FAILED(mStatus)) {
+ return NS_OK;
+ }
+
+ // If a CORS Preflight is required we must not race.
+ if (LoadRequireCORSPreflight() && !LoadIsCorsPreflightDone()) {
+ return NS_OK;
+ }
+
+ 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(),
+ "The pref must be turned on.");
+ LOG(("nsHttpChannel::MaybeRaceCacheWithNetwork [this=%p, delay=%u]\n", this,
+ mRaceDelay));
+
+ return TriggerNetworkWithDelay(mRaceDelay);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Test_triggerNetwork(int32_t aTimeout) {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
+ return TriggerNetworkWithDelay(aTimeout);
+}
+
+NS_IMETHODIMP
+nsHttpChannel::Notify(nsITimer* aTimer) {
+ RefPtr<nsHttpChannel> self(this);
+ if (aTimer == mCacheOpenTimer) {
+ return Test_triggerDelayedOpenCacheEntry();
+ } else if (aTimer == mNetworkTriggerTimer) {
+ return TriggerNetwork();
+ } else {
+ MOZ_CRASH("Unknown timer");
+ }
+
+ return NS_OK;
+}
+
+bool nsHttpChannel::EligibleForTailing() {
+ if (!(mClassOfService & nsIClassOfService::Tail)) {
+ return false;
+ }
+
+ if (mClassOfService &
+ (nsIClassOfService::UrgentStart | nsIClassOfService::Leader |
+ nsIClassOfService::TailForbidden)) {
+ return false;
+ }
+
+ if (mClassOfService & nsIClassOfService::Unblocked &&
+ !(mClassOfService & 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();
+}
+
+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);
+ } else {
+ 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();
+
+ nsresult rv = intercepted->Init(
+ mURI, mCaps, static_cast<nsProxyInfo*>(mProxyInfo.get()),
+ mProxyResolveFlags, mProxyURI, mChannelId, type);
+
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+ intercepted->SetLoadInfo(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.
+ if (ServiceWorkerParentInterceptEnabled()) {
+ 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);
+
+ 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();
+ }
+ 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()));
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h
new file mode 100644
index 0000000000..208dd829af
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -0,0 +1,888 @@
+/* -*- 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 "DelayHttpChannelQueue.h"
+#include "HttpBaseChannel.h"
+#include "nsTArray.h"
+#include "nsIApplicationCache.h"
+#include "nsICachingChannel.h"
+#include "nsICacheEntry.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsIDNSListener.h"
+#include "nsIApplicationCacheChannel.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIHttpAuthenticableChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIThreadRetargetableStreamListener.h"
+#include "nsWeakReference.h"
+#include "TimingStruct.h"
+#include "AutoClose.h"
+#include "nsIStreamListener.h"
+#include "nsICorsPreflightCallback.h"
+#include "AlternateServices.h"
+#include "nsIRaceCacheWithNetwork.h"
+#include "mozilla/AtomicBitfields.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/extensions/PStreamFilterParent.h"
+#include "mozilla/net/DocumentLoadListener.h"
+#include "mozilla/Mutex.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 nsIStreamListener,
+ public nsICachingChannel,
+ public nsICacheEntryOpenCallback,
+ public nsITransportEventSink,
+ public nsIProtocolProxyCallback,
+ public nsIHttpAuthenticableChannel,
+ public nsIApplicationCacheChannel,
+ public nsIAsyncVerifyRedirectCallback,
+ public nsIThreadRetargetableRequest,
+ public nsIThreadRetargetableStreamListener,
+ public nsIDNSListener,
+ public nsSupportsWeakReference,
+ public nsICorsPreflightCallback,
+ public nsIRaceCacheWithNetwork,
+ public nsIRequestTailUnblockCallback,
+ public nsITimerCallback {
+ 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_NSIAPPLICATIONCACHECONTAINER
+ NS_DECL_NSIAPPLICATIONCACHECHANNEL
+ NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+ NS_DECL_NSITHREADRETARGETABLEREQUEST
+ NS_DECL_NSIDNSLISTENER
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTPCHANNEL_IID)
+ NS_DECL_NSIRACECACHEWITHNETWORK
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSIREQUESTTAILUNBLOCKCALLBACK
+
+ // 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) 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 Cancel(nsresult status) override;
+ NS_IMETHOD Suspend() override;
+ NS_IMETHOD Resume() override;
+ // nsIChannel
+ NS_IMETHOD GetSecurityInfo(nsISupports** aSecurityInfo) override;
+ NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+ // nsIHttpChannel
+ NS_IMETHOD GetEncodedBodySize(uint64_t* aEncodedBodySize) override;
+ // nsIHttpChannelInternal
+ NS_IMETHOD SetupFallbackChannel(const char* aFallbackKey) override;
+ 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;
+
+ // 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;
+ // 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) override;
+ NS_IMETHOD LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) override;
+
+ void SetWarningReporter(HttpChannelSecurityWarningReporter* aReporter);
+ HttpChannelSecurityWarningReporter* GetWarningReporter();
+
+ bool DataSentToChildProcess() { return LoadDataSentToChildProcess(); }
+
+ public: /* internal necko use only */
+ uint32_t GetRequestTime() const { return mRequestTime; }
+
+ nsresult AsyncOpenFinal(TimeStamp aTimeStamp);
+
+ [[nodiscard]] nsresult OpenCacheEntry(bool usingSSL);
+ [[nodiscard]] nsresult OpenCacheEntryInternal(
+ bool isHttps, nsIApplicationCache* applicationCache, bool noAppCache);
+ [[nodiscard]] nsresult ContinueConnect();
+
+ [[nodiscard]] nsresult StartRedirectChannelToURI(nsIURI*, uint32_t);
+
+ // This allows cache entry to be marked as foreign even after channel itself
+ // is gone. Needed for e10s (see
+ // HttpChannelParent::RecvDocumentChannelCleanup)
+ class OfflineCacheEntryAsForeignMarker {
+ nsCOMPtr<nsIApplicationCache> mApplicationCache;
+ nsCOMPtr<nsIURI> mCacheURI;
+
+ public:
+ OfflineCacheEntryAsForeignMarker(nsIApplicationCache* appCache,
+ nsIURI* aURI)
+ : mApplicationCache(appCache), mCacheURI(aURI) {}
+
+ nsresult MarkAsForeign();
+ };
+
+ OfflineCacheEntryAsForeignMarker* GetOfflineCacheEntryAsForeignMarker();
+
+ // 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->StoreCacheEntriesToWaitFor(
+ nsHttpChannel::WAIT_FOR_CACHE_ENTRY |
+ nsHttpChannel::WAIT_FOR_OFFLINE_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->StoreCacheEntriesToWaitFor(
+ mChannel->LoadCacheEntriesToWaitFor() & mKeep);
+ }
+
+ private:
+ nsHttpChannel* mChannel;
+ uint32_t mKeep : 2;
+ };
+
+ 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<ipc::Endpoint<extensions::PStreamFilterChild>, bool, true>;
+ [[nodiscard]] RefPtr<ChildEndpointPromise> AttachStreamFilter(
+ base::ProcessId aChildProcessId);
+
+ private: // used for alternate service validation
+ RefPtr<TransactionObserver> mTransactionObserver;
+
+ public:
+ void SetConnectionInfo(nsHttpConnectionInfo*); // clones the argument
+ void SetTransactionObserver(TransactionObserver* arg) {
+ mTransactionObserver = arg;
+ }
+ TransactionObserver* GetTransactionObserver() { return mTransactionObserver; }
+
+ CacheDisposition mCacheDisposition;
+
+ protected:
+ virtual ~nsHttpChannel();
+
+ private:
+ typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
+
+ // 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.)
+ nsresult 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 ContinueBeginConnectWithResult();
+ void ContinueBeginConnect();
+ [[nodiscard]] nsresult PrepareToConnect();
+ void HandleOnBeforeConnect();
+ [[nodiscard]] nsresult OnBeforeConnect();
+ [[nodiscard]] nsresult ContinueOnBeforeConnect(bool aShouldUpgrade,
+ nsresult aStatus);
+ nsresult MaybeUseHTTPSRRForUpgrade(bool aShouldUpgrade, nsresult aStatus);
+ void OnHTTPSRRAvailable(nsIDNSHTTPSSVCRecord* aRecord);
+ void OnBeforeConnectContinue();
+ [[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 httpStatus);
+ [[nodiscard]] nsresult ContinueProcessRedirection(nsresult);
+ [[nodiscard]] nsresult ContinueProcessRedirectionAfterFallback(nsresult);
+ [[nodiscard]] nsresult ProcessFailedProxyConnect(uint32_t httpStatus);
+ [[nodiscard]] nsresult ProcessFallback(bool* waitingForRedirectCallback);
+ [[nodiscard]] nsresult ContinueProcessFallback(nsresult);
+ 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();
+ void HandleAsyncFallback();
+ [[nodiscard]] nsresult ContinueHandleAsyncFallback(nsresult);
+ [[nodiscard]] nsresult PromptTempRedirect();
+ [[nodiscard]] virtual nsresult SetupReplacementChannel(
+ nsIURI*, nsIChannel*, bool preserveMethod,
+ uint32_t redirectFlags) override;
+
+ // proxy specific methods
+ [[nodiscard]] nsresult ProxyFailover();
+ [[nodiscard]] nsresult AsyncDoReplaceWithProxy(nsIProxyInfo*);
+ [[nodiscard]] nsresult ContinueDoReplaceWithProxy(nsresult);
+ [[nodiscard]] nsresult ResolveProxy();
+
+ // cache specific methods
+ [[nodiscard]] nsresult OnOfflineCacheEntryAvailable(
+ nsICacheEntry* aEntry, bool aNew, nsIApplicationCache* aAppCache,
+ nsresult aResult);
+ [[nodiscard]] nsresult OnNormalCacheEntryAvailable(nsICacheEntry* aEntry,
+ bool aNew,
+ nsresult aResult);
+ [[nodiscard]] nsresult OpenOfflineCacheEntryForWriting();
+ [[nodiscard]] nsresult OnOfflineCacheEntryForWritingAvailable(
+ nsICacheEntry* aEntry, nsIApplicationCache* aAppCache, nsresult aResult);
+ [[nodiscard]] nsresult OnCacheEntryAvailableInternal(
+ nsICacheEntry* entry, bool aNew, nsIApplicationCache* aAppCache,
+ nsresult status);
+ [[nodiscard]] nsresult GenerateCacheKey(uint32_t postID, nsACString& key);
+ [[nodiscard]] nsresult UpdateExpirationTime();
+ [[nodiscard]] nsresult CheckPartial(nsICacheEntry* aEntry, int64_t* aSize,
+ int64_t* aContentLength);
+ bool ShouldUpdateOfflineCacheEntry();
+ [[nodiscard]] nsresult ReadFromCache(bool alreadyMarkedValid);
+ void CloseCacheEntry(bool doomOnFailure);
+ void CloseOfflineCacheEntry();
+ [[nodiscard]] nsresult InitCacheEntry();
+ void UpdateInhibitPersistentCachingFlag();
+ [[nodiscard]] nsresult InitOfflineCacheEntry();
+ [[nodiscard]] nsresult AddCacheEntryHeaders(nsICacheEntry* entry);
+ [[nodiscard]] nsresult FinalizeCacheEntry();
+ [[nodiscard]] nsresult InstallCacheListener(int64_t offset = 0);
+ [[nodiscard]] nsresult InstallOfflineCacheListener(int64_t offset = 0);
+ void MaybeInvalidateCacheEntryForSubsequentGet();
+ void AsyncOnExamineCachedResponse();
+
+ // Handle the bogus Content-Encoding Apache sometimes sends
+ void ClearBogusContentEncodingIfNeeded();
+
+ // 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 a single security header (STS or PKP), assumes
+ * some basic sanity checks have been applied to the channel. Called
+ * from ProcessSecurityHeaders.
+ */
+ [[nodiscard]] nsresult ProcessSingleSecurityHeader(
+ uint32_t aType, nsITransportSecurityInfo* aSecInfo, uint32_t aFlags);
+
+ 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,
+ bool checkingAppCacheEntry);
+
+ void SetPushedStreamTransactionAndId(
+ HttpTransactionShell* aTransWithPushedStream, uint32_t aPushedStreamId);
+
+ void MaybeWarnAboutAppCache();
+
+ void SetOriginHeader();
+ void SetDoNotTrack();
+
+ bool IsIsolated();
+
+ const nsCString& GetTopWindowOrigin();
+
+ already_AddRefed<nsChannelClassifier> GetOrCreateChannelClassifier();
+
+ // Start an internal redirect to a new InterceptedHttpChannel which will
+ // resolve in firing a ServiceWorker FetchEvent.
+ [[nodiscard]] nsresult RedirectToInterceptedChannel();
+
+ // 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.
+ nsCOMPtr<nsIApplicationCache> mApplicationCacheForWrite;
+ // auth specific data
+ nsCOMPtr<nsIHttpChannelAuthProvider> mAuthProvider;
+ nsCOMPtr<nsIURI> mRedirectURI;
+ 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;
+
+ uint64_t mLogicalOffset;
+
+ // 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<nsISupports> mCachedSecurityInfo;
+ uint32_t mPostID;
+ uint32_t mRequestTime;
+
+ nsCOMPtr<nsICacheEntry> mOfflineCacheEntry;
+ uint32_t mOfflineCacheLastModifiedTime;
+
+ nsTArray<StreamFilterRequest> mStreamFilterRequests;
+
+ mozilla::TimeStamp mOnStartRequestTimestamp;
+ // Timestamp of the time the channel was suspended.
+ mozilla::TimeStamp mSuspendTimestamp;
+ mozilla::TimeStamp mOnCacheEntryCheckTimestamp;
+#ifdef MOZ_GECKO_PROFILER
+ // For the profiler markers
+ mozilla::TimeStamp mLastStatusReported;
+#endif
+ // Total time the channel spent suspended. This value is reported to
+ // telemetry in nsHttpChannel::OnStartRequest().
+ uint32_t mSuspendTotalTime;
+
+ // If the channel is associated with a cache, and the URI matched
+ // a fallback namespace, this will hold the key for the fallback
+ // cache entry.
+ nsCString mFallbackKey;
+
+ friend class AutoRedirectVetoNotifier;
+ friend class HttpAsyncAborter<nsHttpChannel>;
+
+ uint32_t mRedirectType;
+
+ static const uint32_t WAIT_FOR_CACHE_ENTRY = 1;
+ static const uint32_t WAIT_FOR_OFFLINE_CACHE_ENTRY = 2;
+
+ bool mCacheOpenWithPriority;
+ uint32_t mCacheQueueSizeWhenOpen;
+
+ Atomic<bool, Relaxed> mCachedContentIsValid;
+ Atomic<bool> mIsAuthChannel;
+ Atomic<bool> mAuthRetryPending;
+
+ // 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 we are loading a fallback cache entry from the
+ // application cache.
+ (uint32_t, FallbackChannel, 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, FallingBack, 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, CacheEntriesToWaitFor, 2),
+ // 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 only when we have checked whether this channel has been isolated for
+ // anti-tracking purposes.
+ (uint32_t, HasBeenIsolatedChecked, 1),
+ // True only when we have determined this channel should be isolated for
+ // anti-tracking purposes. Can never ben true unless HasBeenIsolatedChecked
+ // is true.
+ (uint32_t, IsIsolated, 1),
+
+ // True only when we have computed the value of the top window origin.
+ (uint32_t, TopWindowOriginComputed, 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)
+ ))
+ // clang-format on
+
+ // The origin of the top window, only valid when TopWindowOriginComputed is
+ // true.
+ nsCString mTopWindowOrigin;
+
+ nsTArray<nsContinueRedirectionFunc> mRedirectFuncStack;
+
+ // Needed for accurate DNS timing
+ RefPtr<nsDNSPrefetch> mDNSPrefetch;
+
+ uint32_t mPushedStreamId;
+ 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;
+
+ [[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.
+ nsresult (nsHttpChannel::*mOnTailUnblock)();
+ // 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;
+
+ // 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;
+
+ // 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;
+
+ // Determines if it's possible and advisable to race the network request
+ // with the cache fetch, and proceeds to do so.
+ nsresult 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);
+
+ void SetHTTPSSVCRecord(already_AddRefed<nsIDNSHTTPSSVCRecord>&& aRecord);
+
+ // 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;
+ uint32_t mRaceDelay;
+ // If true then OnCacheEntryAvailable should ignore the entry, because
+ // SetupTransaction removed conditional headers and decisions made in
+ // OnCacheEntryCheck are no longer valid.
+ bool mIgnoreCacheEntry;
+ // Lock preventing SetupTransaction/MaybeCreateCacheEntryWhenRCWN and
+ // OnCacheEntryCheck being called at the same time.
+ mozilla::Mutex 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;
+
+ // 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;
+};
+
+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..df3fbbc6a6
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
@@ -0,0 +1,1698 @@
+/* -*- 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/Preferences.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "nsHttpChannelAuthProvider.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 "nsHttpBasicAuth.h"
+#include "nsHttpDigestAuth.h"
+#include "nsHttpNegotiateAuth.h"
+#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()
+ : mAuthChannel(nullptr),
+ mPort(-1),
+ mUsingSSL(false),
+ mProxyUsingSSL(false),
+ mIsPrivate(false),
+ mProxyAuthContinuationState(nullptr),
+ mAuthContinuationState(nullptr),
+ mProxyAuth(false),
+ mTriedProxyAuth(false),
+ mTriedHostAuth(false),
+ mSuppressDefensiveAuth(false),
+ mCrossOrigin(false),
+ mConnectionBased(false),
+ mHttpHandler(gHttpHandler) {}
+
+nsHttpChannelAuthProvider::~nsHttpChannelAuthProvider() {
+ 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.get(), 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
+ const char* proxyHost = ProxyHost();
+ if (proxyHost && UsingHttpProxy()) {
+ SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization, "http",
+ proxyHost, ProxyPort(),
+ nullptr, // 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.get(),
+ Host(), Port(), path.get(), 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_ABORT);
+ return NS_ERROR_ABORT;
+ }
+ 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;
+}
+
+// buf contains "domain\user"
+static void ParseUserDomain(char16_t* buf, const char16_t** user,
+ const char16_t** domain) {
+ char16_t* p = buf;
+ while (*p && *p != '\\') ++p;
+ if (!*p) return;
+ *p = '\0';
+ *domain = buf;
+ *user = p + 1;
+}
+
+// helper function for setting identity from raw user:pass
+static void SetIdent(nsHttpAuthIdentity& ident, uint32_t authFlags,
+ char16_t* userBuf, char16_t* passBuf) {
+ const char16_t* user = userBuf;
+ const char16_t* domain = nullptr;
+
+ if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN)
+ ParseUserDomain(userBuf, &user, &domain);
+
+ DebugOnly<nsresult> rv = ident.Set(domain, user, passBuf);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+// 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 char* scheme,
+ const char* host, int32_t port, const char* directory, const char* realm,
+ const char* challenge, const nsHttpAuthIdentity& ident,
+ nsCOMPtr<nsISupports>& sessionState, char** 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));
+#endif
+
+ return UpdateCache(auth, scheme, host, port, directory, realm, challenge,
+ ident, *result, generateFlags, sessionState, proxyAuth);
+}
+
+nsresult nsHttpChannelAuthProvider::UpdateCache(
+ nsIHttpAuthenticator* auth, const char* scheme, const char* host,
+ int32_t port, const char* directory, const char* realm,
+ const char* challenge, const nsHttpAuthIdentity& ident, const char* 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 : nullptr,
+ saveChallenge ? challenge : nullptr, 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.get(), 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;
+}
+
+nsresult nsHttpChannelAuthProvider::GetCredentials(const char* challenges,
+ bool proxyAuth,
+ nsCString& creds) {
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ nsAutoCString challenge;
+
+ 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 (const char* eol = challenges - 1; eol;) {
+ const char* p = eol + 1;
+
+ // get the challenge string (LF separated -- see nsHttpHeaderArray)
+ if ((eol = strchr(p, '\n')) != nullptr)
+ challenge.Assign(p, eol - p);
+ else
+ challenge.Assign(p);
+
+ rv = GetAuthenticator(challenge.get(), authType, getter_AddRefs(auth));
+ 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(challenge.get(), authType.get(),
+ 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 = challenge;
+ mRemainingChallenges = eol ? eol + 1 : nullptr;
+ 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, const char*& 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 char* challenge, const char* authType, bool proxyAuth,
+ nsIHttpAuthenticator* auth, nsCString& creds) {
+ LOG(
+ ("nsHttpChannelAuthProvider::GetCredentialsForChallenge "
+ "[this=%p channel=%p proxyAuth=%d challenges=%s]\n",
+ this, mAuthChannel, proxyAuth, challenge));
+
+ // 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(challenge, 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
+ const char* 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.get(), host, port,
+ realm.get(), 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, challenge, 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.get(), host, port, realm.get(),
+ suffix);
+ entry = nullptr;
+ ident->Clear();
+ }
+ } else if (!identFromURI ||
+ (nsCRT::strcmp(ident->User(), entry->Identity().User()) == 0 &&
+ !(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.
+ rv = ident->Set(entry->Identity());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ identFromURI = false;
+ if (entry->Creds()[0] != '\0') {
+ LOG((" using cached credentials!\n"));
+ creds.Assign(entry->Creds());
+ return entry->AddPath(path.get());
+ }
+ }
+ } 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.LowerCaseEqualsASCII(authType)) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_BASIC_SECURE : HTTP_AUTH_BASIC_INSECURE);
+ } else if ("digest"_ns.LowerCaseEqualsASCII(authType)) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_DIGEST_SECURE : HTTP_AUTH_DIGEST_INSECURE);
+ } else if ("ntlm"_ns.LowerCaseEqualsASCII(authType)) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_AUTH_TYPE_STATS,
+ UsingSSL() ? HTTP_AUTH_NTLM_SECURE : HTTP_AUTH_NTLM_INSECURE);
+ } else if ("negotiate"_ns.LowerCaseEqualsASCII(authType)) {
+ 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.get(), authType, 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.get(), host, port,
+ path.get(), realm.get(), challenge, *ident,
+ sessionStateGrip, getter_Copies(result));
+ if (NS_SUCCEEDED(rv)) creds = result;
+ 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);
+ bool sameOrigin = false;
+ loadingPrinc->IsSameOrigin(mURI, false, &sameOrigin);
+ mCrossOrigin = !sameOrigin;
+ }
+ }
+
+ 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 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 nsHttpChannelAuthProvider::GetAuthenticator(
+ const char* challenge, nsCString& authType, nsIHttpAuthenticator** auth) {
+ LOG(("nsHttpChannelAuthProvider::GetAuthenticator [this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ GetAuthType(challenge, authType);
+
+ // normalize to lowercase
+ ToLowerCase(authType);
+
+ nsCOMPtr<nsIHttpAuthenticator> authenticator;
+ if (authType.EqualsLiteral("negotiate")) {
+ authenticator = nsHttpNegotiateAuth::GetOrCreate();
+ } else if (authType.EqualsLiteral("basic")) {
+ authenticator = nsHttpBasicAuth::GetOrCreate();
+ } else if (authType.EqualsLiteral("digest")) {
+ authenticator = nsHttpDigestAuth::GetOrCreate();
+ } else if (authType.EqualsLiteral("ntlm")) {
+ authenticator = nsHttpNTLMAuth::GetOrCreate();
+ } else {
+ return NS_ERROR_FACTORY_NOT_REGISTERED;
+ }
+
+ MOZ_ASSERT(authenticator);
+ authenticator.forget(auth);
+
+ return NS_OK;
+}
+
+void nsHttpChannelAuthProvider::GetIdentityFromURI(uint32_t authFlags,
+ nsHttpAuthIdentity& ident) {
+ LOG(("nsHttpChannelAuthProvider::GetIdentityFromURI [this=%p channel=%p]\n",
+ this, mAuthChannel));
+
+ nsAutoString userBuf;
+ nsAutoString passBuf;
+
+ // XXX i18n
+ nsAutoCString buf;
+ mURI->GetUsername(buf);
+ if (!buf.IsEmpty()) {
+ NS_UnescapeURL(buf);
+ CopyUTF8toUTF16(buf, userBuf);
+ mURI->GetPassword(buf);
+ if (!buf.IsEmpty()) {
+ NS_UnescapeURL(buf);
+ CopyUTF8toUTF16(buf, passBuf);
+ }
+ }
+
+ if (!userBuf.IsEmpty()) {
+ SetIdent(ident, authFlags, (char16_t*)userBuf.get(),
+ (char16_t*)passBuf.get());
+ }
+}
+
+void nsHttpChannelAuthProvider::ParseRealm(const char* challenge,
+ 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.
+ //
+
+ const char* p = PL_strcasestr(challenge, "realm=");
+ if (p) {
+ bool has_quote = false;
+ p += 6;
+ if (*p == '"') {
+ has_quote = true;
+ p++;
+ }
+
+ const char* end;
+ if (has_quote) {
+ end = p;
+ while (*end) {
+ if (*end == '\\') {
+ // escaped character, store that one instead if not zero
+ if (!*++end) break;
+ } else if (*end == '\"')
+ // end of string
+ break;
+
+ realm.Append(*end);
+ ++end;
+ }
+ } else {
+ // realm given without quotes
+ end = strchr(p, ' ');
+ if (end)
+ realm.Assign(p, end - p);
+ else
+ realm.Assign(p);
+ }
+ }
+}
+
+class nsHTTPAuthInformation : public nsAuthInformationHolder {
+ public:
+ nsHTTPAuthInformation(uint32_t aFlags, const nsString& aRealm,
+ const nsCString& aAuthType)
+ : nsAuthInformationHolder(aFlags, aRealm, aAuthType) {}
+
+ void SetToHttpAuthIdentity(uint32_t authFlags, nsHttpAuthIdentity& identity);
+};
+
+void nsHTTPAuthInformation::SetToHttpAuthIdentity(
+ uint32_t authFlags, nsHttpAuthIdentity& identity) {
+ DebugOnly<nsresult> rv =
+ identity.Set(Domain().get(), User().get(), Password().get());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+nsresult nsHttpChannelAuthProvider::PromptForIdentity(
+ uint32_t level, bool proxyAuth, const char* realm, const char* 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, nsDependentCString(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;
+
+ const char* 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.get(), 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.get(), host, port,
+ realm.get(), suffix, &entry);
+
+ nsCOMPtr<nsISupports> sessionStateGrip;
+ if (entry) sessionStateGrip = entry->mMetaData;
+
+ nsAuthInformationHolder* holder =
+ static_cast<nsAuthInformationHolder*>(aAuthInfo);
+ rv = ident->Set(holder->Domain().get(), holder->User().get(),
+ holder->Password().get());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ nsAutoCString unused;
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ rv = GetAuthenticator(mCurrentChallenge.get(), 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.get(), host, port,
+ path.get(), realm.get(), mCurrentChallenge.get(),
+ *ident, sessionStateGrip, getter_Copies(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.get(), 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 char* 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.get(), unused, getter_AddRefs(auth));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char* host;
+ int32_t port;
+ nsHttpAuthIdentity* ident;
+ nsAutoCString directory, scheme;
+ nsISupports** unusedContinuationState;
+
+ // Get realm from challenge
+ nsAutoCString realm;
+ ParseRealm(mCurrentChallenge.get(), realm);
+
+ rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, directory, ident,
+ unusedContinuationState);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = UpdateCache(auth, scheme.get(), host, port, directory.get(), realm.get(),
+ mCurrentChallenge.get(), *ident, aGeneratedCreds, aFlags,
+ aSessionState, mProxyAuth);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mCurrentChallenge.Truncate();
+
+ rv = ContinueOnAuthAvailable(nsDependentCString(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/embedcomp/prompt-service;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, nsHttpAtom header, const char* scheme,
+ const char* host, int32_t port, const char* 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 (nsCRT::strcmp(ident.User(), entry->User()) == 0) {
+ uint32_t loadFlags;
+ if (NS_SUCCEEDED(mAuthChannel->GetLoadFlags(&loadFlags)) &&
+ !(loadFlags & nsIChannel::LOAD_EXPLICIT_CREDENTIALS)) {
+ ident.Clear();
+ }
+ }
+ }
+ bool identFromURI;
+ if (ident.IsEmpty()) {
+ rv = ident.Set(entry->Identity());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ identFromURI = false;
+ } else
+ identFromURI = true;
+
+ nsCString temp; // this must have the same lifetime as creds
+ const char* creds = entry->Creds();
+ const char* challenge = entry->Challenge();
+ // 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[0] || identFromURI) && challenge[0]) {
+ nsCOMPtr<nsIHttpAuthenticator> auth;
+ nsAutoCString unused;
+ rv = GetAuthenticator(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(), challenge, ident,
+ entry->mMetaData, getter_Copies(temp));
+ if (NS_SUCCEEDED(rv)) creds = temp.get();
+
+ // make sure the continuation state is null since we do not
+ // support mixing preemptive and 'multirequest' authentication.
+ NS_IF_RELEASE(*continuationState);
+ }
+ }
+ if (creds[0]) {
+ LOG((" adding \"%s\" request header\n", header.get()));
+ if (header == nsHttp::Proxy_Authorization) {
+ rv = mAuthChannel->SetProxyCredentials(nsDependentCString(creds));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ } else {
+ rv = mAuthChannel->SetWWWCredentials(nsDependentCString(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..4bff58ae96
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.h
@@ -0,0 +1,186 @@
+/* -*- 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 "nsCRT.h"
+#include "nsICancelableRunnable.h"
+
+class nsIHttpAuthenticableChannel;
+class nsIHttpAuthenticator;
+class nsIURI;
+
+namespace mozilla {
+namespace net {
+
+class nsHttpHandler;
+
+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 char* ProxyHost() const {
+ return mProxyInfo ? mProxyInfo->Host().get() : nullptr;
+ }
+
+ int32_t ProxyPort() const { return mProxyInfo ? mProxyInfo->Port() : -1; }
+
+ const char* Host() const { return mHost.get(); }
+ 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 char* scheme,
+ const char* host, int32_t port, const char* dir, const char* realm,
+ const char* challenge, const nsHttpAuthIdentity& ident,
+ nsCOMPtr<nsISupports>& session, char** result);
+ [[nodiscard]] nsresult GetAuthenticator(const char* challenge,
+ nsCString& scheme,
+ nsIHttpAuthenticator** auth);
+ void ParseRealm(const char* challenge, 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 char* challenges, bool proxyAuth,
+ nsCString& creds);
+ [[nodiscard]] nsresult GetCredentialsForChallenge(const char* challenge,
+ const char* scheme,
+ bool proxyAuth,
+ nsIHttpAuthenticator* auth,
+ nsCString& creds);
+ [[nodiscard]] nsresult PromptForIdentity(uint32_t level, bool proxyAuth,
+ const char* realm,
+ const char* authType,
+ uint32_t authFlags,
+ nsHttpAuthIdentity&);
+
+ bool ConfirmAuth(const char* bundleKey, bool doYesNoPrompt);
+ void SetAuthorizationHeader(nsHttpAuthCache*, nsHttpAtom header,
+ const char* scheme, const char* host,
+ int32_t port, const char* 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, const char*& 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 char* aScheme, const char* aHost,
+ int32_t aPort, const char* aDirectory, const char* aRealm,
+ const char* aChallenge, const nsHttpAuthIdentity& aIdent,
+ const char* aCreds, uint32_t aGenerateFlags, nsISupports* aSessionState,
+ bool aProxyAuth);
+
+ private:
+ nsIHttpAuthenticableChannel* mAuthChannel; // weak ref
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsProxyInfo> mProxyInfo;
+ nsCString mHost;
+ int32_t mPort;
+ bool mUsingSSL;
+ bool mProxyUsingSSL;
+ bool mIsPrivate;
+
+ nsISupports* mProxyAuthContinuationState;
+ nsCString mProxyAuthType;
+ nsISupports* mAuthContinuationState;
+ 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..76bbd82805
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChunkedDecoder.cpp
@@ -0,0 +1,168 @@
+/* -*- 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 "plstr.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 = PL_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..a1fb291df2
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpChunkedDecoder.h
@@ -0,0 +1,54 @@
+/* -*- 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()
+ : mTrailers(nullptr),
+ mChunkRemaining(0),
+ mReachedEOF(false),
+ mWaitEOF(false) {}
+ ~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* countRead);
+
+ private:
+ UniquePtr<nsHttpHeaderArray> mTrailers;
+ uint32_t mChunkRemaining;
+ nsCString mLineBuf; // may hold a partial line
+ bool mReachedEOF;
+ bool mWaitEOF;
+};
+
+} // 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..b01f02394b
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -0,0 +1,2735 @@
+/* -*- 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/ChaosMode.h"
+#include "mozilla/Telemetry.h"
+#include "nsHttpConnection.h"
+#include "nsHttpHandler.h"
+#include "nsHttpRequestHead.h"
+#include "nsHttpResponseHead.h"
+#include "nsIClassOfService.h"
+#include "nsIOService.h"
+#include "nsISocketTransport.h"
+#include "nsSocketTransportService2.h"
+#include "nsISSLSocketControl.h"
+#include "nsISupportsPriority.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsPreloadedStream.h"
+#include "nsProxyRelease.h"
+#include "nsSocketTransport2.h"
+#include "nsStringStream.h"
+#include "nsITransportSecurityInfo.h"
+#include "mozpkix/pkixnss.h"
+#include "sslt.h"
+#include "NSSErrorsService.h"
+#include "TunnelUtils.h"
+#include "TCPFastOpenLayer.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpConnection <public>
+//-----------------------------------------------------------------------------
+
+nsHttpConnection::nsHttpConnection()
+ : mSocketInCondition(NS_ERROR_NOT_INITIALIZED),
+ mSocketOutCondition(NS_ERROR_NOT_INITIALIZED),
+ mHttpHandler(gHttpHandler),
+ mLastReadTime(0),
+ mLastWriteTime(0),
+ mMaxHangTime(0),
+ mConsiderReusedAfterInterval(0),
+ mConsiderReusedAfterEpoch(0),
+ mCurrentBytesRead(0),
+ mMaxBytesRead(0),
+ mTotalBytesRead(0),
+ mContentBytesWritten(0),
+ mUrgentStartPreferred(false),
+ mUrgentStartPreferredKnown(false),
+ mConnectedTransport(false),
+ mKeepAlive(true) // assume to keep-alive by default
+ ,
+ mKeepAliveMask(true),
+ mDontReuse(false),
+ mIsReused(false),
+ mCompletedProxyConnect(false),
+ mLastTransactionExpectedNoContent(false),
+ mIdleMonitoring(false),
+ mProxyConnectInProgress(false),
+ mInSpdyTunnel(false),
+ mForcePlainText(false),
+ mTrafficCount(0),
+ mTrafficStamp(false),
+ mHttp1xTransactionCount(0),
+ mRemainingConnectionUses(0xffffffff),
+ mNPNComplete(false),
+ mSetupSSLCalled(false),
+ mUsingSpdyVersion(SpdyVersion::NONE),
+ mPriority(nsISupportsPriority::PRIORITY_NORMAL),
+ mReportedSpdy(false),
+ mEverUsedSpdy(false),
+ mLastHttpResponseVersion(HttpVersion::v1_1),
+ mDefaultTimeoutFactor(1),
+ mResponseTimeoutEnabled(false),
+ mTCPKeepaliveConfig(kTCPKeepaliveDisabled),
+ mForceSendPending(false),
+ m0RTTChecked(false),
+ mWaitingFor0RTTResponse(false),
+ mContentBytesWritten0RTT(0),
+ mEarlyDataNegotiated(false),
+ mDid0RTTSpdy(false),
+ mFastOpen(false),
+ mFastOpenStatus(TFO_NOT_SET),
+ mForceSendDuringFastOpenPending(false),
+ mReceivedSocketWouldBlockDuringFastOpen(false),
+ mCheckNetworkStallsWithTFO(false),
+ mLastRequestBytesSentTime(0) {
+ 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;
+ }
+
+ if ((mFastOpenStatus != TFO_FAILED) && (mFastOpenStatus != TFO_HTTP) &&
+ (((mFastOpenStatus > TFO_DISABLED_CONNECT) &&
+ (mFastOpenStatus < TFO_BACKUP_CONN)) ||
+ gHttpHandler->UseFastOpen())) {
+ // TFO_FAILED will be reported in the replacement connection with more
+ // details.
+ // Otherwise report only if TFO is enabled and supported.
+ // If TFO is disabled, report only connections ha cause it to be disabled,
+ // e.g. TFO_FAILED_NET_TIMEOUT, etc.
+ Telemetry::Accumulate(Telemetry::TCP_FAST_OPEN_3, mFastOpenStatus);
+ }
+}
+
+nsresult nsHttpConnection::Init(
+ nsHttpConnectionInfo* info, uint16_t maxHangTime,
+ nsISocketTransport* transport, nsIAsyncInputStream* instream,
+ nsIAsyncOutputStream* outstream, bool connectedTransport,
+ nsIInterfaceRequestor* callbacks, PRIntervalTime rtt) {
+ LOG1(("nsHttpConnection::Init this=%p sockettransport=%p", this, transport));
+ NS_ENSURE_ARG_POINTER(info);
+ NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
+
+ mConnectedTransport = connectedTransport;
+ mConnInfo = info;
+ MOZ_ASSERT(mConnInfo);
+
+ mLastWriteTime = mLastReadTime = PR_IntervalNow();
+ mRtt = rtt;
+ mMaxHangTime = PR_SecondsToInterval(maxHangTime);
+
+ mSocketTransport = transport;
+ mSocketIn = instream;
+ mSocketOut = outstream;
+
+ // See explanation for non-strictness of this operation in
+ // SetSecurityCallbacks.
+ mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(
+ "nsHttpConnection::mCallbacks", callbacks, false);
+
+ mSocketTransport->SetEventSink(this, nullptr);
+ mSocketTransport->SetSecurityCallbacks(this);
+
+ return NS_OK;
+}
+
+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;
+}
+
+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");
+
+ // 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) {
+ 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;
+ mSpdySession =
+ ASpdySession::NewSpdySession(spdyVersion, mSocketTransport, true);
+
+ 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(nsISSLSocketControl* 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;
+ gHttpHandler->ConnMgr()->ReportSpdyConnection(this, true);
+ }
+
+ // 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) {
+ 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));
+ MOZ_ASSERT(mProxyConnectStream);
+
+ mProxyConnectStream = nullptr;
+ mCompletedProxyConnect = true;
+ mProxyConnectInProgress = false;
+ }
+
+ nsresult rv = NS_OK;
+ bool spdyProxy = mConnInfo->UsingHttpsProxy() && !mTLSFilter;
+ 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) {
+ 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;
+
+ if (!mTLSFilter) {
+ mTransaction = mSpdySession;
+ } else {
+ rv = mTLSFilter->SetProxiedTransaction(mSpdySession);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("nsHttpConnection::StartSpdy [%p] SetProxiedTransaction failed"
+ " rv[0x%x]",
+ this, static_cast<uint32_t>(rv)));
+ }
+ }
+ if (mDontReuse) {
+ mSpdySession->DontReuse();
+ }
+}
+
+bool nsHttpConnection::EnsureNPNComplete(nsresult& aOut0RTTWriteHandshakeValue,
+ uint32_t& aOut0RTTBytesWritten) {
+ // If for some reason the components to check on NPN aren't available,
+ // this function will just return true to continue on and disable SPDY
+
+ aOut0RTTWriteHandshakeValue = NS_OK;
+ aOut0RTTBytesWritten = 0;
+
+ MOZ_ASSERT(mSocketTransport);
+ if (!mSocketTransport) {
+ // this cannot happen
+ mNPNComplete = true;
+ return true;
+ }
+
+ if (mNPNComplete) {
+ return true;
+ }
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsISupports> securityInfo;
+ nsCOMPtr<nsITransportSecurityInfo> info;
+ nsCOMPtr<nsISSLSocketControl> ssl;
+ nsAutoCString negotiatedNPN;
+
+ GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (!securityInfo) {
+ goto npnComplete;
+ }
+
+ ssl = do_QueryInterface(securityInfo, &rv);
+ if (NS_FAILED(rv)) goto npnComplete;
+
+ info = do_QueryInterface(securityInfo, &rv);
+ if (NS_FAILED(rv)) goto npnComplete;
+
+ if (!m0RTTChecked) {
+ // We reuse m0RTTChecked. We want to send this status only once.
+ mTransaction->OnTransportStatus(mSocketTransport,
+ NS_NET_STATUS_TLS_HANDSHAKE_STARTING, 0);
+ }
+
+ rv = info->GetNegotiatedNPN(negotiatedNPN);
+ if (!m0RTTChecked && (rv == NS_ERROR_NOT_CONNECTED) &&
+ !mConnInfo->UsingProxy()) {
+ // 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.
+ m0RTTChecked = true;
+ nsresult rvEarlyAlpn = ssl->GetAlpnEarlySelection(mEarlyNegotiatedALPN);
+ if (NS_FAILED(rvEarlyAlpn)) {
+ // if ssl->DriveHandshake() has never been called the value
+ // for AlpnEarlySelection is still not set. So call it here and
+ // check again.
+ LOG1(
+ ("nsHttpConnection::EnsureNPNComplete %p - "
+ "early selected alpn not available, we will try one more time.",
+ this));
+ // Let's do DriveHandshake again.
+ rv = ssl->DriveHandshake();
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ goto npnComplete;
+ }
+
+ // Check NegotiatedNPN first.
+ rv = info->GetNegotiatedNPN(negotiatedNPN);
+ if (rv == NS_ERROR_NOT_CONNECTED) {
+ rvEarlyAlpn = ssl->GetAlpnEarlySelection(mEarlyNegotiatedALPN);
+ }
+ }
+
+ if (NS_FAILED(rvEarlyAlpn)) {
+ LOG1(
+ ("nsHttpConnection::EnsureNPNComplete %p - "
+ "early selected alpn not available",
+ this));
+ mEarlyDataNegotiated = false;
+ } else {
+ LOG1(
+ ("nsHttpConnection::EnsureNPNComplete %p -"
+ "early selected alpn: %s",
+ this, mEarlyNegotiatedALPN.get()));
+ uint32_t infoIndex;
+ const SpdyInformation* info = gHttpHandler->SpdyInfo();
+ if (NS_FAILED(info->GetNPNIndex(mEarlyNegotiatedALPN, &infoIndex))) {
+ // This is the HTTP/1 case.
+ // Check if early-data is allowed for this transaction.
+ if (mTransaction->Do0RTT()) {
+ LOG(
+ ("nsHttpConnection::EnsureNPNComplete [this=%p] - We "
+ "can do 0RTT (http/1)!",
+ this));
+ mWaitingFor0RTTResponse = true;
+ }
+ } else {
+ // We have h2, we can at least 0-RTT the preamble and opening
+ // SETTINGS, etc, and maybe some of the first request
+ LOG(
+ ("nsHttpConnection::EnsureNPNComplete [this=%p] - Starting "
+ "0RTT for h2!",
+ this));
+ mWaitingFor0RTTResponse = true;
+ Start0RTTSpdy(info->Version[infoIndex]);
+ }
+ mEarlyDataNegotiated = true;
+ }
+ }
+
+ if (rv == NS_ERROR_NOT_CONNECTED) {
+ if (mWaitingFor0RTTResponse) {
+ aOut0RTTWriteHandshakeValue = mTransaction->ReadSegments(
+ this, nsIOService::gDefaultSegmentSize, &aOut0RTTBytesWritten);
+ if (NS_FAILED(aOut0RTTWriteHandshakeValue) &&
+ aOut0RTTWriteHandshakeValue != NS_BASE_STREAM_WOULD_BLOCK) {
+ goto npnComplete;
+ }
+ LOG(
+ ("nsHttpConnection::EnsureNPNComplete [this=%p] - written %d "
+ "bytes during 0RTT",
+ this, aOut0RTTBytesWritten));
+ mContentBytesWritten0RTT += aOut0RTTBytesWritten;
+ if (mSocketOutCondition == NS_BASE_STREAM_WOULD_BLOCK) {
+ mReceivedSocketWouldBlockDuringFastOpen = true;
+ }
+ }
+
+ rv = ssl->DriveHandshake();
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ goto npnComplete;
+ }
+
+ return false;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ LOG1(("nsHttpConnection::EnsureNPNComplete %p [%s] negotiated to '%s'%s\n",
+ this, mConnInfo->HashKey().get(), negotiatedNPN.get(),
+ mTLSFilter ? " [Double Tunnel]" : ""));
+
+ int16_t tlsVersion;
+ ssl->GetSSLVersionUsed(&tlsVersion);
+ mConnInfo->SetLessThanTls13(
+ (tlsVersion < nsISSLSocketControl::TLS_VERSION_1_3) &&
+ (tlsVersion != nsISSLSocketControl::SSL_VERSION_UNKNOWN));
+
+ bool earlyDataAccepted = false;
+ if (mWaitingFor0RTTResponse) {
+ // Check if early data has been accepted.
+ nsresult rvEarlyData = ssl->GetEarlyDataAccepted(&earlyDataAccepted);
+ LOG(
+ ("nsHttpConnection::EnsureNPNComplete [this=%p] - early data "
+ "that was sent during 0RTT %s been accepted [rv=%" PRIx32 "].",
+ this, earlyDataAccepted ? "has" : "has not",
+ static_cast<uint32_t>(rv)));
+
+ if (NS_FAILED(rvEarlyData) ||
+ NS_FAILED(mTransaction->Finish0RTT(
+ !earlyDataAccepted, negotiatedNPN != mEarlyNegotiatedALPN))) {
+ LOG(
+ ("nsHttpConection::EnsureNPNComplete [this=%p] closing transaction "
+ "%p",
+ this, mTransaction.get()));
+ mTransaction->Close(NS_ERROR_NET_RESET);
+ goto npnComplete;
+ }
+ }
+
+ // Send the 0RTT telemetry only for tls1.3
+ if (tlsVersion > nsISSLSocketControl::TLS_VERSION_1_2) {
+ Telemetry::Accumulate(
+ Telemetry::TLS_EARLY_DATA_NEGOTIATED,
+ (!mEarlyDataNegotiated)
+ ? TLS_EARLY_DATA_NOT_AVAILABLE
+ : ((mWaitingFor0RTTResponse)
+ ? TLS_EARLY_DATA_AVAILABLE_AND_USED
+ : TLS_EARLY_DATA_AVAILABLE_BUT_NOT_USED));
+ if (mWaitingFor0RTTResponse) {
+ Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_ACCEPTED,
+ earlyDataAccepted);
+ }
+ if (earlyDataAccepted) {
+ Telemetry::Accumulate(Telemetry::TLS_EARLY_DATA_BYTES_WRITTEN,
+ mContentBytesWritten0RTT);
+ }
+ }
+ mWaitingFor0RTTResponse = false;
+
+ if (!earlyDataAccepted) {
+ LOG(
+ ("nsHttpConnection::EnsureNPNComplete [this=%p] early data not "
+ "accepted",
+ this));
+ if (mTransaction->QueryNullTransaction() &&
+ (mBootstrappedTimings.secureConnectionStart.IsNull() ||
+ mBootstrappedTimings.tcpConnectEnd.IsNull())) {
+ // if TFO is used some socket event will be sent after
+ // mBootstrappedTimings has been set. therefore we should
+ // update them.
+ mBootstrappedTimings.secureConnectionStart =
+ mTransaction->QueryNullTransaction()->GetSecureConnectionStart();
+ mBootstrappedTimings.tcpConnectEnd =
+ mTransaction->QueryNullTransaction()->GetTcpConnectEnd();
+ }
+ uint32_t infoIndex;
+ const SpdyInformation* info = gHttpHandler->SpdyInfo();
+ if (NS_SUCCEEDED(info->GetNPNIndex(negotiatedNPN, &infoIndex))) {
+ StartSpdy(ssl, info->Version[infoIndex]);
+ }
+ } else {
+ LOG(("nsHttpConnection::EnsureNPNComplete [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::EnsureNPNComplete [this=%p] - finishing "
+ "StartSpdy for 0rtt spdy session %p",
+ this, mSpdySession.get()));
+ StartSpdy(ssl, mSpdySession->SpdyVersion());
+ }
+ }
+
+ Telemetry::Accumulate(Telemetry::SPDY_NPN_CONNECT, UsingSpdy());
+ }
+
+npnComplete:
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] setting complete to true",
+ this));
+ mNPNComplete = true;
+
+ 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->QueryNullTransaction() &&
+ (mBootstrappedTimings.secureConnectionStart.IsNull() ||
+ mBootstrappedTimings.tcpConnectEnd.IsNull())) {
+ // if TFO is used some socket event will be sent after
+ // mBootstrappedTimings has been set. therefore we should
+ // update them.
+ mBootstrappedTimings.secureConnectionStart =
+ mTransaction->QueryNullTransaction()->GetSecureConnectionStart();
+ mBootstrappedTimings.tcpConnectEnd =
+ mTransaction->QueryNullTransaction()->GetTcpConnectEnd();
+ }
+
+ if (securityInfo) {
+ mBootstrappedTimings.connectEnd = TimeStamp::Now();
+ }
+
+ if (mWaitingFor0RTTResponse) {
+ // Didn't get 0RTT OK, back out of the "attempting 0RTT" state
+ mWaitingFor0RTTResponse = false;
+ LOG(("nsHttpConnection::EnsureNPNComplete [this=%p] 0rtt failed", this));
+ if (NS_FAILED(mTransaction->Finish0RTT(
+ true, negotiatedNPN != mEarlyNegotiatedALPN))) {
+ mTransaction->Close(NS_ERROR_NET_RESET);
+ }
+ mContentBytesWritten0RTT = 0;
+ }
+
+ if (mDid0RTTSpdy && negotiatedNPN != mEarlyNegotiatedALPN) {
+ // Reset the work done by Start0RTTSpdy
+ LOG((
+ "nsHttpConnection::EnsureNPNComplete [this=%p] resetting Start0RTTSpdy",
+ this));
+ 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;
+ }
+
+ if (rv == psm::GetXPCOMFromNSSError(
+ mozilla::pkix::MOZILLA_PKIX_ERROR_MITM_DETECTED)) {
+ gSocketTransportService->SetNotTrustedMitmDetected();
+ }
+ return true;
+}
+
+nsresult nsHttpConnection::OnTunnelNudged(TLSFilterTransaction* trans) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnection::OnTunnelNudged %p\n", this));
+ if (trans != mTLSFilter) {
+ return NS_OK;
+ }
+ LOG(("nsHttpConnection::OnTunnelNudged %p Calling OnSocketWritable\n", this));
+ return OnSocketWritable();
+}
+
+// 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()) {
+ // For QUIC and TFO we have nsHttpConneciton before the actual connection
+ // has been establish so wait fo TFO and TLS handshake to be finished before
+ // we mark the connection 'experienced'.
+ if (!mFastOpen && mNPNComplete) {
+ mExperienced = true;
+ }
+ if (mBootstrappedTimingsSet) {
+ mBootstrappedTimingsSet = false;
+ nsHttpTransaction* hTrans = trans->QueryHttpTransaction();
+ if (hTrans) {
+ hTrans->BootstrapTimings(mBootstrappedTimings);
+ SetUrgentStartPreferred(hTrans->ClassOfService() &
+ nsIClassOfService::UrgentStart);
+ }
+ }
+ mBootstrappedTimings = TimingStruct();
+ }
+
+ if (caps & NS_HTTP_LARGE_KEEPALIVE) {
+ mDefaultTimeoutFactor = 10; // don't ever lower
+ }
+
+ mTransactionCaps = caps;
+ mPriority = pri;
+ if (mTransaction && (mUsingSpdyVersion != SpdyVersion::NONE)) {
+ return AddTransaction(trans, pri);
+ }
+
+ NS_ENSURE_ARG_POINTER(trans);
+ NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS);
+
+ // If TCP fast Open has been used and conection was idle for some time
+ // we will be cautious and watch out for bug 1395494.
+ if (mNPNComplete && (mFastOpenStatus == TFO_DATA_SENT) &&
+ gHttpHandler
+ ->CheckIfConnectionIsStalledOnlyIfIdleForThisAmountOfSeconds() &&
+ IdleTime() >=
+ gHttpHandler
+ ->CheckIfConnectionIsStalledOnlyIfIdleForThisAmountOfSeconds()) {
+ // If a connection was using the TCP FastOpen and it was idle for a
+ // long time we should check for stalls like bug 1395494.
+ mCheckNetworkStallsWithTFO = true;
+ // Also reset last write. We should start measuring a stall time only
+ // after we really write a request to the network.
+ mLastRequestBytesSentTime = 0;
+ }
+ // 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 (!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);
+ SetupSSL();
+
+ // take ownership of the transaction
+ mTransaction = trans;
+
+ 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);
+
+ // need to handle HTTP CONNECT tunnels if this is the first time if
+ // we are tunneling through a proxy
+ nsresult rv = NS_OK;
+ if (mTransaction->ConnectionInfo()->UsingConnect() &&
+ !mCompletedProxyConnect) {
+ rv = SetupProxyConnect();
+ if (NS_FAILED(rv)) goto failed_activation;
+ mProxyConnectInProgress = true;
+ }
+
+ // 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)));
+ }
+
+ if (mTLSFilter) {
+ RefPtr<NullHttpTransaction> baseTrans(do_QueryReferent(mWeakTrans));
+ rv = mTLSFilter->SetProxiedTransaction(trans, baseTrans);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mTransaction->ConnectionInfo()->UsingConnect()) {
+ SpdyConnectTransaction* trans =
+ baseTrans ? baseTrans->QuerySpdyConnectTransaction() : nullptr;
+ if (trans && !trans->IsWebsocket()) {
+ // If we are here, the tunnel is already established. Let the
+ // transaction know that proxy connect is successful.
+ mTransaction->OnProxyConnectComplete(200);
+ }
+ }
+ mTransaction = mTLSFilter;
+ }
+
+ trans->OnActivated();
+
+ rv = OnOutputStreamReady(mSocketOut);
+
+failed_activation:
+ if (NS_FAILED(rv)) {
+ mTransaction = nullptr;
+ }
+
+ return rv;
+}
+
+void nsHttpConnection::SetupSSL() {
+ LOG1(("nsHttpConnection::SetupSSL %p caps=0x%X %s\n", this, mTransactionCaps,
+ 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() || mForcePlainText) {
+ 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 (mInSpdyTunnel) {
+ rv = InitSSLParams(false, true);
+ } else {
+ bool usingHttpsProxy = mConnInfo->UsingHttpsProxy();
+ rv = InitSSLParams(usingHttpsProxy, usingHttpsProxy);
+ }
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+// 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 nsHttpConnection::SetupNPNList(nsISSLSocketControl* ssl,
+ uint32_t caps) {
+ nsTArray<nsCString> protocolArray;
+
+ nsCString npnToken = mConnInfo->GetNPNToken();
+ if (npnToken.IsEmpty()) {
+ // 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 (gHttpHandler->IsSpdyEnabled() && !(caps & NS_HTTP_DISALLOW_SPDY)) {
+ LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection"));
+ const SpdyInformation* info = gHttpHandler->SpdyInfo();
+ for (uint32_t index = SpdyInformation::kCount; index > 0; --index) {
+ if (info->ProtocolEnabled(index - 1) &&
+ info->ALPNCallbacks[index - 1](ssl)) {
+ protocolArray.AppendElement(info->VersionString[index - 1]);
+ }
+ }
+ }
+ } else {
+ LOG(("nsHttpConnection::SetupSSL limiting NPN selection to %s",
+ npnToken.get()));
+ protocolArray.AppendElement(npnToken);
+ }
+
+ nsresult rv = ssl->SetNPNList(protocolArray);
+ LOG(("nsHttpConnection::SetupNPNList %p %" PRIx32 "\n", this,
+ static_cast<uint32_t>(rv)));
+ 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 && !mTLSFilter;
+ 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 && mTLSFilter) {
+ httpTransaction->OnProxyConnectComplete(200);
+ }
+
+ bool isWebsocket = false;
+ nsHttpTransaction* trans = httpTransaction->QueryHttpTransaction();
+ if (trans) {
+ isWebsocket = trans->IsWebsocketUpgrade();
+ MOZ_ASSERT(!isWebsocket || !needTunnel, "Websocket and tunnel?!");
+ }
+
+ LOG(("nsHttpConnection::AddTransaction [this=%p] for %s%s", this,
+ mSpdySession ? "SPDY" : "QUIC",
+ needTunnel ? " over tunnel" : (isWebsocket ? " websocket" : "")));
+
+ if (mSpdySession) {
+ if (!mSpdySession->AddStream(httpTransaction, priority, needTunnel,
+ isWebsocket, mCallbacks)) {
+ MOZ_ASSERT(false); // this cannot happen!
+ httpTransaction->Close(NS_ERROR_ABORT);
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ Unused << ResumeSend();
+ return NS_OK;
+}
+
+void nsHttpConnection::Close(nsresult reason, bool aIsShutdown) {
+ LOG(("nsHttpConnection::Close [this=%p reason=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(reason)));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ // 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());
+ }
+ }
+
+ if (NS_FAILED(reason)) {
+ if (mIdleMonitoring) EndIdleMonitoring();
+
+ mTLSFilter = nullptr;
+
+ // 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 (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) {
+ 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;
+ }
+}
+
+// called on the socket thread
+nsresult nsHttpConnection::InitSSLParams(bool connectingToProxy,
+ bool proxyStartSSL) {
+ LOG(("nsHttpConnection::InitSSLParams [this=%p] connectingToProxy=%d\n", this,
+ connectingToProxy));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> securityInfo;
+ GetSecurityInfo(getter_AddRefs(securityInfo));
+ if (!securityInfo) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (proxyStartSSL) {
+ rv = ssl->ProxyStartSSL();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (NS_SUCCEEDED(SetupNPNList(ssl, mTransactionCaps))) {
+ LOG(("InitSSLParams Setting up SPDY Negotiation OK"));
+ mNPNComplete = false;
+ }
+
+ return NS_OK;
+}
+
+void nsHttpConnection::DontReuse() {
+ LOG(("nsHttpConnection::DontReuse %p spdysession=%p\n", this,
+ mSpdySession.get()));
+ mKeepAliveMask = false;
+ mKeepAlive = false;
+ mDontReuse = true;
+ mIdleTimeout = 0;
+ if (mSpdySession) {
+ mSpdySession->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.
+ SetupSSL();
+
+ 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();
+ 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() - mLastWriteTime) < k1000ms)) {
+ Close(NS_ERROR_NET_RESET);
+ *reset = true;
+ return NS_OK;
+ }
+
+ // 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
+ if (explicitKeepAlive)
+ mKeepAlive = true;
+ else
+ mKeepAlive = false;
+ } 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 = PL_strcasestr(keepAlive.get(), "timeout=");
+ if (cp)
+ mIdleTimeout = PR_SecondsToInterval((uint32_t)atoi(cp + 8));
+ else
+ mIdleTimeout = gHttpHandler->IdleTimeout() * mDefaultTimeoutFactor;
+
+ cp = PL_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;
+ }
+
+ // 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.
+ bool itWasProxyConnect = !!mProxyConnectStream;
+ if (mProxyConnectStream) {
+ MOZ_ASSERT(mUsingSpdyVersion == SpdyVersion::NONE,
+ "SPDY NPN Complete while using proxy connect stream");
+ 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 = 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.
+ mNPNComplete = true;
+ }
+ }
+ mCompletedProxyConnect = true;
+ mProxyConnectInProgress = false;
+ 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();
+ }
+ }
+
+ nsAutoCString upgradeReq;
+ bool hasUpgradeReq =
+ NS_SUCCEEDED(requestHead->GetHeader(nsHttp::Upgrade, upgradeReq));
+ // 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 (!itWasProxyConnect && hasUpgradeReq && responseStatus != 401 &&
+ responseStatus != 407 && !mSpdySession) {
+ LOG(("HTTP Upgrade in play - disable keepalive for http/1.x\n"));
+ DontReuse();
+ }
+
+ if (responseStatus == 101) {
+ 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()));
+ }
+ }
+
+ mLastHttpResponseVersion = responseHead->Version();
+
+ return NS_OK;
+}
+
+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)));
+ }
+ }
+
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+
+ // The nsHttpConnection will go away soon, so if there is a TLS Filter
+ // being used (e.g. for wss CONNECT tunnel from a proxy connected to
+ // via https) that filter needs to take direct control of the
+ // streams
+ if (mTLSFilter) {
+ nsCOMPtr<nsIAsyncInputStream> ref1(mSocketIn);
+ nsCOMPtr<nsIAsyncOutputStream> ref2(mSocketOut);
+ mTLSFilter->newIODriver(ref1, ref2, getter_AddRefs(mSocketIn),
+ getter_AddRefs(mSocketOut));
+ mTLSFilter = 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;
+
+ // 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);
+ }
+
+ // Check for the TCP Fast Open related stalls.
+ if (mCheckNetworkStallsWithTFO && mLastRequestBytesSentTime) {
+ PRIntervalTime initialResponseDelta = now - mLastRequestBytesSentTime;
+ if (initialResponseDelta >= gHttpHandler->FastOpenStallsTimeout()) {
+ gHttpHandler->IncrementFastOpenStallsCounter();
+ mCheckNetworkStallsWithTFO = false;
+ } else {
+ uint32_t next =
+ PR_IntervalToSeconds(gHttpHandler->FastOpenStallsTimeout()) -
+ PR_IntervalToSeconds(initialResponseDelta);
+ nextTickAfter = std::min(nextTickAfter, next);
+ }
+ }
+
+ if (!mNPNComplete) {
+ // 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
+ 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::GetSecurityInfo(nsISupports** secinfo) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpConnection::GetSecurityInfo trans=%p tlsfilter=%p socket=%p\n",
+ mTransaction.get(), mTLSFilter.get(), mSocketTransport.get()));
+
+ if (mTransaction &&
+ NS_SUCCEEDED(mTransaction->GetTransactionSecurityInfo(secinfo))) {
+ return;
+ }
+
+ if (mTLSFilter &&
+ NS_SUCCEEDED(mTLSFilter->GetTransactionSecurityInfo(secinfo))) {
+ return;
+ }
+
+ if (mSocketTransport &&
+ NS_SUCCEEDED(mSocketTransport->GetSecurityInfo(secinfo))) {
+ return;
+ }
+
+ *secinfo = nullptr;
+}
+
+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,
+ bool isFastOpenForce)
+ : Runnable("net::HttpConnectionForceIO"),
+ mConn(aConn),
+ mDoRecv(doRecv),
+ mIsFastOpenForce(isFastOpenForce) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mDoRecv) {
+ if (!mConn->mSocketIn) return NS_OK;
+ return mConn->OnInputStreamReady(mConn->mSocketIn);
+ }
+
+ // This runnable will be called when the ForceIO timer expires
+ // (mIsFastOpenForce==false) or during the TCP Fast Open to force
+ // writes (mIsFastOpenForce==true).
+ if (mIsFastOpenForce && !mConn->mWaitingFor0RTTResponse) {
+ // If we have exit the TCP Fast Open in the meantime we can skip
+ // this.
+ return NS_OK;
+ }
+ if (!mIsFastOpenForce) {
+ MOZ_ASSERT(mConn->mForceSendPending);
+ mConn->mForceSendPending = false;
+ }
+
+ if (!mConn->mSocketOut) {
+ return NS_OK;
+ }
+ return mConn->OnOutputStreamReady(mConn->mSocketOut);
+ }
+
+ private:
+ RefPtr<nsHttpConnection> mConn;
+ bool mDoRecv;
+ bool mIsFastOpenForce;
+};
+
+nsresult nsHttpConnection::ResumeSend() {
+ LOG(("nsHttpConnection::ResumeSend [this=%p]\n", this));
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mSocketOut) {
+ nsresult rv = mSocketOut->AsyncWait(this, 0, 0, nullptr);
+ LOG(
+ ("nsHttpConnection::ResumeSend [this=%p] "
+ "mWaitingFor0RTTResponse=%d mForceSendDuringFastOpenPending=%d "
+ "mReceivedSocketWouldBlockDuringFastOpen=%d\n",
+ this, mWaitingFor0RTTResponse, mForceSendDuringFastOpenPending,
+ mReceivedSocketWouldBlockDuringFastOpen));
+ if (mWaitingFor0RTTResponse && !mForceSendDuringFastOpenPending &&
+ !mReceivedSocketWouldBlockDuringFastOpen && NS_SUCCEEDED(rv)) {
+ // During TCP Fast Open, poll does not work properly so we will
+ // trigger writes manually.
+ mForceSendDuringFastOpenPending = true;
+ NS_DispatchToCurrentThread(new HttpConnectionForceIO(this, false, true));
+ }
+ return rv;
+ }
+
+ 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");
+
+ if (mFastOpen) {
+ LOG(
+ ("nsHttpConnection::ResumeRecv - do not waiting for read during "
+ "fast open! [this=%p]\n",
+ this));
+ return NS_OK;
+ }
+
+ // 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 (!mTLSFilter || !mTLSFilter->HasDataToRecv() || NS_FAILED(ForceRecv())) {
+ return mSocketIn->AsyncWait(this, 0, 0, nullptr);
+ }
+ return NS_OK;
+ }
+
+ 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, 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, false));
+}
+
+// trigger an asynchronous write
+nsresult nsHttpConnection::ForceSend() {
+ LOG(("nsHttpConnection::ForceSend [this=%p]\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mTLSFilter) {
+ return mTLSFilter->NudgeTunnel(this);
+ }
+ 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;
+}
+
+//-----------------------------------------------------------------------------
+// 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((trans == mTransaction) ||
+ (mTLSFilter && !mTLSFilter->Transaction()) ||
+ (mTLSFilter && mTLSFilter->Transaction() == trans));
+ 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 && mTLSFilter) {
+ // In case of a race when the transaction is being closed before the tunnel
+ // is established we need to carry closing status on the proxied
+ // transaction.
+ // Not doing this leads to use of this closed connection to activate the
+ // not closed transaction what will likely lead to a use of a closed ssl
+ // socket and may cause a crash because of an unexpected use.
+ //
+ // There can possibly be two states: the actual transaction is still hanging
+ // of off the filter, or has not even been assigned on it yet. In the
+ // latter case we simply must close the transaction given to us via the
+ // argument.
+ if (!mTLSFilter->Transaction()) {
+ if (trans) {
+ LOG((" closing transaction directly"));
+ trans->Close(reason);
+ }
+ } else {
+ LOG((" closing transactin hanging of off mTLSFilter"));
+ mTLSFilter->Close(reason);
+ }
+ }
+
+ 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;
+}
+
+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::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
+ }
+
+ 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 (!mProxyConnectInProgress) mTotalBytesWritten += *countRead;
+ }
+
+ 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;
+
+ mForceSendDuringFastOpenPending = false;
+
+ if (mTransactionCaps & NS_HTTP_CONNECT_ONLY) {
+ if (!mCompletedProxyConnect && !mProxyConnectStream) {
+ // 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 (mCompletedProxyConnect) {
+ // 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;
+
+ // 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 (mConnInfo->UsingHttpsProxy() &&
+ !EnsureNPNComplete(rv, transactionBytes)) {
+ MOZ_ASSERT(!transactionBytes);
+ mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
+ } else if (mProxyConnectStream) {
+ // If we're need an HTTP/1 CONNECT tunnel through a proxy
+ // send it before doing the SSL handshake
+ LOG((" writing CONNECT request stream\n"));
+ rv = mProxyConnectStream->ReadSegments(ReadFromStream, this,
+ nsIOService::gDefaultSegmentSize,
+ &transactionBytes);
+ } else if (!EnsureNPNComplete(rv, transactionBytes)) {
+ if (NS_SUCCEEDED(rv) && !transactionBytes &&
+ NS_SUCCEEDED(mSocketOutCondition)) {
+ 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) {
+ mReportedSpdy = true;
+ MOZ_ASSERT(!mEverUsedSpdy);
+ gHttpHandler->ConnMgr()->ReportSpdyConnection(this, false);
+ }
+
+ LOG((" writing transaction request stream\n"));
+ mProxyConnectInProgress = false;
+ rv = mTransaction->ReadSegmentsAgain(
+ this, nsIOService::gDefaultSegmentSize, &transactionBytes, &again);
+ 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 (!again && (mFastOpen || mWaitingFor0RTTResponse)) {
+ // Continue waiting;
+ rv = mSocketOut->AsyncWait(this, 0, 0, nullptr);
+ }
+ 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;
+ if (mFastOpen || mWaitingFor0RTTResponse) {
+ // Continue waiting;
+ rv = mSocketOut->AsyncWait(this, 0, 0, nullptr);
+ }
+ }
+ again = false;
+ } else if (NS_FAILED(mSocketOutCondition)) {
+ if (mSocketOutCondition == NS_BASE_STREAM_WOULD_BLOCK) {
+ if (mTLSFilter) {
+ LOG((" blocked tunnel (handshake?)\n"));
+ rv = mTLSFilter->NudgeTunnel(this);
+ } else {
+ rv = mSocketOut->AsyncWait(this, 0, 0, nullptr); // continue writing
+ }
+ } else {
+ rv = mSocketOutCondition;
+ }
+ again = false;
+ } else if (!transactionBytes) {
+ rv = NS_OK;
+
+ if (mWaitingFor0RTTResponse || mFastOpen) {
+ // Wait for tls handshake to finish or waiting for connect.
+ rv = mSocketOut->AsyncWait(this, 0, 0, nullptr);
+ } else 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);
+ if (mCheckNetworkStallsWithTFO) {
+ mLastRequestBytesSentTime = PR_IntervalNow();
+ }
+
+ rv = ResumeRecv(); // start reading
+ }
+ 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
+
+ mCheckNetworkStallsWithTFO = false;
+
+ 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) && !mCompletedProxyConnect &&
+ !mProxyConnectStream) {
+ // 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;
+ uint32_t n;
+ bool again = true;
+
+ do {
+ if (!mProxyConnectInProgress && !mNPNComplete) {
+ // 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));
+ rv = NS_OK;
+ break;
+ }
+
+ mSocketInCondition = NS_OK;
+ 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(
+ nsAHttpTransaction* aSpdyConnectTransaction) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mTLSFilter);
+ LOG(
+ ("nsHttpConnection %p SetupSecondaryTLS %s %d "
+ "aSpdyConnectTransaction=%p\n",
+ this, mConnInfo->Origin(), mConnInfo->OriginPort(),
+ aSpdyConnectTransaction));
+
+ nsHttpConnectionInfo* ci = nullptr;
+ if (mTransaction) {
+ ci = mTransaction->ConnectionInfo();
+ }
+ if (!ci) {
+ ci = mConnInfo;
+ }
+ MOZ_ASSERT(ci);
+
+ mTLSFilter = new TLSFilterTransaction(mTransaction, ci->Origin(),
+ ci->OriginPort(), this, this);
+
+ if (mTransaction) {
+ mTransaction = mTLSFilter;
+ }
+ mWeakTrans = do_GetWeakReference(aSpdyConnectTransaction);
+}
+
+void nsHttpConnection::SetInSpdyTunnel(bool arg) {
+ MOZ_ASSERT(mTLSFilter);
+ mInSpdyTunnel = arg;
+
+ // don't setup another tunnel :)
+ mProxyConnectStream = nullptr;
+ mCompletedProxyConnect = true;
+ mProxyConnectInProgress = false;
+}
+
+// static
+nsresult nsHttpConnection::MakeConnectString(nsAHttpTransaction* trans,
+ nsHttpRequestHead* request,
+ nsACString& result, bool h2ws) {
+ 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());
+ } else {
+ request->SetRequestURI(result);
+ }
+ rv = request->SetHeader(nsHttp::User_Agent, gHttpHandler->UserAgent());
+ 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 [",
+ trans->QueryHttpTransaction()));
+ LogHeaders(result.BeginReading());
+ LOG(("]"));
+ }
+
+ result.AppendLiteral("\r\n");
+ return NS_OK;
+}
+
+nsresult nsHttpConnection::SetupProxyConnect() {
+ LOG(("nsHttpConnection::SetupProxyConnect [this=%p]\n", this));
+ NS_ENSURE_TRUE(!mProxyConnectStream, NS_ERROR_ALREADY_INITIALIZED);
+ MOZ_ASSERT(mUsingSpdyVersion == SpdyVersion::NONE,
+ "SPDY NPN Complete while using proxy connect stream");
+
+ nsAutoCString buf;
+ nsHttpRequestHead request;
+ nsresult rv = MakeConnectString(mTransaction, &request, buf, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_NewCStringInputStream(getter_AddRefs(mProxyConnectStream),
+ std::move(buf));
+}
+
+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 (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 (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;
+ }
+}
+
+nsAHttpTransaction*
+nsHttpConnection::CloseConnectionFastOpenTakesTooLongOrError(
+ bool aCloseSocketTransport) {
+ MOZ_ASSERT(!mCurrentBytesRead);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mFastOpenStatus = TFO_FAILED;
+ RefPtr<nsAHttpTransaction> trans;
+
+ DontReuse();
+
+ if (mUsingSpdyVersion != SpdyVersion::NONE) {
+ // If we have a http2 connection just restart it as if 0rtt failed.
+ // For http2 we do not need to do similar thing as for http1 because
+ // backup connection will pick immediately all this transaction anyway.
+ mUsingSpdyVersion = SpdyVersion::NONE;
+ if (mSpdySession) {
+ mTransaction->SetFastOpenStatus(TFO_FAILED);
+ Unused << mSpdySession->Finish0RTT(true, true);
+ }
+ mSpdySession = nullptr;
+ } else {
+ // For http1 we want to make this transaction an absolute priority to
+ // get the backup connection so we will return it from here.
+ if (NS_SUCCEEDED(mTransaction->RestartOnFastOpenError())) {
+ trans = mTransaction;
+ }
+ mTransaction->SetConnection(nullptr);
+ }
+
+ {
+ MutexAutoLock lock(mCallbacksLock);
+ mCallbacks = nullptr;
+ }
+
+ if (mSocketIn) {
+ mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+
+ mTransaction = nullptr;
+ if (!aCloseSocketTransport) {
+ if (mSocketOut) {
+ mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ }
+ mSocketTransport->SetEventSink(nullptr, nullptr);
+ mSocketTransport->SetSecurityCallbacks(nullptr);
+ mSocketTransport = nullptr;
+ }
+ Close(NS_ERROR_NET_RESET);
+ return trans;
+}
+
+void nsHttpConnection::SetFastOpen(bool aFastOpen) {
+ mFastOpen = aFastOpen;
+ if (!mFastOpen && mTransaction && !mTransaction->IsNullTransaction()) {
+ mExperienced = true;
+
+ nsHttpTransaction* hTrans = mTransaction->QueryHttpTransaction();
+ if (hTrans) {
+ SetUrgentStartPreferred(hTrans->ClassOfService() &
+ nsIClassOfService::UrgentStart);
+ }
+ }
+}
+
+void nsHttpConnection::SetFastOpenStatus(uint8_t tfoStatus) {
+ mFastOpenStatus = tfoStatus;
+ if ((mFastOpenStatus >= TFO_FAILED_CONNECTION_REFUSED) &&
+ (mFastOpenStatus <=
+ TFO_FAILED_BACKUP_CONNECTION_TFO_DATA_COOKIE_NOT_ACCEPTED) &&
+ mSocketTransport) {
+ nsresult firstRetryError;
+ if (NS_SUCCEEDED(mSocketTransport->GetFirstRetryError(&firstRetryError)) &&
+ (NS_FAILED(firstRetryError))) {
+ if ((mFastOpenStatus >= TFO_FAILED_BACKUP_CONNECTION_TFO_NOT_TRIED) &&
+ (mFastOpenStatus <=
+ TFO_FAILED_BACKUP_CONNECTION_TFO_DATA_COOKIE_NOT_ACCEPTED)) {
+ mFastOpenStatus = TFO_FAILED_BACKUP_CONNECTION_NO_TFO_FAILED_TOO;
+ } else {
+ // We add +7 to tranform TFO_FAILED_CONNECTION_REFUSED into
+ // TFO_FAILED_CONNECTION_REFUSED_NO_TFO_FAILED_TOO, etc.
+ // If the list in TCPFastOpenLayer.h changes please addapt +7.
+ mFastOpenStatus = tfoStatus + 7;
+ }
+ }
+ }
+}
+
+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;
+ if ((mFastOpenStatus != TFO_DATA_SENT) &&
+ !mBootstrappedTimings.secureConnectionStart.IsNull()) {
+ 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<nsISupports> secInfo;
+ mSocketTransport->GetSecurityInfo(getter_AddRefs(secInfo));
+ if (!secInfo) {
+ return false;
+ }
+
+ nsCOMPtr<nsISSLSocketControl> ssc(do_QueryInterface(secInfo));
+ if (!ssc) {
+ return false;
+ }
+
+ return !ssc->GetClientCertSent();
+}
+
+bool nsHttpConnection::CanAcceptWebsocket() {
+ if (!UsingSpdy()) {
+ return true;
+ }
+
+ return mSpdySession->CanAcceptWebsocket();
+}
+
+bool nsHttpConnection::IsProxyConnectInProgress() {
+ return mProxyConnectInProgress;
+}
+
+bool nsHttpConnection::LastTransactionExpectedNoContent() {
+ return mLastTransactionExpectedNoContent;
+}
+
+void nsHttpConnection::SetLastTransactionExpectedNoContent(bool val) {
+ mLastTransactionExpectedNoContent = val;
+}
+
+bool nsHttpConnection::IsPersistent() { return IsKeepAlive() && !mDontReuse; }
+
+nsAHttpTransaction* nsHttpConnection::Transaction() { return mTransaction; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h
new file mode 100644
index 0000000000..7616daba62
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -0,0 +1,359 @@
+/* -*- 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 "HttpConnectionBase.h"
+#include "nsHttpConnectionInfo.h"
+#include "nsHttpResponseHead.h"
+#include "nsAHttpTransaction.h"
+#include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
+#include "prinrval.h"
+#include "TunnelUtils.h"
+#include "mozilla/Mutex.h"
+#include "ARefBase.h"
+#include "TimingStruct.h"
+#include "HttpTrafficAnalyzer.h"
+
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsITimer.h"
+
+class nsISocketTransport;
+class nsISSLSocketControl;
+
+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,
+ public NudgeTunnelCallback {
+ 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
+ NS_DECL_NUDGETUNNELCALLBACK
+
+ nsHttpConnection();
+
+ void SetFastOpen(bool aFastOpen);
+ // Close this connection and return the transaction. The transaction is
+ // restarted as well. This will only happened before connection is
+ // connected.
+ nsAHttpTransaction* CloseConnectionFastOpenTakesTooLongOrError(
+ bool aCloseocketTransport);
+
+ //-------------------------------------------------------------------------
+ // 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() && !mTLSFilter &&
+ 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;
+
+ [[nodiscard]] static nsresult ReadFromStream(nsIInputStream*, void*,
+ const char*, uint32_t, uint32_t,
+ uint32_t*);
+
+ // 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; }
+
+ [[nodiscard]] static nsresult MakeConnectString(nsAHttpTransaction* trans,
+ nsHttpRequestHead* request,
+ nsACString& result,
+ bool h2ws);
+ void SetupSecondaryTLS(nsAHttpTransaction* aSpdyConnectTransaction = nullptr);
+ void SetInSpdyTunnel(bool arg);
+
+ // 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)) &&
+ !mFastOpen;
+ }
+
+ void SetFastOpenStatus(uint8_t tfoStatus);
+ uint8_t GetFastOpenStatus() { return mFastOpenStatus; }
+
+ // 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;
+
+ bool CanAcceptWebsocket() override;
+
+ private:
+ // Value (set in mTCPKeepaliveConfig) indicates which set of prefs to use.
+ enum TCPKeepaliveConfig {
+ kTCPKeepaliveDisabled = 0,
+ kTCPKeepaliveShortLivedConfig,
+ kTCPKeepaliveLongLivedConfig
+ };
+
+ // called to cause the underlying socket to start speaking SSL
+ [[nodiscard]] nsresult InitSSLParams(bool connectingToProxy,
+ bool ProxyStartSSL);
+ [[nodiscard]] nsresult SetupNPNList(nsISSLSocketControl* ssl, uint32_t caps);
+
+ [[nodiscard]] nsresult OnTransactionDone(nsresult reason);
+ [[nodiscard]] nsresult OnSocketWritable();
+ [[nodiscard]] nsresult OnSocketReadable();
+
+ [[nodiscard]] nsresult SetupProxyConnect();
+
+ PRIntervalTime IdleTime();
+ bool IsAlive();
+
+ // Makes certain the SSL handshake is complete and NPN negotiation
+ // has had a chance to happen
+ [[nodiscard]] bool EnsureNPNComplete(nsresult& aOut0RTTWriteHandshakeValue,
+ uint32_t& aOut0RTTBytesWritten);
+
+ void SetupSSL();
+
+ // Start the Spdy transaction handler when NPN indicates spdy/*
+ void StartSpdy(nsISSLSocketControl* ssl, SpdyVersion versionLevel);
+ // Like the above, but do the bare minimum to do 0RTT data, so we can back
+ // it out, if necessary
+ void Start0RTTSpdy(SpdyVersion versionLevel);
+
+ // 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();
+
+ private:
+ // mTransaction only points to the HTTP Transaction callbacks if the
+ // transaction is open, otherwise it is null.
+ RefPtr<nsAHttpTransaction> mTransaction;
+
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+
+ nsresult mSocketInCondition;
+ nsresult mSocketOutCondition;
+
+ nsCOMPtr<nsIInputStream> mProxyConnectStream;
+ nsCOMPtr<nsIInputStream> mRequestStream;
+
+ RefPtr<TLSFilterTransaction> mTLSFilter;
+ nsWeakPtr mWeakTrans; // SpdyConnectTransaction *
+
+ RefPtr<nsHttpHandler> mHttpHandler; // keep gHttpHandler alive
+
+ PRIntervalTime mLastReadTime;
+ PRIntervalTime mLastWriteTime;
+ PRIntervalTime
+ mMaxHangTime; // max download time before dropping keep-alive status
+ PRIntervalTime mIdleTimeout; // value of keep-alive: timeout=
+ PRIntervalTime mConsiderReusedAfterInterval;
+ PRIntervalTime mConsiderReusedAfterEpoch;
+ int64_t mCurrentBytesRead; // data read per activation
+ int64_t mMaxBytesRead; // max read in 1 activation
+ int64_t mTotalBytesRead; // total data read
+ int64_t mContentBytesWritten; // 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;
+ // A flag to prevent reset of mUrgentStartPreferred by subsequent transactions
+ bool mUrgentStartPreferredKnown;
+ bool mConnectedTransport;
+ bool mKeepAlive;
+ bool mKeepAliveMask;
+ bool mDontReuse;
+ bool mIsReused;
+ bool mCompletedProxyConnect;
+ bool mLastTransactionExpectedNoContent;
+ bool mIdleMonitoring;
+ bool mProxyConnectInProgress;
+ bool mInSpdyTunnel;
+ bool mForcePlainText;
+
+ // A snapshot of current number of transfered bytes
+ int64_t mTrafficCount;
+ bool mTrafficStamp; // true then the above is set
+
+ // The number of <= HTTP/1.1 transactions performed on this connection. This
+ // excludes spdy transactions.
+ uint32_t mHttp1xTransactionCount;
+
+ // 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;
+
+ // SPDY related
+ bool mNPNComplete;
+ bool mSetupSSLCalled;
+
+ // version level in use, 0 if unused
+ SpdyVersion mUsingSpdyVersion;
+
+ RefPtr<ASpdySession> mSpdySession;
+ int32_t mPriority;
+ bool mReportedSpdy;
+
+ // mUsingSpdyVersion is cleared when mSpdySession is freed, this is permanent
+ bool mEverUsedSpdy;
+
+ // mLastHttpResponseVersion stores the last response's http version seen.
+ HttpVersion mLastHttpResponseVersion;
+
+ // If a large keepalive has been requested for any trans,
+ // scale the default by this factor
+ uint32_t mDefaultTimeoutFactor;
+
+ bool mResponseTimeoutEnabled;
+
+ // Flag to indicate connection is in inital keepalive period (fast detect).
+ uint32_t mTCPKeepaliveConfig;
+ nsCOMPtr<nsITimer> mTCPKeepaliveTransitionTimer;
+
+ private:
+ // For ForceSend()
+ static void ForceSendIO(nsITimer* aTimer, void* aClosure);
+ [[nodiscard]] nsresult MaybeForceSendIO();
+ bool mForceSendPending;
+ nsCOMPtr<nsITimer> mForceSendTimer;
+
+ // Helper variable for 0RTT handshake;
+ bool m0RTTChecked; // Possible 0RTT has been
+ // checked.
+ bool mWaitingFor0RTTResponse; // We have are
+ // sending 0RTT
+ // data and we
+ // are waiting
+ // for the end of
+ // the handsake.
+ int64_t mContentBytesWritten0RTT;
+ bool mEarlyDataNegotiated; // Only used for telemetry
+ nsCString mEarlyNegotiatedALPN;
+ bool mDid0RTTSpdy;
+
+ bool mFastOpen;
+ uint8_t mFastOpenStatus;
+
+ bool mForceSendDuringFastOpenPending;
+ bool mReceivedSocketWouldBlockDuringFastOpen;
+ bool mCheckNetworkStallsWithTFO;
+ PRIntervalTime mLastRequestBytesSentTime;
+
+ private:
+ bool mThroughCaptivePortal;
+};
+
+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..e2c256580e
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
@@ -0,0 +1,580 @@
+/* -*- 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,
+ const nsACString& topWindowOrigin, nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes, bool endToEndSSL, bool isolated,
+ bool aIsHttp3)
+ : mRoutedPort(443), mIsolated(isolated), mLessThanTls13(false) {
+ Init(originHost, originPort, npnToken, username, topWindowOrigin, proxyInfo,
+ originAttributes, endToEndSSL, aIsHttp3);
+}
+
+nsHttpConnectionInfo::nsHttpConnectionInfo(
+ const nsACString& originHost, int32_t originPort,
+ const nsACString& npnToken, const nsACString& username,
+ const nsACString& topWindowOrigin, nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes, bool endToEndSSL, bool aIsHttp3)
+ : nsHttpConnectionInfo(originHost, originPort, npnToken, username,
+ topWindowOrigin, proxyInfo, originAttributes,
+ endToEndSSL, false, aIsHttp3) {}
+
+nsHttpConnectionInfo::nsHttpConnectionInfo(
+ const nsACString& originHost, int32_t originPort,
+ const nsACString& npnToken, const nsACString& username,
+ const nsACString& topWindowOrigin, nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes, const nsACString& routedHost,
+ int32_t routedPort, bool isolated, bool aIsHttp3)
+ : mIsolated(isolated), mLessThanTls13(false) {
+ mEndToEndSSL = true; // so DefaultPort() works
+ mRoutedPort = routedPort == -1 ? DefaultPort() : routedPort;
+
+ if (!originHost.Equals(routedHost) || (originPort != routedPort)) {
+ mRoutedHost = routedHost;
+ }
+ Init(originHost, originPort, npnToken, username, topWindowOrigin, proxyInfo,
+ originAttributes, true, aIsHttp3);
+}
+
+nsHttpConnectionInfo::nsHttpConnectionInfo(
+ const nsACString& originHost, int32_t originPort,
+ const nsACString& npnToken, const nsACString& username,
+ const nsACString& topWindowOrigin, nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes, const nsACString& routedHost,
+ int32_t routedPort, bool aIsHttp3)
+ : nsHttpConnectionInfo(originHost, originPort, npnToken, username,
+ topWindowOrigin, proxyInfo, originAttributes,
+ routedHost, routedPort, false, aIsHttp3) {}
+
+void nsHttpConnectionInfo::Init(const nsACString& host, int32_t port,
+ const nsACString& npnToken,
+ const nsACString& username,
+ const nsACString& topWindowOrigin,
+ nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes,
+ bool e2eSSL, bool aIsHttp3) {
+ LOG(("Init nsHttpConnectionInfo @%p\n", this));
+
+ mUsername = username;
+ mTopWindowOrigin = topWindowOrigin;
+ mProxyInfo = proxyInfo;
+ mEndToEndSSL = e2eSSL;
+ mUsingConnect = false;
+ mNPNToken = npnToken;
+ mIsHttp3 = aIsHttp3;
+ 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 4 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 i/. i is for isolated
+ // 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.
+
+ mHashKey.AssignLiteral("........[tlsflags0x00000000]");
+ if (mIsolated) {
+ mHashKey.SetCharAt('i', 7);
+ }
+
+ mHashKey.Append(keyHost);
+ mHashKey.Append(':');
+ mHashKey.AppendInt(keyPort);
+ if (!mUsername.IsEmpty()) {
+ mHashKey.Append('[');
+ mHashKey.Append(mUsername);
+ mHashKey.Append(']');
+ }
+
+ if (mUsingHttpsProxy) {
+ mHashKey.SetCharAt('T', 0);
+ } else if (mUsingHttpProxy) {
+ mHashKey.SetCharAt('P', 0);
+ }
+ if (mEndToEndSSL) {
+ mHashKey.SetCharAt('S', 1);
+ }
+
+ // 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 (mIsolated && !mTopWindowOrigin.IsEmpty()) {
+ mHashKey.Append('{');
+ mHashKey.Append('{');
+ mHashKey.Append(mTopWindowOrigin);
+ mHashKey.Append('}');
+ mHashKey.Append('}');
+ }
+
+ 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();
+
+ BuildHashKey();
+
+ // Restore all of those properties.
+ SetAnonymous(isAnonymous);
+ SetPrivate(isPrivate);
+ SetInsecureScheme(isInsecureScheme);
+ SetNoSpdy(isNoSpdy);
+ SetBeConservative(isBeConservative);
+}
+
+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, mTopWindowOrigin,
+ mProxyInfo, mOriginAttributes, mEndToEndSSL, mIsolated, mIsHttp3);
+ } else {
+ MOZ_ASSERT(mEndToEndSSL);
+ clone = new nsHttpConnectionInfo(mOrigin, mOriginPort, mNPNToken, mUsername,
+ mTopWindowOrigin, mProxyInfo,
+ mOriginAttributes, mRoutedHost,
+ mRoutedPort, mIsolated, mIsHttp3);
+ }
+
+ // 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->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<Tuple<nsCString, bool>> alpn = aRecord->GetAlpn();
+
+ // Let the new conn info learn h3 will be used.
+ bool isHttp3 = alpn ? Get<1>(*alpn) : false;
+
+ LOG(("HTTPSSVC: use new routed host (%s) and new npnToken (%s)", name.get(),
+ alpn ? Get<0>(*alpn).get() : "None"));
+
+ RefPtr<nsHttpConnectionInfo> clone;
+ if (name.IsEmpty()) {
+ clone = new nsHttpConnectionInfo(
+ mOrigin, mOriginPort, alpn ? Get<0>(*alpn) : EmptyCString(), mUsername,
+ mTopWindowOrigin, mProxyInfo, mOriginAttributes, mEndToEndSSL,
+ mIsolated, isHttp3);
+ } else {
+ MOZ_ASSERT(mEndToEndSSL);
+ clone = new nsHttpConnectionInfo(
+ mOrigin, mOriginPort, alpn ? Get<0>(*alpn) : EmptyCString(), mUsername,
+ mTopWindowOrigin, mProxyInfo, mOriginAttributes, name,
+ port ? *port : mOriginPort, mIsolated, isHttp3);
+ }
+
+ // 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->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.tlsFlags() = aInfo->GetTlsFlags();
+ aArgs.isolated() = aInfo->GetIsolated();
+ aArgs.isTrrServiceChannel() = aInfo->GetTRRMode();
+ aArgs.trrMode() = aInfo->GetTRRMode();
+ aArgs.isIPv4Disabled() = aInfo->GetIPv4Disabled();
+ aArgs.isIPv6Disabled() = aInfo->GetIPv6Disabled();
+ aArgs.topWindowOrigin() = aInfo->GetTopWindowOrigin();
+ aArgs.isHttp3() = aInfo->IsHttp3();
+ aArgs.hasIPHintAddress() = aInfo->HasIPHintAddress();
+ aArgs.echConfig() = aInfo->GetEchConfig();
+
+ 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(), aInfoArgs.topWindowOrigin(), pi,
+ aInfoArgs.originAttributes(), aInfoArgs.endToEndSSL(),
+ aInfoArgs.isolated(), aInfoArgs.isHttp3());
+ } else {
+ MOZ_ASSERT(aInfoArgs.endToEndSSL());
+ cinfo = new nsHttpConnectionInfo(
+ aInfoArgs.host(), aInfoArgs.port(), aInfoArgs.npnToken(),
+ aInfoArgs.username(), aInfoArgs.topWindowOrigin(), pi,
+ aInfoArgs.originAttributes(), aInfoArgs.routedHost(),
+ aInfoArgs.routedPort(), aInfoArgs.isolated(), aInfoArgs.isHttp3());
+ }
+
+ // 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->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) {
+ if (mRoutedHost.IsEmpty()) {
+ RefPtr<nsHttpConnectionInfo> clone = Clone();
+ // Explicitly set mIsHttp3 to false, since CloneAsDirectRoute() is used to
+ // create a non-http3 connection info.
+ clone->mIsHttp3 = false;
+ clone.forget(outCI);
+ return;
+ }
+
+ RefPtr<nsHttpConnectionInfo> clone = new nsHttpConnectionInfo(
+ mOrigin, mOriginPort, ""_ns, mUsername, mTopWindowOrigin, mProxyInfo,
+ mOriginAttributes, mEndToEndSSL, mIsolated);
+ // 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->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,
+ mTopWindowOrigin, mProxyInfo,
+ mOriginAttributes, true, mIsHttp3);
+ // 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::SetTlsFlags(uint32_t aTlsFlags) {
+ mTlsFlags = aTlsFlags;
+
+ mHashKey.Replace(19, 8, nsPrintfCString("%08x", mTlsFlags));
+}
+
+bool nsHttpConnectionInfo::UsingProxy() {
+ if (!mProxyInfo) return false;
+ return !mProxyInfo->IsDirect();
+}
+
+bool nsHttpConnectionInfo::HostIsLocalIPLiteral() const {
+ PRNetAddr prAddr;
+ // If the host/proxy host is not an IP address literal, return false.
+ if (ProxyHost()) {
+ if (PR_StringToNetAddr(ProxyHost(), &prAddr) != PR_SUCCESS) {
+ return false;
+ }
+ } else if (PR_StringToNetAddr(Origin(), &prAddr) != PR_SUCCESS) {
+ return false;
+ }
+ NetAddr netAddr(&prAddr);
+ 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..c1b22f48eb
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -0,0 +1,280 @@
+/* -*- 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"
+
+//-----------------------------------------------------------------------------
+// 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,
+ const nsACString& topWindowOrigin,
+ nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes,
+ bool endToEndSSL = false, bool aIsHttp3 = 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,
+ const nsACString& topWindowOrigin,
+ nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes,
+ const nsACString& routedHost, int32_t routedPort,
+ bool aIsHttp3);
+
+ 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();
+
+ 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** outParam);
+ [[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) { mHashKey.SetCharAt(anon ? 'A' : '.', 2); }
+ bool GetAnonymous() const { return mHashKey.CharAt(2) == 'A'; }
+ void SetPrivate(bool priv) { mHashKey.SetCharAt(priv ? 'P' : '.', 3); }
+ bool GetPrivate() const { return mHashKey.CharAt(3) == 'P'; }
+ void SetInsecureScheme(bool insecureScheme) {
+ mHashKey.SetCharAt(insecureScheme ? 'I' : '.', 4);
+ }
+ bool GetInsecureScheme() const { return mHashKey.CharAt(4) == 'I'; }
+
+ void SetNoSpdy(bool aNoSpdy) { mHashKey.SetCharAt(aNoSpdy ? 'X' : '.', 5); }
+ bool GetNoSpdy() const { return mHashKey.CharAt(5) == 'X'; }
+
+ void SetBeConservative(bool aBeConservative) {
+ mHashKey.SetCharAt(aBeConservative ? 'C' : '.', 6);
+ }
+ bool GetBeConservative() const { return mHashKey.CharAt(6) == 'C'; }
+
+ void SetIsolated(bool aIsolated) {
+ mIsolated = aIsolated;
+ RebuildHashKey();
+ }
+ bool GetIsolated() const { return mIsolated; }
+
+ 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; }
+
+ const nsCString& GetNPNToken() { return mNPNToken; }
+ const nsCString& GetUsername() { return mUsername; }
+ const nsCString& GetTopWindowOrigin() { return mTopWindowOrigin; }
+
+ 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:
+ // These constructor versions are intended to be used from Clone() and
+ // DeserializeHttpConnectionInfoCloneArgs().
+ nsHttpConnectionInfo(const nsACString& originHost, int32_t originPort,
+ const nsACString& npnToken, const nsACString& username,
+ const nsACString& topWindowOrigin,
+ nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes,
+ bool endToEndSSL, bool isolated, bool aIsHttp3);
+ nsHttpConnectionInfo(const nsACString& originHost, int32_t originPort,
+ const nsACString& npnToken, const nsACString& username,
+ const nsACString& topWindowOrigin,
+ nsProxyInfo* proxyInfo,
+ const OriginAttributes& originAttributes,
+ const nsACString& routedHost, int32_t routedPort,
+ bool isolated, bool aIsHttp3);
+
+ void Init(const nsACString& host, int32_t port, const nsACString& npnToken,
+ const nsACString& username, const nsACString& topWindowOrigin,
+ nsProxyInfo* proxyInfo, const OriginAttributes& originAttributes,
+ bool EndToEndSSL, bool aIsHttp3);
+ void SetOriginServer(const nsACString& host, int32_t port);
+
+ nsCString mOrigin;
+ int32_t mOriginPort;
+ nsCString mRoutedHost;
+ int32_t mRoutedPort;
+
+ nsCString mHashKey;
+ nsCString mUsername;
+ nsCString mTopWindowOrigin;
+ nsCOMPtr<nsProxyInfo> mProxyInfo;
+ bool mUsingHttpProxy;
+ bool mUsingHttpsProxy;
+ bool mEndToEndSSL;
+ bool mUsingConnect; // if will use CONNECT with http proxy
+ nsCString mNPNToken;
+ OriginAttributes mOriginAttributes;
+ nsIRequest::TRRMode mTRRMode;
+
+ uint32_t mTlsFlags;
+ uint16_t mIsolated : 1;
+ 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;
+
+ 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..1cecce5724
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -0,0 +1,3527 @@
+/* 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 "NullHttpTransaction.h"
+#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.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 "nsIDNSRecord.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSService.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIRequestContext.h"
+#include "nsISocketTransport.h"
+#include "nsISocketTransportService.h"
+#include "nsITransport.h"
+#include "nsIXPConnect.h"
+#include "nsInterfaceRequestorAgg.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "ConnectionHandle.h"
+#include "HttpConnectionUDP.h"
+#include "SpeculativeTransaction.h"
+#include "TCPFastOpenLayer.h"
+
+namespace mozilla {
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsHttpConnectionMgr, nsIObserver)
+
+//-----------------------------------------------------------------------------
+
+nsHttpConnectionMgr::nsHttpConnectionMgr()
+ : mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor"),
+ mMaxUrgentExcessiveConns(0),
+ mMaxConns(0),
+ mMaxPersistConnsPerHost(0),
+ mMaxPersistConnsPerProxy(0),
+ mMaxRequestDelay(0),
+ mThrottleEnabled(false),
+ mThrottleVersion(2),
+ mThrottleSuspendFor(0),
+ mThrottleResumeFor(0),
+ mThrottleReadLimit(0),
+ mThrottleReadInterval(0),
+ mThrottleHoldTime(0),
+ mThrottleMaxTime(0),
+ mBeConservativeForProxy(true),
+ mIsShuttingDown(false),
+ mNumActiveConns(0),
+ mNumIdleConns(0),
+ mNumSpdyHttp3ActiveConns(0),
+ mNumHalfOpenConns(0),
+ mTimeOfNextWakeUp(UINT64_MAX),
+ mPruningNoTraffic(false),
+ mTimeoutTickArmed(false),
+ mTimeoutTickNext(1),
+ mCurrentTopLevelOuterContentWindowId(0),
+ mThrottlingInhibitsReading(false),
+ mActiveTabTransactionsExist(false),
+ mActiveTabUnthrottledTransactionsExist(false) {
+ 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 = services::GetIOService();
+ if (ioService) {
+ nsCOMPtr<nsISocketTransportService> realSTS =
+ services::GetSocketTransportService();
+ 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() : mBool(false) {}
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BoolWrapper, override)
+
+ public: // intentional!
+ bool mBool;
+
+ 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([&, 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();
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ nsresult rv;
+ if (!mSocketThreadTarget) {
+ NS_WARNING("cannot post event if not initialized");
+ rv = NS_ERROR_NOT_INITIALIZED;
+ } else {
+ nsCOMPtr<nsIRunnable> event = new ConnEvent(this, handler, iparam, vparam);
+ rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ return rv;
+}
+
+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 && gHttpHandler->IsSpdyEnabled()))
+ 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::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));
+ 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));
+ 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, uint32_t classOfService) {
+ LOG(
+ ("nsHttpConnectionMgr::UpdateClassOfServiceOnTransaction [trans=%p "
+ "classOfService=%" PRIu32 "]\n",
+ trans, static_cast<uint32_t>(classOfService)));
+ Unused << PostEvent(
+ &nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction,
+ static_cast<int32_t>(classOfService), 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);
+}
+
+class SpeculativeConnectArgs : public ARefBase {
+ public:
+ SpeculativeConnectArgs() : mFetchHTTPSRR(false) {}
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SpeculativeConnectArgs, override)
+
+ public: // intentional!
+ RefPtr<SpeculativeTransaction> mTrans;
+
+ bool mFetchHTTPSRR;
+
+ 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;
+ }
+
+ 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();
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (!mSocketThreadTarget) {
+ NS_WARNING("cannot post event if not initialized");
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<HttpConnectionBase> connRef(conn);
+ RefPtr<nsHttpConnectionMgr> self(this);
+ return mSocketThreadTarget->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->HalfOpensLength() == 0 && ent->UrgentStartQueueLength() == 0 &&
+ ent->PendingQueueLength() == 0 &&
+ ent->HalfOpenFastOpenBackupsLength() == 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("~.:");
+ }
+ 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) {
+ 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);
+
+ HttpConnectionBase* existingConn =
+ FindCoalescableConnection(ent, true, false, false);
+ if (existingConn) {
+ // Prefer http3 connection.
+ if (newConn->UsingHttp3() && existingConn->UsingSpdy()) {
+ LOG(
+ ("UpdateCoalescingForNewConn() found existing active H2 conn that "
+ "could have served newConn, but new connection is H3, therefore "
+ "close the H2 conncetion"));
+ existingConn->DontReuse();
+ } 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));
+ 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()));
+ nsTArray<nsWeakPtr>* listOfWeakConns =
+ mCoalescingHash.Get(ent->mCoalescingKeys[i]);
+ if (!listOfWeakConns) {
+ LOG(("UpdateCoalescingForNewConn() need new list element\n"));
+ listOfWeakConns = new nsTArray<nsWeakPtr>(1);
+ mCoalescingHash.Put(ent->mCoalescingKeys[i], listOfWeakConns);
+ }
+ listOfWeakConns->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) {
+ 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);
+
+ 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);
+ 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 alreadyHalfOpenOrWaitingForTLS =
+ pendingTransInfo->IsAlreadyClaimedInitializingConn();
+
+ rv = TryDispatchTransaction(
+ ent,
+ alreadyHalfOpenOrWaitingForTLS ||
+ !!pendingTransInfo->Transaction()->TunnelProvider(),
+ 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(mCurrentTopLevelOuterContentWindowId,
+ pendingQ, maxFocusedWindowConnections);
+
+ if (pendingQ.IsEmpty()) {
+ ent->AppendPendingQForNonFocusedWindows(
+ mCurrentTopLevelOuterContentWindowId, pendingQ, availableConnections);
+ }
+ return;
+ }
+
+ uint32_t maxNonFocusedWindowConnections =
+ availableConnections - maxFocusedWindowConnections;
+ nsTArray<RefPtr<PendingTransactionInfo>> remainingPendingQ;
+
+ ent->AppendPendingQForFocusedWindow(mCurrentTopLevelOuterContentWindowId,
+ pendingQ, maxFocusedWindowConnections);
+
+ if (maxNonFocusedWindowConnections) {
+ ent->AppendPendingQForNonFocusedWindows(
+ mCurrentTopLevelOuterContentWindowId, 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(
+ mCurrentTopLevelOuterContentWindowId, pendingQ,
+ maxNonFocusedWindowConnections - remainingPendingQ.Length());
+ } else if (pendingQ.Length() < maxFocusedWindowConnections) {
+ ent->AppendPendingQForNonFocusedWindows(
+ mCurrentTopLevelOuterContentWindowId, 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()) {
+ 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.Iter();
+ 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 &&
+ gHttpHandler->IsSpdyEnabled()) {
+ // 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 (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ConnectionEntry> entry = iter.Data();
+ 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 =
+ CreateTransport(ent, trans, trans->Caps(), false, false,
+ trans->ClassOfService() & nsIClassOfService::UrgentStart,
+ true, pendingTransInfo);
+ if (NS_FAILED(rv)) {
+ /* hard failure */
+ LOG(
+ ("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] "
+ "CreateTransport() 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 tunnelprovider=%p "
+ "onlyreused=%d active=%zu idle=%zu]\n",
+ trans, ent->mConnInfo.get(), ent->mConnInfo->HashKey().get(),
+ uint32_t(trans->Caps()), trans->TunnelProvider(), 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, (!gHttpHandler->IsSpdyEnabled() || (caps & NS_HTTP_DISALLOW_SPDY)),
+ (!gHttpHandler->IsHttp3Enabled() || (caps & NS_HTTP_DISALLOW_HTTP3)));
+ if (conn) {
+ if (trans->IsWebsocketUpgrade() && !conn->CanAcceptWebsocket()) {
+ // 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;
+ } 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.
+ // Note that this is only used in test currently.
+ if (caps & NS_HTTP_WAIT_HTTPSSVC_RESULT) {
+ 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->ClassOfService() & 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;
+ }
+ }
+ } else if (trans->TunnelProvider() &&
+ trans->TunnelProvider()->MaybeReTunnel(trans)) {
+ LOG((" sort of dispatched step 4a tunnel requeue trans=%p\n", trans));
+ // the tunnel provider took responsibility for making a new tunnel
+ 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->ClassOfService() & 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);
+
+ 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);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch");
+ if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
+ if (conn->UsingSpdy()) {
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_SPDY,
+ trans->GetPendingTime(), TimeStamp::Now());
+ } else {
+ AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP3,
+ trans->GetPendingTime(), TimeStamp::Now());
+ }
+ 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(), TimeStamp::Now());
+ trans->SetPendingTime(false);
+ }
+ return rv;
+}
+
+nsAHttpConnection* nsHttpConnectionMgr::MakeConnectionHandle(
+ HttpConnectionBase* aWrapped) {
+ return new ConnectionHandle(aWrapped);
+}
+
+// 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;
+ }
+
+ trans->SetPendingTime();
+
+ RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper =
+ trans->GetPushedStream();
+ if (pushedStreamWrapper) {
+ Http2PushedStream* pushedStream = pushedStreamWrapper->GetStream();
+ if (pushedStream) {
+ LOG((" ProcessNewTransaction %p tied to h2 session push %p\n", trans,
+ pushedStream->Session()));
+ return pushedStream->Session()->AddStream(trans, trans->Priority(), false,
+ false, 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));
+
+ ConnectionEntry* ent = GetOrCreateConnectionEntry(
+ ci, !!trans->TunnelProvider(), trans->Caps() & NS_HTTP_DISALLOW_SPDY,
+ trans->Caps() & NS_HTTP_DISALLOW_HTTP3);
+ MOZ_ASSERT(ent);
+
+ if (gHttpHandler->EchConfigEnabled()) {
+ 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 (!ent->AllowHttp2()) {
+ trans->DisableSpdy();
+ }
+ pendingTransInfo = new PendingTransactionInfo(trans);
+ rv = TryDispatchTransaction(ent, !!trans->TunnelProvider(),
+ 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) {
+ 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() {
+ mNumActiveConns--;
+ ConditionallyStopTimeoutTick();
+}
+
+nsresult nsHttpConnectionMgr::CreateTransport(
+ ConnectionEntry* ent, 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<HalfOpenSocket> sock = new HalfOpenSocket(
+ ent, trans, caps, speculative, isFromPredictor, urgentStart);
+
+ if (speculative) {
+ sock->SetAllow1918(allow1918);
+ }
+ // The socket stream holds the reference to the half open
+ // socket - so if the stream fails to init the half open
+ // will go away.
+ nsresult rv = sock->SetupPrimaryStreams();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (pendingTransInfo) {
+ DebugOnly<bool> claimed = pendingTransInfo->TryClaimingHalfOpen(sock);
+ MOZ_ASSERT(claimed);
+ }
+
+ ent->InsertIntoHalfOpens(sock);
+ return NS_OK;
+}
+
+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 (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ ProcessSpdyPendingQ(iter.Data().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 all pending transactions.
+ ent->CancelAllTransactions(NS_ERROR_ABORT);
+
+ // Close all half open tcp connections.
+ ent->CloseAllHalfOpens();
+
+ MOZ_ASSERT(ent->HalfOpenFastOpenBackupsLength() == 0 &&
+ !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(
+ int32_t arg, ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgUpdateClassOfServiceOnTransaction "
+ "[trans=%p]\n",
+ param));
+
+ uint32_t cos = static_cast<uint32_t>(arg);
+ nsHttpTransaction* trans = static_cast<nsHttpTransaction*>(param);
+
+ uint32_t previous = trans->ClassOfService();
+ trans->SetClassOfService(cos);
+
+ if ((previous ^ cos) & (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 (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ Unused << ProcessPendingQForEntry(iter.Data().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 (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ if (ProcessPendingQForEntry(iter.Data().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 && gHttpHandler->IsSpdyEnabled())) {
+ 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->HalfOpensLength() == 0 &&
+ ent->PendingQueueLength() == 0 &&
+ ent->UrgentStartQueueLength() == 0 &&
+ ent->HalfOpenFastOpenBackupsLength() == 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 (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ // Close the connections with no registered traffic.
+ RefPtr<ConnectionEntry> ent = iter.Data();
+ 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;
+ }
+
+ // Mark connections for traffic verification
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ iter.Data()->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!");
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup(int32_t,
+ ARefBase* param) {
+ LOG(("nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup\n"));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ nsHttpConnectionInfo* ci = static_cast<nsHttpConnectionInfo*>(param);
+
+ for (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ iter.Data()->ClosePersistentConnections();
+ }
+
+ if (ci) 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.
+ ent =
+ GetOrCreateConnectionEntry(conn->ConnectionInfo(), true, false, false);
+ 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))) {
+ } 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 {
+ LOG((" connection cannot be reused; closing connection\n"));
+ 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);
+ auto transportAvailableFunc = [upgradeData{std::move(upgradeData)},
+ 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, upgradeData->mSocketIn,
+ upgradeData->mSocketOut);
+ 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*) {
+ 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;
+ }
+ mTimeoutTick->SetTarget(mSocketThreadTarget);
+ }
+
+ 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::UpdateCurrentTopLevelOuterContentWindowId(
+ uint64_t aWindowId) {
+ RefPtr<UINT64Wrapper> windowIdWrapper = new UINT64Wrapper(aWindowId);
+ return PostEvent(
+ &nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId, 0,
+ windowIdWrapper);
+}
+
+void nsHttpConnectionMgr::SetThrottlingEnabled(bool aEnable) {
+ LOG(("nsHttpConnectionMgr::SetThrottlingEnabled enable=%d", aEnable));
+
+ 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(mCurrentTopLevelOuterContentWindowId);
+ au = trs ? trs->Length() : 0;
+ trs = mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
+ at = trs ? trs->Length() : 0;
+
+ for (auto iter = mActiveTransactions[false].Iter(); !iter.Done();
+ iter.Next()) {
+ bu += iter.UserData()->Length();
+ }
+ bu -= au;
+ for (auto iter = mActiveTransactions[true].Iter(); !iter.Done();
+ iter.Next()) {
+ bt += iter.UserData()->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->TopLevelOuterContentWindowId();
+ bool throttled = aTrans->EligibleForThrottling();
+
+ nsTArray<RefPtr<nsHttpTransaction>>* transactions =
+ mActiveTransactions[throttled].LookupOrAdd(tabId);
+
+ MOZ_ASSERT(!transactions->Contains(aTrans));
+
+ transactions->AppendElement(aTrans);
+
+ LOG(("nsHttpConnectionMgr::AddActiveTransaction t=%p tabid=%" PRIx64
+ "(%d) thr=%d",
+ aTrans, tabId, tabId == mCurrentTopLevelOuterContentWindowId,
+ throttled));
+ LogActiveTransactions('+');
+
+ if (tabId == mCurrentTopLevelOuterContentWindowId) {
+ 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->TopLevelOuterContentWindowId();
+ bool forActiveTab = tabId == mCurrentTopLevelOuterContentWindowId;
+ 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(mCurrentTopLevelOuterContentWindowId));
+ }
+ 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->TopLevelOuterContentWindowId();
+ bool forActiveTab = tabId == mCurrentTopLevelOuterContentWindowId;
+ 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(
+ mCurrentTopLevelOuterContentWindowId) > 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('^');
+}
+
+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 (auto iter = hashtable.Iter(); !iter.Done(); iter.Next()) {
+ if (excludeForActiveTab &&
+ iter.Key() == mCurrentTopLevelOuterContentWindowId) {
+ // These have never been throttled (never stopped reading)
+ continue;
+ }
+ ResumeReadOf(iter.UserData());
+ }
+}
+
+void nsHttpConnectionMgr::ResumeReadOf(
+ nsTArray<RefPtr<nsHttpTransaction>>* transactions) {
+ MOZ_ASSERT(transactions);
+
+ for (const auto& trans : *transactions) {
+ trans->ResumeReading();
+ }
+}
+
+void nsHttpConnectionMgr::NotifyConnectionOfWindowIdChange(
+ uint64_t previousWindowId) {
+ 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(previousWindowId);
+ addConnectionHelper(transactions);
+ transactions =
+ mActiveTransactions[false].Get(mCurrentTopLevelOuterContentWindowId);
+ addConnectionHelper(transactions);
+
+ // Get throttled transactions with the previous and current window id.
+ transactions = mActiveTransactions[true].Get(previousWindowId);
+ addConnectionHelper(transactions);
+ transactions =
+ mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
+ addConnectionHelper(transactions);
+
+ for (const auto& conn : connections) {
+ conn->TopLevelOuterContentWindowIdChanged(
+ mCurrentTopLevelOuterContentWindowId);
+ }
+}
+
+void nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId(
+ int32_t aLoading, ARefBase* param) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ uint64_t winId = static_cast<UINT64Wrapper*>(param)->GetValue();
+
+ if (mCurrentTopLevelOuterContentWindowId == winId) {
+ // duplicate notification
+ return;
+ }
+
+ bool activeTabWasLoading = mActiveTabTransactionsExist;
+
+ uint64_t previousWindowId = mCurrentTopLevelOuterContentWindowId;
+ mCurrentTopLevelOuterContentWindowId = winId;
+
+ if (gHttpHandler->ActiveTabPriority()) {
+ NotifyConnectionOfWindowIdChange(previousWindowId);
+ }
+
+ LOG(
+ ("nsHttpConnectionMgr::OnMsgUpdateCurrentTopLevelOuterContentWindowId"
+ " id=%" PRIx64 "\n",
+ mCurrentTopLevelOuterContentWindowId));
+
+ 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(mCurrentTopLevelOuterContentWindowId);
+ mActiveTabUnthrottledTransactionsExist = !!transactions;
+
+ if (!mActiveTabUnthrottledTransactionsExist) {
+ transactions =
+ mActiveTransactions[true].Get(mCurrentTopLevelOuterContentWindowId);
+ }
+ 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 (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ConnectionEntry> ent = iter.Data();
+
+ 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) {
+ // step 1
+ ConnectionEntry* specificEnt = mCT.GetWeak(specificCI->HashKey());
+ if (specificEnt && specificEnt->AvailableForDispatchNow()) {
+ 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()) {
+ return wildCardEnt;
+ }
+ }
+
+ // step 3
+ if (!specificEnt) {
+ RefPtr<nsHttpConnectionInfo> clone(specificCI->Clone());
+ specificEnt = new ConnectionEntry(clone);
+ mCT.Put(clone->HashKey(), RefPtr{specificEnt});
+ }
+ return specificEnt;
+}
+
+void nsHttpConnectionMgr::DoSpeculativeConnection(
+ SpeculativeTransaction* aTrans, bool aFetchHTTPSRR) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(aTrans);
+
+ ConnectionEntry* ent = GetOrCreateConnectionEntry(
+ aTrans->ConnectionInfo(), false, aTrans->Caps() & NS_HTTP_DISALLOW_SPDY,
+ aTrans->Caps() & NS_HTTP_DISALLOW_HTTP3);
+
+ 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 (mNumHalfOpenConns < parallelSpeculativeConnectLimit &&
+ ((ignoreIdle &&
+ (ent->IdleConnectionsLength() < parallelSpeculativeConnectLimit)) ||
+ !ent->IdleConnectionsLength()) &&
+ !(keepAlive && ent->RestrictConnections()) &&
+ !AtActiveConnectionLimit(ent, aTrans->Caps())) {
+ if (aFetchHTTPSRR) {
+ Unused << aTrans->FetchHTTPSRR();
+ }
+ DebugOnly<nsresult> rv =
+ CreateTransport(ent, aTrans, aTrans->Caps(), true, isFromPredictor,
+ false, allow1918, nullptr);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ } else {
+ LOG(
+ ("OnMsgSpeculativeConnect Transport "
+ "not created due to existing connection count\n"));
+ }
+}
+
+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) {
+ 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);
+ nsTArray<nsWeakPtr>* listOfWeakConns = mCoalescingHash.Get(newKey);
+ if (!listOfWeakConns) {
+ listOfWeakConns = new nsTArray<nsWeakPtr>(1);
+ mCoalescingHash.Put(newKey, listOfWeakConns);
+ }
+ listOfWeakConns->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 (auto iter = mCT.Iter(); !iter.Done(); iter.Next()) {
+ RefPtr<ConnectionEntry> ent = iter.Data();
+
+ 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;
+ }
+
+ ConnectionEntry* wcEnt =
+ GetOrCreateConnectionEntry(wildCardCI, true, false, false);
+ 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->HalfOpensLength(), ent->PendingQueueLength()));
+
+ LOG(
+ ("nsHttpConnectionMgr::MakeConnEntryWildCard wc-ent %p "
+ "idle=%zu active=%zu half=%zu pending=%zu\n",
+ wcEnt, wcEnt->IdleConnectionsLength(), wcEnt->ActiveConnsLength(),
+ wcEnt->HalfOpensLength(), wcEnt->PendingQueueLength()));
+
+ ent->MoveConnection(proxyConn, wcEnt);
+}
+
+bool nsHttpConnectionMgr::MoveTransToNewConnEntry(
+ nsHttpTransaction* aTrans, nsHttpConnectionInfo* aNewCI) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(("nsHttpConnectionMgr::MoveTransToNewConnEntry: trans=%p aNewCI=%s",
+ aTrans, aNewCI->HashKey().get()));
+
+ // Step 1: Get the transaction's connection entry.
+ ConnectionEntry* entry = mCT.GetWeak(aTrans->ConnectionInfo()->HashKey());
+ if (!entry) {
+ return false;
+ }
+
+ // Step 2: Try to find the undispatched transaction.
+ if (!entry->RemoveTransFromPendingQ(aTrans)) {
+ return false;
+ }
+
+ // Step 3: Add the transaction.
+ aTrans->UpdateConnectionInfo(aNewCI);
+ Unused << ProcessNewTransaction(aTrans);
+ return true;
+}
+
+void nsHttpConnectionMgr::IncreaseNumHalfOpenConns() { mNumHalfOpenConns++; }
+
+void nsHttpConnectionMgr::DecreaseNumHalfOpenConns() {
+ MOZ_ASSERT(mNumHalfOpenConns);
+ if (mNumHalfOpenConns) { // just in case
+ mNumHalfOpenConns--;
+ }
+}
+
+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();
+}
+
+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();
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.h b/netwerk/protocol/http/nsHttpConnectionMgr.h
new file mode 100644
index 0000000000..66d3621627
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -0,0 +1,453 @@
+/* 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 "HalfOpenSocket.h"
+#include "HttpConnectionBase.h"
+#include "HttpConnectionMgrShell.h"
+#include "nsHttpConnection.h"
+#include "nsHttpTransaction.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "nsClassHashtable.h"
+#include "nsDataHashtable.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Attributes.h"
+#include "ARefBase.h"
+#include "nsWeakReference.h"
+#include "ConnectionEntry.h"
+#include "TCPFastOpen.h"
+
+#include "nsINamed.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+
+class nsIHttpUpgradeListener;
+
+namespace mozilla {
+namespace net {
+class EventTokenBucket;
+class NullHttpTransaction;
+struct HttpRetParams;
+
+//-----------------------------------------------------------------------------
+
+// message handlers have this signature
+class nsHttpConnectionMgr;
+typedef void (nsHttpConnectionMgr::*nsConnEventHandler)(int32_t, ARefBase*);
+
+class nsHttpConnectionMgr final : public HttpConnectionMgrShell,
+ public nsIObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_HTTPCONNECTIONMGRSHELL
+ NS_DECL_NSIOBSERVER
+
+ //-------------------------------------------------------------------------
+ // 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 reason);
+
+ //-------------------------------------------------------------------------
+ // 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);
+
+ // Move a transaction from the pendingQ of it's connection entry to another
+ // one. Returns true if the transaction is moved successfully, otherwise
+ // returns false.
+ bool MoveTransToNewConnEntry(nsHttpTransaction* aTrans,
+ nsHttpConnectionInfo* aNewCI);
+
+ // 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*);
+
+ // 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);
+
+ 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 CurrentTopLevelOuterContentWindowId() {
+ return mCurrentTopLevelOuterContentWindowId;
+ }
+
+ void DoSpeculativeConnection(SpeculativeTransaction* aTrans,
+ bool aFetchHTTPSRR);
+
+ HttpConnectionBase* GetH2orH3ActiveConn(ConnectionEntry* ent, bool aNoHttp2,
+ bool aNoHttp3);
+
+ void IncreaseNumHalfOpenConns();
+ void DecreaseNumHalfOpenConns();
+
+ // 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);
+
+ public:
+ static nsAHttpConnection* MakeConnectionHandle(HttpConnectionBase* aWrapped);
+ void RegisterOriginCoalescingKey(HttpConnectionBase*, const nsACString& host,
+ int32_t port);
+
+ protected:
+ friend class ConnectionEntry;
+ void IncrementActiveConnCount();
+ void DecrementActiveConnCount(HttpConnectionBase*);
+
+ private:
+ friend class HalfOpenSocket;
+ friend class PendingTransactionInfo;
+
+ //-------------------------------------------------------------------------
+ // NOTE: these members may be accessed from any thread (use mReentrantMonitor)
+ //-------------------------------------------------------------------------
+
+ ReentrantMonitor mReentrantMonitor;
+ nsCOMPtr<nsIEventTarget> mSocketThreadTarget;
+
+ // connection limits
+ uint16_t mMaxUrgentExcessiveConns;
+ uint16_t mMaxConns;
+ uint16_t mMaxPersistConnsPerHost;
+ uint16_t mMaxPersistConnsPerProxy;
+ uint16_t mMaxRequestDelay; // in seconds
+ bool mThrottleEnabled;
+ uint32_t mThrottleVersion;
+ uint32_t mThrottleSuspendFor;
+ uint32_t mThrottleResumeFor;
+ uint32_t mThrottleReadLimit;
+ uint32_t mThrottleReadInterval;
+ uint32_t mThrottleHoldTime;
+ TimeDuration mThrottleMaxTime;
+ bool mBeConservativeForProxy;
+ Atomic<bool, mozilla::Relaxed> mIsShuttingDown;
+
+ //-------------------------------------------------------------------------
+ // NOTE: these members are only accessed on the socket transport thread
+ //-------------------------------------------------------------------------
+
+ [[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 ProcessNewTransaction(nsHttpTransaction*);
+ [[nodiscard]] nsresult EnsureSocketThreadTarget();
+ void ReportProxyTelemetry(ConnectionEntry* ent);
+ [[nodiscard]] nsresult CreateTransport(
+ ConnectionEntry*, nsAHttpTransaction*, uint32_t, bool, bool, bool, bool,
+ PendingTransactionInfo* pendingTransInfo);
+ void StartedConnect();
+ void RecvdConnect();
+
+ ConnectionEntry* GetOrCreateConnectionEntry(nsHttpConnectionInfo*,
+ bool allowWildCard, bool aNoHttp2,
+ bool aNoHttp3);
+
+ [[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);
+
+ 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(int32_t, 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 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 OnMsgUpdateCurrentTopLevelOuterContentWindowId(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;
+ // Total number of idle connections in all of the ConnectionEntry objects
+ // that are accessed from mCT connection table.
+ uint16_t mNumIdleConns;
+ // Total number of spdy or http3 connections which are a subset of the active
+ // conns
+ uint16_t mNumSpdyHttp3ActiveConns;
+ // Total number of connections in mHalfOpens ConnectionEntry objects
+ // that are accessed from mCT connection table
+ uint32_t mNumHalfOpenConns;
+
+ // Holds time in seconds for next wake-up to prune dead connections.
+ uint64_t mTimeOfNextWakeUp;
+ // Timer for next pruning of dead connections.
+ nsCOMPtr<nsITimer> mTimer;
+ // Timer for pruning stalled connections after changed network.
+ nsCOMPtr<nsITimer> mTrafficTimer;
+ bool mPruningNoTraffic;
+
+ // 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;
+ uint32_t mTimeoutTickNext;
+
+ //
+ // 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 mCurrentTopLevelOuterContentWindowId;
+
+ // 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;
+
+ 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 excludeActive = false);
+ void ResumeReadOf(nsTArray<RefPtr<nsHttpTransaction>>*);
+
+ // Cached status of the active tab active transactions existence,
+ // saves a lot of hashtable lookups
+ bool mActiveTabTransactionsExist;
+ bool mActiveTabUnthrottledTransactionsExist;
+
+ void LogActiveTransactions(char);
+
+ // When current active tab is changed, this function uses
+ // |previousWindowId| to select background transactions and
+ // mCurrentTopLevelOuterContentWindowId| to select foreground transactions.
+ // Then, it notifies selected transactions' connection of the new active tab
+ // id.
+ void NotifyConnectionOfWindowIdChange(uint64_t previousWindowId);
+
+ // A test if be-conservative should be used when proxy is setup for the
+ // connection
+ bool BeConservativeIfProxied(nsIProxyInfo* proxy);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // !nsHttpConnectionMgr_h__
diff --git a/netwerk/protocol/http/nsHttpDigestAuth.cpp b/netwerk/protocol/http/nsHttpDigestAuth.cpp
new file mode 100644
index 0000000000..ed725da702
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpDigestAuth.cpp
@@ -0,0 +1,662 @@
+/* -*- 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"
+
+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::MD5Hash(const char* buf, uint32_t len) {
+ 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;
+ }
+ }
+
+ rv = mVerifier->Init(nsICryptoHash::MD5);
+ 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() == sizeof(mHashBuf));
+ 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 char* 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 (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 char* challenge,
+ bool isProxyAuth, const char16_t* domain, const char16_t* username,
+ const char16_t* password, nsISupports* sessionState,
+ nsISupports* continuationState, nsICancelable** aCancellable) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpDigestAuth::GenerateCredentials(
+ nsIHttpAuthenticableChannel* authChannel, const char* challenge,
+ bool isProxyAuth, const char16_t* userdomain, const char16_t* username,
+ const char16_t* password, nsISupports** sessionState,
+ nsISupports** continuationState, uint32_t* aFlags, char** creds)
+
+{
+ LOG(("nsHttpDigestAuth::GenerateCredentials [challenge=%s]\n", challenge));
+
+ NS_ENSURE_ARG_POINTER(creds);
+
+ *aFlags = 0;
+
+ bool isDigestAuth = !PL_strncasecmp(challenge, "digest ", 7);
+ 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 =
+ !PL_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(challenge, 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;
+ }
+
+ char ha1_digest[EXPANDED_DIGEST_LENGTH + 1];
+ char ha2_digest[EXPANDED_DIGEST_LENGTH + 1];
+ char response_digest[EXPANDED_DIGEST_LENGTH + 1];
+ char upload_data_digest[EXPANDED_DIGEST_LENGTH + 1];
+
+ 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 || algorithm & ALGO_MD5_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, qop, upload_data_digest, ha2_digest);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = CalculateResponse(ha1_digest, ha2_digest, 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
+ 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 = ToNewCString(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, const nsCString& nonce,
+ uint16_t qop, const char* nonce_count, const nsCString& cnonce,
+ char* result) {
+ uint32_t len = 2 * EXPANDED_DIGEST_LENGTH + 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, EXPANDED_DIGEST_LENGTH);
+ 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, EXPANDED_DIGEST_LENGTH);
+
+ nsresult rv = MD5Hash(contents.get(), contents.Length());
+ if (NS_SUCCEEDED(rv)) rv = ExpandToHex(mHashBuf, result);
+ return rv;
+}
+
+nsresult nsHttpDigestAuth::ExpandToHex(const char* digest, char* result) {
+ int16_t index, value;
+
+ for (index = 0; index < DIGEST_LENGTH; 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[EXPANDED_DIGEST_LENGTH] = 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) {
+ int16_t len = username.Length() + password.Length() + realm.Length() + 2;
+ if (algorithm & ALGO_MD5_SESS) {
+ int16_t exlen =
+ EXPANDED_DIGEST_LENGTH + 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 = MD5Hash(contents.get(), contents.Length());
+ if (NS_FAILED(rv)) return rv;
+
+ if (algorithm & ALGO_MD5_SESS) {
+ char part1[EXPANDED_DIGEST_LENGTH + 1];
+ rv = ExpandToHex(mHashBuf, part1);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ contents.Assign(part1, EXPANDED_DIGEST_LENGTH);
+ contents.Append(':');
+ contents.Append(nonce);
+ contents.Append(':');
+ contents.Append(cnonce);
+
+ rv = MD5Hash(contents.get(), contents.Length());
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return ExpandToHex(mHashBuf, result);
+}
+
+nsresult nsHttpDigestAuth::CalculateHA2(const nsCString& method,
+ const nsCString& path, uint16_t qop,
+ const char* bodyDigest, char* result) {
+ uint16_t methodLen = method.Length();
+ uint32_t pathLen = path.Length();
+ uint32_t len = methodLen + pathLen + 1;
+
+ if (qop & QOP_AUTH_INT) {
+ len += EXPANDED_DIGEST_LENGTH + 1;
+ }
+
+ nsAutoCString contents;
+ contents.SetCapacity(len);
+
+ contents.Assign(method);
+ contents.Append(':');
+ contents.Append(path);
+
+ if (qop & QOP_AUTH_INT) {
+ contents.Append(':');
+ contents.Append(bodyDigest, EXPANDED_DIGEST_LENGTH);
+ }
+
+ nsresult rv = MD5Hash(contents.get(), contents.Length());
+ if (NS_SUCCEEDED(rv)) rv = ExpandToHex(mHashBuf, result);
+ return rv;
+}
+
+nsresult nsHttpDigestAuth::ParseChallenge(const char* challenge,
+ 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 (strlen(challenge) > 16000000) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const char* p = challenge + 6; // first 6 characters are "Digest"
+
+ *stale = false;
+ *algorithm = ALGO_MD5; // default is MD5
+ *qop = 0;
+
+ for (;;) {
+ while (*p && (*p == ',' || nsCRT::IsAsciiSpace(*p))) ++p;
+ if (!*p) break;
+
+ // name
+ int32_t nameStart = (p - challenge);
+ while (*p && !nsCRT::IsAsciiSpace(*p) && *p != '=') ++p;
+ if (!*p) return NS_ERROR_INVALID_ARG;
+ int32_t nameLength = (p - challenge) - nameStart;
+
+ while (*p && (nsCRT::IsAsciiSpace(*p) || *p == '=')) ++p;
+ if (!*p) 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 && *p != '"') ++p;
+ if (*p != '"') return NS_ERROR_INVALID_ARG;
+ valueLength = (p - challenge) - valueStart;
+ ++p;
+ } else {
+ while (*p && !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 (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..eb110d3b89
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpDigestAuth.h
@@ -0,0 +1,93 @@
+/* -*- 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 QOP_AUTH 0x01
+#define QOP_AUTH_INT 0x02
+
+#define DIGEST_LENGTH 16
+#define EXPANDED_DIGEST_LENGTH 32
+#define NONCE_COUNT_LENGTH 8
+
+//-----------------------------------------------------------------------------
+// nsHttpDigestAuth
+//-----------------------------------------------------------------------------
+
+class nsHttpDigestAuth final : public nsIHttpAuthenticator {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHTTPAUTHENTICATOR
+
+ nsHttpDigestAuth() = default;
+
+ static already_AddRefed<nsIHttpAuthenticator> GetOrCreate();
+
+ protected:
+ ~nsHttpDigestAuth() = default;
+
+ [[nodiscard]] nsresult ExpandToHex(const char* digest, char* result);
+
+ [[nodiscard]] nsresult CalculateResponse(const char* ha1_digest,
+ const char* ha2_digest,
+ 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 qop, const char* body_digest,
+ char* result);
+
+ [[nodiscard]] nsresult ParseChallenge(const char* challenge,
+ nsACString& realm, nsACString& domain,
+ nsACString& nonce, nsACString& opaque,
+ bool* stale, uint16_t* algorithm,
+ uint16_t* qop);
+
+ // result is in mHashBuf
+ [[nodiscard]] nsresult MD5Hash(const char* buf, uint32_t len);
+
+ [[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[DIGEST_LENGTH];
+
+ 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..3e86877ed0
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -0,0 +1,3053 @@
+/* -*- 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 "nsError.h"
+#include "nsHttp.h"
+#include "nsHttpHandler.h"
+#include "nsHttpChannel.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/ClearOnShutdown.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 "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/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"
+
+#if defined(XP_UNIX)
+# include <sys/utsname.h>
+#endif
+
+#if defined(XP_WIN)
+# include <windows.h>
+# include "mozilla/WindowsVersion.h"
+#endif
+
+#if defined(XP_MACOSX)
+# include <CoreServices/CoreServices.h>
+# include "nsCocoaFeatures.h"
+#endif
+
+#ifdef MOZ_TASK_TRACER
+# include "GeckoTaskTracer.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 TCP_FAST_OPEN_ENABLE "network.tcp.tcp_fastopen_enable"
+#define TCP_FAST_OPEN_FAILURE_LIMIT \
+ "network.tcp.tcp_fastopen_consecutive_failure_limit"
+#define TCP_FAST_OPEN_STALLS_LIMIT "network.tcp.tcp_fastopen_http_stalls_limit"
+#define TCP_FAST_OPEN_STALLS_IDLE \
+ "network.tcp.tcp_fastopen_http_check_for_stalls_only_if_idle_for"
+#define TCP_FAST_OPEN_STALLS_TIMEOUT \
+ "network.tcp.tcp_fastopen_http_stalls_timeout"
+
+#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
+
+//-----------------------------------------------------------------------------
+// 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::ShutdownPostLastCycleCollection);
+ }
+ 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_webp_enabled()) {
+ mimeTypes.Append("image/webp,");
+ }
+
+ mimeTypes.Append("*/*");
+
+ 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()
+ : mHttpVersion(HttpVersion::v1_1),
+ mProxyHttpVersion(HttpVersion::v1_1),
+ mCapabilities(NS_HTTP_ALLOW_KEEPALIVE),
+ mFastFallbackToIPv4(false),
+ mIdleTimeout(PR_SecondsToInterval(10)),
+ mSpdyTimeout(PR_SecondsToInterval(180)),
+ mResponseTimeout(PR_SecondsToInterval(300)),
+ mResponseTimeoutEnabled(false),
+ mNetworkChangedTimeout(5000),
+ mMaxRequestAttempts(6),
+ mMaxRequestDelay(10),
+ mIdleSynTimeout(250),
+ mFallbackSynTimeout(5),
+ mH2MandatorySuiteEnabled(false),
+ mMaxUrgentExcessiveConns(3),
+ mMaxConnections(24),
+ mMaxPersistentConnectionsPerServer(2),
+ mMaxPersistentConnectionsPerProxy(4),
+ mThrottleEnabled(true),
+ mThrottleVersion(2),
+ mThrottleSuspendFor(3000),
+ mThrottleResumeFor(200),
+ mThrottleReadLimit(8000),
+ mThrottleReadInterval(500),
+ mThrottleHoldTime(600),
+ mThrottleMaxTime(3000),
+ mSendWindowSize(1024),
+ mUrgentStartEnabled(true),
+ mTailBlockingEnabled(true),
+ mTailDelayQuantum(600),
+ mTailDelayQuantumAfterDCL(100),
+ mTailDelayMax(6000),
+ mTailTotalMax(0),
+ mRedirectionLimit(10),
+ mBeConservativeForProxy(true),
+ mPhishyUserPassLength(1),
+ mQoSBits(0x00),
+ mEnforceAssocReq(false),
+ mImageAcceptHeader(ImageAcceptHeader()),
+ mDocumentAcceptHeader(DocumentAcceptHeader(ImageAcceptHeader())),
+ mLastUniqueID(NowInSeconds()),
+ mSessionStartTime(0),
+ mLegacyAppName("Mozilla"),
+ mLegacyAppVersion("5.0"),
+ mProduct("Gecko"),
+ mCompatFirefoxEnabled(false),
+ mUserAgentIsDirty(true),
+ mAcceptLanguagesIsDirty(true),
+ mPromptTempRedirect(true),
+ mEnablePersistentHttpsCaching(false),
+ mSafeHintEnabled(false),
+ mParentalControlEnabled(false),
+ mHandlerActive(false),
+ mDebugObservations(false),
+ mEnableSpdy(false),
+ mHttp2Enabled(true),
+ mUseH2Deps(true),
+ mEnforceHttp2TlsProfile(true),
+ mCoalesceSpdy(true),
+ mSpdyPersistentSettings(false),
+ mAllowPush(true),
+ mEnableAltSvc(false),
+ mEnableAltSvcOE(false),
+ mEnableOriginExtension(false),
+ mEnableH2Websockets(true),
+ mDumpHpackTables(false),
+ mSpdySendingChunkSize(ASpdySession::kSendingChunkSize),
+ mSpdySendBufferSize(ASpdySession::kTCPSendBufferSize),
+ mSpdyPushAllowance(131072) // match default pref
+ ,
+ mSpdyPullAllowance(ASpdySession::kInitialRwin),
+ mDefaultSpdyConcurrent(ASpdySession::kDefaultMaxConcurrent),
+ mSpdyPingThreshold(PR_SecondsToInterval(58)),
+ mSpdyPingTimeout(PR_SecondsToInterval(8)),
+ mConnectTimeout(90000),
+ mTLSHandshakeTimeout(30000),
+ mParallelSpeculativeConnectLimit(6),
+ mRequestTokenBucketEnabled(true),
+ mRequestTokenBucketMinParallelism(6),
+ mRequestTokenBucketHz(100),
+ mRequestTokenBucketBurst(32),
+ mCriticalRequestPrioritization(true),
+ mTCPKeepaliveShortLivedEnabled(false),
+ mTCPKeepaliveShortLivedTimeS(60),
+ mTCPKeepaliveShortLivedIdleTimeS(10),
+ mTCPKeepaliveLongLivedEnabled(false),
+ mTCPKeepaliveLongLivedIdleTimeS(600),
+ mEnforceH1Framing(FRAMECHECK_BARELY),
+ mDefaultHpackBuffer(4096),
+ mBug1563538(true),
+ mHttp3Enabled(true),
+ mQpackTableSize(4096),
+ mHttp3MaxBlockedStreams(10),
+ mMaxHttpResponseHeaderSize(393216),
+ mFocusedWindowTransactionRatio(0.9f),
+ mSpeculativeConnectEnabled(false),
+ mUseFastOpen(true),
+ mFastOpenConsecutiveFailureLimit(5),
+ mFastOpenConsecutiveFailureCounter(0),
+ mFastOpenStallsLimit(3),
+ mFastOpenStallsCounter(0),
+ mFastOpenStallsIdleTime(10),
+ mFastOpenStallsTimeout(20),
+ mActiveTabPriority(true),
+ mProcessId(0),
+ mNextChannelId(1),
+ mLastActiveTabLoadOptimizationLock(
+ "nsHttpConnectionMgr::LastActiveTabLoadOptimization"),
+ mHttpExclusionLock("nsHttpHandler::HttpExclusion"),
+ mThroughCaptivePortal(false) {
+ 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);
+ }
+
+ mFastOpenSupported = false;
+ if (XRE_IsParentProcess()) {
+ SetFastOpenOSSupport();
+ }
+}
+
+void nsHttpHandler::SetFastOpenOSSupport() {
+#if !defined(XP_WIN) && !defined(XP_LINUX) && !defined(ANDROID) && \
+ !defined(HAS_CONNECTX)
+ return;
+#elif defined(XP_WIN)
+ mFastOpenSupported = IsWindows10BuildOrLater(16299);
+
+ if (mFastOpenSupported) {
+ // We have some problems with lavasoft software and tcp fast open.
+ if (GetModuleHandleW(L"pmls64.dll") || GetModuleHandleW(L"rlls64.dll")) {
+ mFastOpenSupported = false;
+ }
+ }
+#else
+
+ nsAutoCString version;
+ nsresult rv;
+# ifdef ANDROID
+ nsCOMPtr<nsIPropertyBag2> infoService =
+ do_GetService("@mozilla.org/system-info;1");
+ MOZ_ASSERT(infoService, "Could not find a system info service");
+ rv = infoService->GetPropertyAsACString(u"sdk_version"_ns, version);
+# else
+ char buf[SYS_INFO_BUFFER_LENGTH];
+ if (PR_GetSystemInfo(PR_SI_RELEASE, buf, sizeof(buf)) == PR_SUCCESS) {
+ version = buf;
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+# endif
+
+ LOG(("nsHttpHandler::SetFastOpenOSSupport version %s", version.get()));
+
+ if (NS_SUCCEEDED(rv)) {
+ // set min version minus 1.
+# if XP_MACOSX
+ int min_version[] = {17, 5}; // High Sierra 10.13.4
+# elif ANDROID
+ int min_version[] = {4, 4};
+# elif XP_LINUX
+ int min_version[] = {3, 6};
+# endif
+ int inx = 0;
+ nsCCharSeparatedTokenizer tokenizer(version, '.');
+ while ((inx < 2) && tokenizer.hasMoreTokens()) {
+ nsAutoCString token(tokenizer.nextToken());
+ const char* nondigit = NS_strspnp("0123456789", token.get());
+ if (nondigit && *nondigit) {
+ break;
+ }
+ nsresult rv;
+ int32_t ver = token.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ if (ver > min_version[inx]) {
+ mFastOpenSupported = true;
+ break;
+ } else if (ver == min_version[inx] && inx == 1) {
+ mFastOpenSupported = true;
+ } else if (ver < min_version[inx]) {
+ break;
+ }
+ inx++;
+ }
+ }
+#endif
+ LOG(("nsHttpHandler::SetFastOpenOSSupport %s supported.\n",
+ mFastOpenSupported ? "" : "not"));
+}
+
+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,
+ TCP_FAST_OPEN_ENABLE,
+ TCP_FAST_OPEN_FAILURE_LIMIT,
+ TCP_FAST_OPEN_STALLS_LIMIT,
+ TCP_FAST_OPEN_STALLS_IDLE,
+ TCP_FAST_OPEN_STALLS_TIMEOUT,
+ "image.http.accept",
+ "image.avif.enabled",
+ "image.webp.enabled",
+ nullptr,
+};
+
+nsresult nsHttpHandler::Init() {
+ nsresult rv;
+
+ LOG(("nsHttpHandler::Init\n"));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ 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);
+ }
+
+ auto initQLogDir = [&]() {
+ 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,
+ mHttp3Enabled);
+
+ mMisc.AssignLiteral("rv:" MOZILLA_UAVERSION);
+
+ 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);
+ }
+
+ // 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().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);
+
+ if (!IsNeckoChild()) {
+ obsService->AddObserver(
+ this, "net:current-toplevel-outer-content-windowid", true);
+ }
+
+ if (mFastOpenSupported) {
+ obsService->AddObserver(this, "captive-portal-login", true);
+ obsService->AddObserver(this, "captive-portal-login-success", 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]() {
+ HttpConnectionMgrParent* parent =
+ self->mConnMgr->AsHttpConnectionMgrParent();
+ Unused << SocketProcessParent::GetSingleton()
+ ->SendPHttpConnectionMgrConstructor(
+ parent,
+ HttpHandlerInitArgs(
+ self->mFastOpenSupported, 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) {
+ nsresult rv;
+
+ // Add the "User-Agent" header
+ rv = request->SetHeader(nsHttp::User_Agent, UserAgent(), 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 &&
+ (!PL_strcasecmp(enc, "gzip") || !PL_strcasecmp(enc, "deflate") ||
+ !PL_strcasecmp(enc, "x-gzip") || !PL_strcasecmp(enc, "x-deflate"))) {
+ rv = true;
+ }
+ LOG(("nsHttpHandler::IsAceptableEncoding %s https=%d %d\n", enc, isSecure,
+ rv));
+ return rv;
+}
+
+void nsHttpHandler::IncrementFastOpenConsecutiveFailureCounter() {
+ LOG(
+ ("nsHttpHandler::IncrementFastOpenConsecutiveFailureCounter - "
+ "failed=%d failure_limit=%d",
+ mFastOpenConsecutiveFailureCounter, mFastOpenConsecutiveFailureLimit));
+ if (mFastOpenConsecutiveFailureCounter < mFastOpenConsecutiveFailureLimit) {
+ mFastOpenConsecutiveFailureCounter++;
+ if (mFastOpenConsecutiveFailureCounter ==
+ mFastOpenConsecutiveFailureLimit) {
+ LOG(
+ ("nsHttpHandler::IncrementFastOpenConsecutiveFailureCounter - "
+ "Fast open failed too many times"));
+ }
+ }
+}
+
+void nsHttpHandler::IncrementFastOpenStallsCounter() {
+ LOG(
+ ("nsHttpHandler::IncrementFastOpenStallsCounter - failed=%d "
+ "failure_limit=%d",
+ mFastOpenStallsCounter, mFastOpenStallsLimit));
+ if (mFastOpenStallsCounter < mFastOpenStallsLimit) {
+ mFastOpenStallsCounter++;
+ if (mFastOpenStallsCounter == mFastOpenStallsLimit) {
+ LOG(
+ ("nsHttpHandler::IncrementFastOpenStallsCounter - "
+ "There are too many stalls involving TFO and TLS."));
+ }
+ }
+}
+
+nsresult nsHttpHandler::GetStreamConverterService(
+ nsIStreamConverterService** result) {
+ if (!mStreamConvSvc) {
+ nsresult rv;
+ nsCOMPtr<nsIStreamConverterService> service =
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+ mStreamConvSvc = new nsMainThreadPtrHolder<nsIStreamConverterService>(
+ "nsHttpHandler::mStreamConvSvc", service);
+ }
+ *result = do_AddRef(mStreamConvSvc.get()).take();
+ return NS_OK;
+}
+
+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 [chan=%p event=\"%s\"]\n", 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);
+
+ AntiTrackingRedirectHeuristic(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() {
+ if (nsContentUtils::ShouldResistFingerprinting() &&
+ !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
+# define OSCPU_WINDOWS "Windows NT %ld.%ld"
+# 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;
+ }
+
+#ifndef MOZ_UA_OS_AGNOSTIC
+ // 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
+ );
+#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.
+# if defined MOZ_WIDGET_ANDROID
+# ifndef MOZ_UA_OS_AGNOSTIC // Don't add anything to mPlatform since it's
+ // empty.
+ nsAutoString androidVersion;
+ rv = infoService->GetPropertyAsAString(u"release_version"_ns, androidVersion);
+ if (NS_SUCCEEDED(rv)) {
+ mPlatform += " ";
+ // If the 2nd character is a ".", we know the major version is a single
+ // digit. If we're running on a version below 4 we pretend to be on
+ // Android KitKat (4.4) to work around scripts sniffing for low versions.
+ if (androidVersion[1] == 46 && androidVersion[0] < 52) {
+ mPlatform += "4.4";
+ } else {
+ mPlatform += NS_LossyConvertUTF16toASCII(androidVersion);
+ }
+ }
+# endif
+# endif
+ // Add the `Mobile` or `Tablet` or `TV` token when running on device.
+ bool isTablet;
+ rv = infoService->GetPropertyAsBool(u"tablet"_ns, &isTablet);
+ if (NS_SUCCEEDED(rv) && isTablet) {
+ mCompatDevice.AssignLiteral("Tablet");
+ } else {
+ 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
+
+#ifndef MOZ_UA_OS_AGNOSTIC
+ // Gather OS/CPU.
+# if defined(XP_WIN)
+ OSVERSIONINFO info = {sizeof(OSVERSIONINFO)};
+# pragma warning(push)
+# pragma warning(disable : 4996)
+ if (GetVersionEx(&info)) {
+# pragma warning(pop)
+
+ const char* format;
+# if defined _M_X64 || defined _M_AMD64
+ format = OSCPU_WIN64;
+# else
+ BOOL isWow64 = FALSE;
+ if (!IsWow64Process(GetCurrentProcess(), &isWow64)) {
+ isWow64 = FALSE;
+ }
+ format = isWow64 ? OSCPU_WIN64 : OSCPU_WINDOWS;
+# endif
+
+ SmprintfPointer buf =
+ mozilla::Smprintf(format, info.dwMajorVersion, info.dwMinorVersion);
+ if (buf) {
+ mOscpu = buf.get();
+ }
+ }
+# elif defined(XP_MACOSX)
+ SInt32 majorVersion = nsCocoaFeatures::macOSVersionMajor();
+ SInt32 minorVersion = nsCocoaFeatures::macOSVersionMinor();
+
+ // Always return an "Intel" UA string, even on ARM64 macOS like Safari does.
+ mOscpu =
+ nsPrintfCString("Intel Mac OS X %d.%d", static_cast<int>(majorVersion),
+ static_cast<int>(minorVersion));
+# elif defined(XP_UNIX)
+ 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
+#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) || !PL_strcmp(pref, p))
+#define MULTI_PREF_CHANGED(p) \
+ ((pref == nullptr) || !PL_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("spdy.enabled"))) {
+ rv = Preferences::GetBool(HTTP_PREF("spdy.enabled"), &cVar);
+ if (NS_SUCCEEDED(rv)) mEnableSpdy = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.enabled.http2"))) {
+ rv = Preferences::GetBool(HTTP_PREF("spdy.enabled.http2"), &cVar);
+ if (NS_SUCCEEDED(rv)) mHttp2Enabled = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.enabled.deps"))) {
+ rv = Preferences::GetBool(HTTP_PREF("spdy.enabled.deps"), &cVar);
+ if (NS_SUCCEEDED(rv)) mUseH2Deps = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.enforce-tls-profile"))) {
+ rv = Preferences::GetBool(HTTP_PREF("spdy.enforce-tls-profile"), &cVar);
+ if (NS_SUCCEEDED(rv)) mEnforceHttp2TlsProfile = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.coalesce-hostnames"))) {
+ rv = Preferences::GetBool(HTTP_PREF("spdy.coalesce-hostnames"), &cVar);
+ if (NS_SUCCEEDED(rv)) mCoalesceSpdy = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.persistent-settings"))) {
+ rv = Preferences::GetBool(HTTP_PREF("spdy.persistent-settings"), &cVar);
+ if (NS_SUCCEEDED(rv)) mSpdyPersistentSettings = cVar;
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("spdy.timeout"), &val);
+ if (NS_SUCCEEDED(rv))
+ mSpdyTimeout = PR_SecondsToInterval(clamped(val, 1, 0xffff));
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.chunk-size"))) {
+ // keep this within http/2 ranges of 1 to 2^14-1
+ rv = Preferences::GetInt(HTTP_PREF("spdy.chunk-size"), &val);
+ if (NS_SUCCEEDED(rv))
+ mSpdySendingChunkSize = (uint32_t)clamped(val, 1, 0x3fff);
+ }
+
+ // The amount of idle seconds on a spdy connection before initiating a
+ // server ping. 0 will disable.
+ if (PREF_CHANGED(HTTP_PREF("spdy.ping-threshold"))) {
+ rv = Preferences::GetInt(HTTP_PREF("spdy.ping-threshold"), &val);
+ if (NS_SUCCEEDED(rv))
+ mSpdyPingThreshold =
+ PR_SecondsToInterval((uint16_t)clamped(val, 0, 0x7fffffff));
+ }
+
+ // The amount of seconds to wait for a spdy ping response before
+ // closing the session.
+ if (PREF_CHANGED(HTTP_PREF("spdy.ping-timeout"))) {
+ rv = Preferences::GetInt(HTTP_PREF("spdy.ping-timeout"), &val);
+ if (NS_SUCCEEDED(rv))
+ mSpdyPingTimeout =
+ PR_SecondsToInterval((uint16_t)clamped(val, 0, 0x7fffffff));
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.allow-push"))) {
+ rv = Preferences::GetBool(HTTP_PREF("spdy.allow-push"), &cVar);
+ if (NS_SUCCEEDED(rv)) mAllowPush = cVar;
+ }
+
+ 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("spdy.websockets"))) {
+ rv = Preferences::GetBool(HTTP_PREF("spdy.websockets"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mEnableH2Websockets = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.push-allowance"))) {
+ rv = Preferences::GetInt(HTTP_PREF("spdy.push-allowance"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mSpdyPushAllowance = static_cast<uint32_t>(
+ clamped(val, 1024, static_cast<int32_t>(ASpdySession::kInitialRwin)));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.pull-allowance"))) {
+ rv = Preferences::GetInt(HTTP_PREF("spdy.pull-allowance"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mSpdyPullAllowance =
+ static_cast<uint32_t>(clamped(val, 1024, 0x7fffffff));
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.default-concurrent"))) {
+ rv = Preferences::GetInt(HTTP_PREF("spdy.default-concurrent"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mDefaultSpdyConcurrent = static_cast<uint32_t>(
+ std::max<int32_t>(std::min<int32_t>(val, 9999), 1));
+ }
+ }
+
+ // The amount of seconds to wait for a spdy ping response before
+ // closing the session.
+ if (PREF_CHANGED(HTTP_PREF("spdy.send-buffer-size"))) {
+ rv = Preferences::GetInt(HTTP_PREF("spdy.send-buffer-size"), &val);
+ if (NS_SUCCEEDED(rv))
+ mSpdySendBufferSize = (uint32_t)clamped(val, 1500, 0x7fffffff);
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.enable-hpack-dump"))) {
+ rv = Preferences::GetBool(HTTP_PREF("spdy.enable-hpack-dump"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mDumpHpackTables = cVar;
+ }
+ }
+
+ // 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(TCP_FAST_OPEN_ENABLE)) {
+ rv = Preferences::GetBool(TCP_FAST_OPEN_ENABLE, &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mUseFastOpen = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(TCP_FAST_OPEN_FAILURE_LIMIT)) {
+ rv = Preferences::GetInt(TCP_FAST_OPEN_FAILURE_LIMIT, &val);
+ if (NS_SUCCEEDED(rv)) {
+ if (val < 0) {
+ val = 0;
+ }
+ mFastOpenConsecutiveFailureLimit = val;
+ }
+ }
+
+ if (PREF_CHANGED(TCP_FAST_OPEN_STALLS_LIMIT)) {
+ rv = Preferences::GetInt(TCP_FAST_OPEN_STALLS_LIMIT, &val);
+ if (NS_SUCCEEDED(rv)) {
+ if (val < 0) {
+ val = 0;
+ }
+ mFastOpenStallsLimit = val;
+ }
+ }
+
+ if (PREF_CHANGED(TCP_FAST_OPEN_STALLS_TIMEOUT)) {
+ rv = Preferences::GetInt(TCP_FAST_OPEN_STALLS_TIMEOUT, &val);
+ if (NS_SUCCEEDED(rv)) {
+ if (val < 0) {
+ val = 0;
+ }
+ mFastOpenStallsTimeout = val;
+ }
+ }
+
+ if (PREF_CHANGED(TCP_FAST_OPEN_STALLS_IDLE)) {
+ rv = Preferences::GetInt(TCP_FAST_OPEN_STALLS_IDLE, &val);
+ if (NS_SUCCEEDED(rv)) {
+ if (val < 0) {
+ val = 0;
+ }
+ mFastOpenStallsIdleTime = val;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.default-hpack-buffer"))) {
+ rv = Preferences::GetInt(HTTP_PREF("spdy.default-hpack-buffer"), &val);
+ if (NS_SUCCEEDED(rv)) {
+ mDefaultHpackBuffer = val;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("spdy.bug1563538"))) {
+ rv = Preferences::GetBool(HTTP_PREF("spdy.bug1563538"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mBug1563538 = cVar;
+ }
+ }
+
+ if (PREF_CHANGED(HTTP_PREF("http3.enabled"))) {
+ rv = Preferences::GetBool(HTTP_PREF("http3.enabled"), &cVar);
+ if (NS_SUCCEEDED(rv)) {
+ mHttp3Enabled = cVar;
+ }
+ }
+
+ 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.webp.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) {
+ auto* map = new nsCString(Substring(token, index + 1));
+ mAltSvcMappingTemptativeMap.Put(Substring(token, 0, index), map);
+ }
+ }
+ }
+ }
+
+ 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);
+}
+
+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, new SyncRunnable(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::GetDefaultPort(int32_t* result) {
+ *result = NS_HTTP_DEFAULT_PORT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpHandler::GetProtocolFlags(uint32_t* result) {
+ *result = NS_HTTP_PROTOCOL_FLAGS;
+ 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;
+
+#ifdef MOZ_TASK_TRACER
+ if (tasktracer::IsStartLogging()) {
+ nsAutoCString urispec;
+ uri->GetSpec(urispec);
+ tasktracer::AddLabel("nsHttpHandler::NewProxiedChannel2 %s", urispec.get());
+ }
+#endif
+
+ 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());
+ if (NS_FAILED(rv)) return rv;
+
+ // TRRServiceChannel doesn't need loadInfo.
+ if (aLoadInfo) {
+ // set the loadInfo on the new channel
+ rv = httpChannel->SetLoadInfo(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();
+ 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);
+ }
+
+ if (UseFastOpen()) {
+ Telemetry::Accumulate(Telemetry::TCP_FAST_OPEN_STATUS, 0);
+ } else if (!mFastOpenSupported) {
+ Telemetry::Accumulate(Telemetry::TCP_FAST_OPEN_STATUS, 1);
+ } else if (!mUseFastOpen) {
+ Telemetry::Accumulate(Telemetry::TCP_FAST_OPEN_STATUS, 2);
+ } else if (mFastOpenConsecutiveFailureCounter >=
+ mFastOpenConsecutiveFailureLimit) {
+ Telemetry::Accumulate(Telemetry::TCP_FAST_OPEN_STATUS, 3);
+ } else {
+ Telemetry::Accumulate(Telemetry::TCP_FAST_OPEN_STATUS, 4);
+ }
+ }
+ } 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();
+ }
+ } 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)) {
+ nsAutoCString converted = NS_ConvertUTF16toUTF8(data);
+ if (!strcmp(converted.get(), NS_NETWORK_LINK_DATA_CHANGED)) {
+ 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-toplevel-outer-content-windowid")) {
+ // The window id will be updated by HttpConnectionMgrParent.
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(subject);
+ MOZ_RELEASE_ASSERT(wrapper);
+
+ uint64_t windowId = 0;
+ wrapper->GetData(&windowId);
+ MOZ_ASSERT(windowId);
+
+ static uint64_t sCurrentTopLevelOuterContentWindowId = 0;
+ if (sCurrentTopLevelOuterContentWindowId != windowId) {
+ sCurrentTopLevelOuterContentWindowId = windowId;
+ if (mConnMgr) {
+ mConnMgr->UpdateCurrentTopLevelOuterContentWindowId(
+ sCurrentTopLevelOuterContentWindowId);
+ }
+ }
+ }
+ } else if (!strcmp(topic, "captive-portal-login") ||
+ !strcmp(topic, "captive-portal-login-success")) {
+ // We have detected a captive portal and we will reset the Fast Open
+ // failure counter.
+ ResetFastOpenConsecutiveFailureCounter();
+ } else if (!strcmp(topic, "psm:user-certificate-added")) {
+ // A user certificate has just been added.
+ // We should immediately disable speculative connect
+ mSpeculativeConnectEnabled = false;
+ } else if (!strcmp(topic, "psm:user-certificate-deleted")) {
+ // If a user certificate has been removed, we need to check if there
+ // are others installed
+ MaybeEnableSpeculativeConnect();
+ } 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, "browser-delayed-startup-finished")) {
+ MaybeEnableSpeculativeConnect();
+ } 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();
+ }
+
+ return NS_OK;
+}
+
+// nsISpeculativeConnect
+
+static bool CanEnableSpeculativeConnect() {
+ nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID));
+
+ MOZ_ASSERT(!NS_IsMainThread(), "Must run on the background thread");
+ // Check if any 3rd party PKCS#11 module are installed, as they may produce
+ // client certificates
+ bool activeSmartCards = false;
+ nsresult rv = component->HasActiveSmartCards(&activeSmartCards);
+ if (NS_FAILED(rv) || activeSmartCards) {
+ return false;
+ }
+
+ // If there are any client certificates installed, we can't enable speculative
+ // connect, as it may pop up the certificate chooser at any time.
+ bool hasUserCerts = false;
+ rv = component->HasUserCertsInstalled(&hasUserCerts);
+ if (NS_FAILED(rv) || hasUserCerts) {
+ return false;
+ }
+
+ // No smart cards and no client certificates means
+ // we can enable speculative connect.
+ return true;
+}
+
+void nsHttpHandler::MaybeEnableSpeculativeConnect() {
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread only");
+
+ // We don't need to and can't check this in the child and socket process.
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ net_EnsurePSMInit();
+
+ NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction("CanEnableSpeculativeConnect", [] {
+ gHttpHandler->mSpeculativeConnectEnabled =
+ CanEnableSpeculativeConnect();
+ }));
+}
+
+nsresult nsHttpHandler::SpeculativeConnectInternal(
+ nsIURI* aURI, nsIPrincipal* aPrincipal, nsIInterfaceRequestor* aCallbacks,
+ bool anonymous) {
+ if (IsNeckoChild()) {
+ gNeckoChild->SendSpeculativeConnect(aURI, aPrincipal, anonymous);
+ return NS_OK;
+ }
+
+ if (!mHandlerActive) return NS_OK;
+
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (mDebugObservations && obsService) {
+ // this is basically used for test coverage of an otherwise 'hintable'
+ // feature
+ obsService->NotifyObservers(nullptr, "speculative-connect-request",
+ nullptr);
+ for (auto* cp :
+ dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
+ PNeckoParent* neckoParent =
+ SingleManagedOrNull(cp->ManagedPNeckoParent());
+ if (!neckoParent) {
+ continue;
+ }
+ Unused << neckoParent->SendSpeculativeConnectRequest();
+ }
+ }
+
+ nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+ bool isStsHost = false;
+ if (!sss) return NS_OK;
+
+ nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(aCallbacks);
+ uint32_t flags = 0;
+ if (loadContext && loadContext->UsePrivateBrowsing())
+ flags |= nsISocketProvider::NO_PERMANENT_STORAGE;
+
+ OriginAttributes originAttributes;
+ // If the principal is given, we use the originAttributes from this
+ // principal. Otherwise, we use the originAttributes from the loadContext.
+ if (aPrincipal) {
+ originAttributes = aPrincipal->OriginAttributesRef();
+ } else if (loadContext) {
+ loadContext->GetOriginAttributes(originAttributes);
+ }
+
+ StoragePrincipalHelper::UpdateOriginAttributesForNetworkState(
+ aURI, originAttributes);
+
+ nsCOMPtr<nsIURI> clone;
+ if (NS_SUCCEEDED(sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI,
+ flags, originAttributes, nullptr, nullptr,
+ &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;
+
+ // Construct connection info object
+ if (aURI->SchemeIs("https") && !mSpeculativeConnectEnabled) {
+ 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);
+
+ // TODO For now pass ""_ns for topWindowOrigin for all speculative
+ // connection attempts, but ideally we should pass the accurate top window
+ // origin here. This would require updating the nsISpeculativeConnect API
+ // and all of its consumers.
+ RefPtr<nsHttpConnectionInfo> ci =
+ new nsHttpConnectionInfo(host, port, ""_ns, username, ""_ns, nullptr,
+ originAttributes, aURI->SchemeIs("https"));
+ ci->SetAnonymous(anonymous);
+
+ return SpeculativeConnect(ci, aCallbacks);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::SpeculativeConnect(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ nsIInterfaceRequestor* aCallbacks) {
+ return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, false);
+}
+
+NS_IMETHODIMP
+nsHttpHandler::SpeculativeAnonymousConnect(nsIURI* aURI,
+ nsIPrincipal* aPrincipal,
+ nsIInterfaceRequestor* aCallbacks) {
+ return SpeculativeConnectInternal(aURI, aPrincipal, aCallbacks, true);
+}
+
+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::GetDefaultPort(int32_t* aPort) {
+ *aPort = NS_HTTPS_DEFAULT_PORT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsHttpsHandler::GetProtocolFlags(uint32_t* aProtocolFlags) {
+ *aProtocolFlags = NS_HTTP_PROTOCOL_FLAGS | URI_IS_POTENTIALLY_TRUSTWORTHY;
+ 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, 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);
+}
+
+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 const 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::ExcludeHttp2(const nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mConnMgr->ExcludeHttp2(ci);
+ if (!mExcludedHttp2Origins.Contains(ci->GetOrigin())) {
+ MutexAutoLock lock(mHttpExclusionLock);
+ mExcludedHttp2Origins.PutEntry(ci->GetOrigin());
+ }
+}
+
+bool nsHttpHandler::IsHttp2Excluded(const nsHttpConnectionInfo* ci) {
+ MutexAutoLock lock(mHttpExclusionLock);
+ return mExcludedHttp2Origins.Contains(ci->GetOrigin());
+}
+
+void nsHttpHandler::ExcludeHttp3(const nsHttpConnectionInfo* ci) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ mConnMgr->ExcludeHttp3(ci);
+ if (!mExcludedHttp3Origins.Contains(ci->GetRoutedHost())) {
+ MutexAutoLock lock(mHttpExclusionLock);
+ mExcludedHttp3Origins.PutEntry(ci->GetRoutedHost());
+ }
+}
+
+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::IsHttp3VersionSupported(const nsACString& version) {
+ for (uint32_t i = 0; i < kHttp3VersionCount; i++) {
+ if (version.Equals(kHttp3Versions[i])) {
+ 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, uint32_t 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.Put(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());
+ mFastOpenSupported = aArgs.mFastOpenSupported();
+ 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 nsCString& aModelId) {
+ MOZ_ASSERT(XRE_IsSocketProcess());
+ mDeviceModelId = aModelId;
+}
+
+void nsHttpHandler::MaybeAddAltSvcForTesting(
+ nsIURI* aUri, const nsACString& aUsername,
+ const nsACString& aTopWindowOrigin, bool aPrivateBrowsing, bool aIsolated,
+ nsIInterfaceRequestor* aCallbacks,
+ const OriginAttributes& aOriginAttributes) {
+ if (!IsHttp3Enabled() || mAltSvcMappingTemptativeMap.IsEmpty()) {
+ return;
+ }
+
+ bool isHttps = false;
+ if (NS_FAILED(aUri->SchemeIs("https", &isHttps)) || !isHttps) {
+ // Only set forr 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, aTopWindowOrigin,
+ aPrivateBrowsing, aIsolated, aCallbacks,
+ nullptr, 0, aOriginAttributes, true);
+ }
+}
+
+bool nsHttpHandler::UseHTTPSRRAsAltSvcEnabled() const {
+ return StaticPrefs::network_dns_use_https_rr_as_altsvc();
+}
+
+bool nsHttpHandler::EchConfigEnabled() const {
+ return StaticPrefs::network_dns_echconfig_enabled();
+}
+
+bool nsHttpHandler::FallbackToOriginIfConfigsAreECHAndAllFailed() const {
+ return StaticPrefs::
+ network_dns_echconfig_fallback_to_origin_when_all_failed();
+}
+
+bool nsHttpHandler::UseHTTPSRRForSpeculativeConnection() const {
+ return StaticPrefs::network_dns_use_https_rr_for_speculative_connection();
+}
+
+} // namespace mozilla::net
diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h
new file mode 100644
index 0000000000..30a2818e46
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -0,0 +1,939 @@
+/* -*- 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 "nsDataHashtable.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"
+
+class nsIHttpUpgradeListener;
+class nsIPrefBranch;
+class nsICancelable;
+class nsICookieService;
+class nsIIOService;
+class nsIRequestContextService;
+class nsISiteSecurityService;
+class nsIStreamConverterService;
+
+namespace mozilla {
+namespace net {
+
+bool OnSocketThread();
+
+class ATokenBucketEvent;
+class EventTokenBucket;
+class Tickler;
+class nsHttpConnection;
+class nsHttpConnectionInfo;
+class HttpBaseChannel;
+class HttpHandlerInitArgs;
+class HttpTransactionShell;
+class AltSvcMapping;
+class TRR;
+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);
+ [[nodiscard]] nsresult AddConnectionHeader(nsHttpRequestHead*,
+ uint32_t capabilities);
+ bool IsAcceptableEncoding(const char* encoding, bool isSecure);
+
+ const nsCString& UserAgent();
+
+ 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;
+ }
+
+ bool IsSpdyEnabled() { return mEnableSpdy; }
+ bool IsHttp2Enabled() { return mHttp2Enabled; }
+ bool EnforceHttp2TlsProfile() { return mEnforceHttp2TlsProfile; }
+ bool CoalesceSpdy() { return mCoalesceSpdy; }
+ bool UseSpdyPersistentSettings() { return mSpdyPersistentSettings; }
+ 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 AllowPush() { return mAllowPush; }
+ 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;
+ }
+
+ bool UseH2Deps() { return mUseH2Deps; }
+ bool IsH2WebsocketsEnabled() { return mEnableH2Websockets; }
+
+ 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;
+ }
+
+ bool UseFastOpen() {
+ return mUseFastOpen && mFastOpenSupported &&
+ (mFastOpenStallsCounter < mFastOpenStallsLimit) &&
+ (mFastOpenConsecutiveFailureCounter <
+ mFastOpenConsecutiveFailureLimit);
+ }
+ // If one of tcp connections return PR_NOT_TCP_SOCKET_ERROR while trying
+ // fast open, it means that Fast Open is turned off so we will not try again
+ // until a restart. This is only on Linux.
+ void SetFastOpenNotSupported() { mFastOpenSupported = false; }
+
+ void IncrementFastOpenConsecutiveFailureCounter();
+
+ void ResetFastOpenConsecutiveFailureCounter() {
+ mFastOpenConsecutiveFailureCounter = 0;
+ }
+
+ void IncrementFastOpenStallsCounter();
+ uint32_t CheckIfConnectionIsStalledOnlyIfIdleForThisAmountOfSeconds() {
+ return mFastOpenStallsIdleTime;
+ }
+ uint32_t FastOpenStallsTimeout() { return mFastOpenStallsTimeout; }
+
+ // 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,
+ uint32_t 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);
+ }
+
+ [[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,
+ bool isolated, const nsACString& topWindowOrigin,
+ const OriginAttributes& originAttributes, bool aHttp2Allowed,
+ bool aHttp3Allowed) {
+ return mAltSvcCache->GetAltServiceMapping(scheme, host, port, pb, isolated,
+ topWindowOrigin, originAttributes,
+ aHttp2Allowed, aHttp3Allowed);
+ }
+
+ //
+ // The HTTP handler caches pointers to specific XPCOM services, and
+ // provides the following helper routines for accessing those services:
+ //
+ [[nodiscard]] nsresult GetStreamConverterService(nsIStreamConverterService**);
+ [[nodiscard]] nsresult GetIOService(nsIIOService** service);
+ 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);
+ }
+
+ 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; }
+
+ bool Bug1563538() const { return mBug1563538; }
+
+ bool IsHttp3VersionSupported(const nsACString& version);
+
+ bool IsHttp3Enabled() const { return mHttp3Enabled; }
+ 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 const GetLastActiveTabLoadOptimizationHit();
+ void SetLastActiveTabLoadOptimizationHit(TimeStamp const& when);
+ bool IsBeforeLastActiveTabLoadOptimization(TimeStamp const& when);
+
+ bool DumpHpackTables() { return mDumpHpackTables; }
+
+ HttpTrafficAnalyzer* GetHttpTrafficAnalyzer();
+
+ bool GetThroughCaptivePortal() { return mThroughCaptivePortal; }
+
+ nsresult CompleteUpgrade(HttpTransactionShell* aTrans,
+ nsIHttpUpgradeListener* aUpgradeListener);
+
+ nsresult DoShiftReloadConnectionCleanupWithConnInfo(
+ nsHttpConnectionInfo* aCI);
+
+ void MaybeAddAltSvcForTesting(nsIURI* aUri, const nsACString& aUsername,
+ const nsACString& aTopWindowOrigin,
+ bool aPrivateBrowsing, bool aIsolated,
+ nsIInterfaceRequestor* aCallbacks,
+ const OriginAttributes& aOriginAttributes);
+
+ bool UseHTTPSRRAsAltSvcEnabled() const;
+
+ bool EchConfigEnabled() 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;
+
+ bool UseHTTPSRRForSpeculativeConnection() const;
+
+ 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);
+
+ void SetFastOpenOSSupport();
+
+ friend class SocketProcessChild;
+ void SetHttpHandlerInitArgs(const HttpHandlerInitArgs& aArgs);
+ void SetDeviceModelId(const nsCString& aModelId);
+
+ // Checks if there are any user certs or active smart cards on a different
+ // thread. Updates mSpeculativeConnectEnabled when done.
+ void MaybeEnableSpeculativeConnect();
+
+ // We only allow TRR and TRRServiceChannel itself to create TRRServiceChannel.
+ friend class TRRServiceChannel;
+ friend class TRR;
+ 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<nsIStreamConverterService> mStreamConvSvc;
+ 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;
+ enum HttpVersion mProxyHttpVersion;
+ uint32_t mCapabilities;
+
+ bool mFastFallbackToIPv4;
+ PRIntervalTime mIdleTimeout;
+ PRIntervalTime mSpdyTimeout;
+ PRIntervalTime mResponseTimeout;
+ Atomic<bool, Relaxed> mResponseTimeoutEnabled;
+ uint32_t mNetworkChangedTimeout; // milliseconds
+ uint16_t mMaxRequestAttempts;
+ uint16_t mMaxRequestDelay;
+ uint16_t mIdleSynTimeout;
+ uint16_t mFallbackSynTimeout; // seconds
+
+ bool mH2MandatorySuiteEnabled;
+ uint16_t mMaxUrgentExcessiveConns;
+ uint16_t mMaxConnections;
+ uint8_t mMaxPersistentConnectionsPerServer;
+ uint8_t mMaxPersistentConnectionsPerProxy;
+
+ bool mThrottleEnabled;
+ uint32_t mThrottleVersion;
+ uint32_t mThrottleSuspendFor;
+ uint32_t mThrottleResumeFor;
+ uint32_t mThrottleReadLimit;
+ uint32_t mThrottleReadInterval;
+ uint32_t mThrottleHoldTime;
+ uint32_t mThrottleMaxTime;
+
+ int32_t mSendWindowSize;
+
+ bool mUrgentStartEnabled;
+ bool mTailBlockingEnabled;
+ uint32_t mTailDelayQuantum;
+ uint32_t mTailDelayQuantumAfterDCL;
+ uint32_t mTailDelayMax;
+ uint32_t mTailTotalMax;
+
+ uint8_t mRedirectionLimit;
+
+ bool mBeConservativeForProxy;
+
+ // 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;
+
+ uint8_t mQoSBits;
+
+ bool mEnforceAssocReq;
+
+ nsCString mImageAcceptHeader;
+ nsCString mDocumentAcceptHeader;
+
+ nsCString mAcceptLanguages;
+ nsCString mHttpAcceptEncodings;
+ nsCString mHttpsAcceptEncodings;
+
+ nsCString mDefaultSocketType;
+
+ // cache support
+ uint32_t mLastUniqueID;
+ uint32_t mSessionStartTime;
+
+ // useragent components
+ nsCString mLegacyAppName;
+ nsCString mLegacyAppVersion;
+ nsCString mPlatform;
+ nsCString mOscpu;
+ nsCString mMisc;
+ nsCString mProduct;
+ nsCString mProductSub;
+ nsCString mAppName;
+ nsCString mAppVersion;
+ nsCString mCompatFirefox;
+ bool mCompatFirefoxEnabled;
+ nsCString mCompatDevice;
+ nsCString mDeviceModelId;
+
+ nsCString mUserAgent;
+ nsCString mSpoofedUserAgent;
+ nsCString mUserAgentOverride;
+ bool mUserAgentIsDirty; // true if mUserAgent should be rebuilt
+ bool mAcceptLanguagesIsDirty;
+
+ bool mPromptTempRedirect;
+
+ // Persistent HTTPS caching flag
+ bool mEnablePersistentHttpsCaching;
+
+ // for broadcasting safe hint;
+ bool mSafeHintEnabled;
+ bool mParentalControlEnabled;
+
+ // true in between init and shutdown states
+ Atomic<bool, Relaxed> mHandlerActive;
+
+ // The value of 'hidden' network.http.debug-observations : 1;
+ uint32_t mDebugObservations : 1;
+
+ uint32_t mEnableSpdy : 1;
+ uint32_t mHttp2Enabled : 1;
+ uint32_t mUseH2Deps : 1;
+ uint32_t mEnforceHttp2TlsProfile : 1;
+ uint32_t mCoalesceSpdy : 1;
+ uint32_t mSpdyPersistentSettings : 1;
+ uint32_t mAllowPush : 1;
+ uint32_t mEnableAltSvc : 1;
+ uint32_t mEnableAltSvcOE : 1;
+ uint32_t mEnableOriginExtension : 1;
+ uint32_t mEnableH2Websockets : 1;
+ uint32_t mDumpHpackTables : 1;
+
+ // Try to use SPDY features instead of HTTP/1.1 over SSL
+ SpdyInformation mSpdyInfo;
+
+ uint32_t mSpdySendingChunkSize;
+ uint32_t mSpdySendBufferSize;
+ uint32_t mSpdyPushAllowance;
+ uint32_t mSpdyPullAllowance;
+ uint32_t mDefaultSpdyConcurrent;
+ PRIntervalTime mSpdyPingThreshold;
+ PRIntervalTime mSpdyPingTimeout;
+
+ // The maximum amount of time to wait for socket transport to be
+ // established. In milliseconds.
+ uint32_t mConnectTimeout;
+
+ // The maximum amount of time to wait for a tls handshake to be
+ // established. In milliseconds.
+ uint32_t mTLSHandshakeTimeout;
+
+ // The maximum number of current global half open sockets allowable
+ // when starting a new speculative connection.
+ uint32_t mParallelSpeculativeConnectLimit;
+
+ // For Rate Pacing of HTTP/1 requests through a netwerk/base/EventTokenBucket
+ // Active requests <= *MinParallelism are not subject to the rate pacing
+ bool mRequestTokenBucketEnabled;
+ uint16_t mRequestTokenBucketMinParallelism;
+ uint32_t mRequestTokenBucketHz; // EventTokenBucket HZ
+ uint32_t mRequestTokenBucketBurst; // EventTokenBucket Burst
+
+ // Whether or not to block requests for non head js/css items (e.g. media)
+ // while those elements load.
+ bool mCriticalRequestPrioritization;
+
+ // TCP Keepalive configuration values.
+
+ // True if TCP keepalive is enabled for short-lived conns.
+ bool mTCPKeepaliveShortLivedEnabled;
+ // Time (secs) indicating how long a conn is considered short-lived.
+ int32_t mTCPKeepaliveShortLivedTimeS;
+ // Time (secs) before first keepalive probe; between successful probes.
+ int32_t mTCPKeepaliveShortLivedIdleTimeS;
+
+ // True if TCP keepalive is enabled for long-lived conns.
+ bool mTCPKeepaliveLongLivedEnabled;
+ // Time (secs) before first keepalive probe; between successful probes.
+ int32_t mTCPKeepaliveLongLivedIdleTimeS;
+
+ // if true, generate NS_ERROR_PARTIAL_TRANSFER for h1 responses with
+ // incorrect content lengths or malformed chunked encodings
+ FrameCheckLevel mEnforceH1Framing;
+
+ nsCOMPtr<nsIRequestContextService> mRequestContextService;
+
+ // The default size (in bytes) of the HPACK decompressor table.
+ uint32_t mDefaultHpackBuffer;
+
+ // Pref for the whole fix that bug provides
+ Atomic<bool, Relaxed> mBug1563538;
+
+ Atomic<bool, Relaxed> mHttp3Enabled;
+ // Http3 parameters
+ Atomic<uint32_t, Relaxed> mQpackTableSize;
+ Atomic<uint32_t, Relaxed>
+ mHttp3MaxBlockedStreams; // uint16_t is enough here, but Atomic only
+ // supports uint32_t or uint64_t.
+ nsCString mHttp3QlogDir;
+
+ // The max size (in bytes) for received Http response header.
+ uint32_t mMaxHttpResponseHeaderSize;
+
+ // The ratio for dispatching transactions from the focused window.
+ float mFocusedWindowTransactionRatio;
+
+ // We may disable speculative connect if the browser has user certificates
+ // installed as that might randomly popup the certificate choosing window.
+ Atomic<bool, Relaxed> mSpeculativeConnectEnabled;
+
+ Atomic<bool, Relaxed> mUseFastOpen;
+ Atomic<bool, Relaxed> mFastOpenSupported;
+ uint32_t mFastOpenConsecutiveFailureLimit;
+ uint32_t mFastOpenConsecutiveFailureCounter;
+ uint32_t mFastOpenStallsLimit;
+ uint32_t mFastOpenStallsCounter;
+ Atomic<uint32_t, Relaxed> mFastOpenStallsIdleTime;
+ uint32_t mFastOpenStallsTimeout;
+
+ // If true, the transactions from active tab will be dispatched first.
+ bool mActiveTabPriority;
+
+ 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, nsIInterfaceRequestor* aCallbacks,
+ bool anonymous);
+
+ // State for generating channelIds
+ uint32_t mProcessId;
+ Atomic<uint32_t, Relaxed> mNextChannelId;
+
+ // 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;
+ TimeStamp mLastActiveTabLoadOptimizationHit;
+
+ Mutex mHttpExclusionLock;
+
+ 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);
+
+ private:
+ nsTHashtable<nsCStringHashKey> mExcludedHttp2Origins;
+ nsTHashtable<nsCStringHashKey> mExcludedHttp3Origins;
+
+ bool mThroughCaptivePortal;
+
+ // The mapping of channel id and the weak pointer of nsHttpChannel.
+ nsDataHashtable<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;
+};
+
+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_FORWARD_NSISPECULATIVECONNECT(gHttpHandler->)
+
+ 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 net
+} // namespace mozilla
+
+#endif // nsHttpHandler_h__
diff --git a/netwerk/protocol/http/nsHttpHeaderArray.cpp b/netwerk/protocol/http/nsHttpHeaderArray.cpp
new file mode 100644
index 0000000000..18ae69e99e
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHeaderArray.cpp
@@ -0,0 +1,452 @@
+/* -*- 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(PromiseFlatCString(headerName).get());
+ if (!header) {
+ NS_WARNING("failed to resolve atom");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return SetHeader(header, headerName, value, merge, variety);
+}
+
+nsresult nsHttpHeaderArray::SetHeader(
+ nsHttpAtom header, const nsACString& value, bool merge,
+ nsHttpHeaderArray::HeaderVariety variety) {
+ return SetHeader(header, ""_ns, value, merge, variety);
+}
+
+nsresult nsHttpHeaderArray::SetHeader(
+ nsHttpAtom header, const nsACString& headerName, const nsACString& value,
+ bool merge, nsHttpHeaderArray::HeaderVariety variety) {
+ MOZ_ASSERT(
+ (variety == eVarietyResponse) || (variety == eVarietyRequestDefault) ||
+ (variety == eVarietyRequestOverride),
+ "Net original headers can only be set using SetHeader_internal().");
+
+ nsEntry* entry = nullptr;
+ int32_t index;
+
+ index = LookupEntry(header, &entry);
+
+ // If an empty value is passed in, then delete the header entry...
+ // unless we are merging, in which case this function becomes a NOP.
+ if (value.IsEmpty()) {
+ if (!merge && entry) {
+ if (entry->variety == eVarietyResponseNetOriginalAndResponse) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ } else {
+ mHeaders.RemoveElementAt(index);
+ }
+ }
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!entry || variety != eVarietyRequestDefault,
+ "Cannot set default entry which overrides existing entry!");
+ if (!entry) {
+ return SetHeader_internal(header, headerName, value, variety);
+ } else if (merge && !IsSingletonHeader(header)) {
+ return MergeHeader(header, entry, value, variety);
+ } else 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(
+ 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(PromiseFlatCString(headerName).get());
+ 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;
+ } else if (entry) {
+ MOZ_ASSERT(variety == eVarietyResponse);
+ entry->variety = eVarietyResponseNetOriginal;
+ }
+
+ return SetHeader_internal(header, headerName, ""_ns, variety);
+}
+
+nsresult nsHttpHeaderArray::SetHeaderFromNet(
+ 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);
+
+ } else 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;
+ } else if (!IsIgnoreMultipleHeader(header)) {
+ // Multiple instances of non-mergeable header received from network
+ // - ignore if same value
+ if (!entry->value.Equals(value)) {
+ 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(
+ 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 != mHeaders.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 != mHeaders.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(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(nsHttpAtom header) const {
+ const nsEntry* entry = nullptr;
+ LookupEntry(header, &entry);
+ return entry ? entry->value.get() : nullptr;
+}
+
+nsresult nsHttpHeaderArray::GetHeader(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(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;
+ }
+
+ nsAutoCString hdr;
+ if (entry.headerNameOriginal.IsEmpty()) {
+ hdr = nsDependentCString(entry.header);
+ } else {
+ hdr = 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(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;
+ }
+
+ nsAutoCString hdr;
+ if (entry.headerNameOriginal.IsEmpty()) {
+ hdr = nsDependentCString(entry.header);
+ } else {
+ hdr = 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);
+ } 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);
+ } 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..342a02cf80
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpHeaderArray.h
@@ -0,0 +1,289 @@
+/* -*- 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(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,
+ // 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(nsHttpAtom header, const nsACString& value,
+ bool merge, HeaderVariety variety);
+ [[nodiscard]] nsresult SetHeader(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(nsHttpAtom header,
+ const nsACString& headerNameOriginal,
+ const nsACString& value,
+ bool response);
+
+ [[nodiscard]] nsresult SetResponseHeaderFromCache(
+ nsHttpAtom header, const nsACString& headerNameOriginal,
+ const nsACString& value, HeaderVariety variety);
+
+ [[nodiscard]] nsresult GetHeader(nsHttpAtom header, nsACString& value) const;
+ [[nodiscard]] nsresult GetOriginalHeader(nsHttpAtom aHeader,
+ nsIHttpHeaderVisitor* aVisitor);
+ void ClearHeader(nsHttpAtom h);
+
+ // Find the location of the given header value, or null if none exists.
+ const char* FindHeaderValue(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(nsHttpAtom header, const char* value) const {
+ return FindHeaderValue(header, value) != nullptr;
+ }
+
+ bool HasHeader(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 and a pointer to the
+ // header value (the substring of the header line -- do not free).
+ [[nodiscard]] static nsresult ParseHeaderLine(
+ const nsACString& line, nsHttpAtom* header = 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(nsHttpAtom header, const nsEntry**) const;
+ int32_t LookupEntry(nsHttpAtom header, nsEntry**);
+ [[nodiscard]] nsresult MergeHeader(nsHttpAtom header, nsEntry* entry,
+ const nsACString& value,
+ HeaderVariety variety);
+ [[nodiscard]] nsresult SetHeader_internal(nsHttpAtom header,
+ const nsACString& headeName,
+ const nsACString& value,
+ HeaderVariety variety);
+
+ // Header cannot be merged: only one value possible
+ bool IsSingletonHeader(nsHttpAtom header);
+ // Header cannot be merged, and subsequent values should be ignored
+ bool IsIgnoreMultipleHeader(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(nsHttpAtom header);
+
+ // 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(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(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(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 ||
+ // 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(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(
+ nsHttpAtom header, nsEntry* entry, const nsACString& value,
+ nsHttpHeaderArray::HeaderVariety variety) {
+ if (value.IsEmpty()) return NS_OK; // merge of empty header = no-op
+
+ nsCString newValue = entry->value;
+ if (!newValue.IsEmpty()) {
+ // 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(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;
+}
+
+} // 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..0a30de0510
--- /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 "plstr.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 "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/net/HttpAuthUtils.h"
+#include "mozilla/ClearOnShutdown.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;
+ PRNetAddr addr;
+
+ 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('.') &&
+ PR_StringToNetAddr(host.BeginReading(), &addr) != PR_SUCCESS;
+}
+
+// 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;
+ }
+
+ bool dontRememberHistory;
+ if (NS_SUCCEEDED(prefs->GetBoolPref("browser.privatebrowsing.autostart",
+ &dontRememberHistory)) &&
+ !dontRememberHistory) {
+ 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 char* 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 (PL_strcasecmp(challenge, "NTLM") == 0) {
+ nsCOMPtr<nsIAuthModule> module;
+
+ // 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;
+ }
+
+ // 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 char* challenge,
+ bool isProxyAuth, const char16_t* domain, const char16_t* username,
+ const char16_t* password, nsISupports* sessionState,
+ nsISupports* continuationState, nsICancelable** aCancellable) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsHttpNTLMAuth::GenerateCredentials(nsIHttpAuthenticableChannel* authChannel,
+ const char* challenge, bool isProxyAuth,
+ const char16_t* domain,
+ const char16_t* user, const char16_t* pass,
+ nsISupports** sessionState,
+ nsISupports** continuationState,
+ uint32_t* aFlags, char** creds)
+
+{
+ LOG(("nsHttpNTLMAuth::GenerateCredentials\n"));
+
+ *creds = nullptr;
+ *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 || !pass) *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 (PL_strcasecmp(challenge, "NTLM") == 0) {
+ // 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.get(), reqFlags, domain, user, pass);
+ if (NS_FAILED(rv)) return rv;
+
+// 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.
+#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<nsISupports> security;
+ rv = channel->GetSecurityInfo(getter_AddRefs(security));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsITransportSecurityInfo> secInfo = do_QueryInterface(security);
+
+ if (mUseNative && secInfo) {
+ nsCOMPtr<nsIX509Cert> cert;
+ rv = secInfo->GetServerCert(getter_AddRefs(cert));
+ if (NS_FAILED(rv)) return rv;
+
+ 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();
+ } else {
+ // If there is no server certificate, we don't pass anything.
+ inBufLen = 0;
+ inBuf = nullptr;
+ }
+#else // Extended protection update is just for Linux and Windows machines.
+ inBufLen = 0;
+ inBuf = nullptr;
+#endif
+ } else {
+ // decode challenge; skip past "NTLM " to the start of the base64
+ // encoded data.
+ int len = strlen(challenge);
+ if (len < 6) return NS_ERROR_UNEXPECTED; // bogus challenge
+ challenge += 5;
+ len -= 5;
+
+ // strip off any padding (see bug 230351)
+ while (len && challenge[len - 1] == '=') len--;
+
+ // decode into the input secbuffer
+ rv = Base64Decode(challenge, 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 {
+ *creds = (char*)moz_xmalloc(credsLen.value());
+ memcpy(*creds, "NTLM ", 5);
+ PL_Base64Encode((char*)outBuf, outBufLen, *creds + 5);
+ (*creds)[credsLen.value() - 1] = '\0'; // null terminate
+ }
+
+ // 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..cd3906737b
--- /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() : mUseNative(false) {}
+
+ 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;
+
+ 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..0cbed53465
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpRequestHead.cpp
@@ -0,0 +1,360 @@
+/* -*- 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()
+ : mMethod("GET"_ns),
+ mVersion(HttpVersion::v1_1),
+ mParsedMethod(kMethod_Get),
+ mHTTPS(false),
+ mRecursiveMutex("nsHttpRequestHead.mRecursiveMutex"),
+ mInVisitHeaders(false) {
+ MOZ_COUNT_CTOR(nsHttpRequestHead);
+}
+
+nsHttpRequestHead::nsHttpRequestHead(const nsHttpRequestHead& aRequestHead)
+ : mRecursiveMutex("nsHttpRequestHead.mRecursiveMutex"),
+ mInVisitHeaders(false) {
+ 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() { 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(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(
+ 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(nsHttpAtom h, nsACString& v) {
+ v.Truncate();
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mHeaders.GetHeader(h, v);
+}
+
+nsresult nsHttpRequestHead::ClearHeader(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(nsHttpAtom h) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mHeaders.HasHeader(h);
+}
+
+bool nsHttpRequestHead::HasHeaderValue(nsHttpAtom h, const char* v) {
+ RecursiveMutexAutoLock mon(mRecursiveMutex);
+ return mHeaders.HasHeaderValue(h, v);
+}
+
+nsresult nsHttpRequestHead::SetHeaderOnce(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..9dcbda06ed
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpRequestHead.h
@@ -0,0 +1,147 @@
+/* -*- 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& 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;
+ void Enter() { mRecursiveMutex.Lock(); }
+ void Exit() { 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(nsHttpAtom h, const nsACString& v,
+ bool m = false);
+ [[nodiscard]] nsresult SetHeader(nsHttpAtom h, const nsACString& v, bool m,
+ nsHttpHeaderArray::HeaderVariety variety);
+ [[nodiscard]] nsresult SetEmptyHeader(const nsACString& h);
+ [[nodiscard]] nsresult GetHeader(nsHttpAtom h, nsACString& v);
+
+ [[nodiscard]] nsresult ClearHeader(nsHttpAtom h);
+ void ClearHeaders();
+
+ bool HasHeaderValue(nsHttpAtom h, const char* v);
+ // This function returns true if header is set even if it is an empty
+ // header.
+ bool HasHeader(nsHttpAtom h);
+ void Flatten(nsACString&, bool pruneProxyHeaders = false);
+
+ // Don't allow duplicate values
+ [[nodiscard]] nsresult SetHeaderOnce(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;
+ nsCString mMethod;
+ HttpVersion mVersion;
+
+ // mRequestURI and mPath are strings instead of an nsIURI
+ // because this is used off the main thread
+ nsCString mRequestURI;
+ nsCString mPath;
+
+ nsCString mOrigin;
+ ParsedMethodType mParsedMethod;
+ bool mHTTPS;
+
+ // We are using RecursiveMutex instead of a Mutex because VisitHeader
+ // function calls nsIHttpHeaderVisitor::VisitHeader while under lock.
+ RecursiveMutex mRecursiveMutex;
+
+ // During VisitHeader we sould not allow cal to SetHeader.
+ bool mInVisitHeaders;
+
+ 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..3c6c23e80d
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpResponseHead.cpp
@@ -0,0 +1,1266 @@
+/* -*- 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/Unused.h"
+#include "nsHttpResponseHead.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsPrintfCString.h"
+#include "prtime.h"
+#include "plstr.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)
+ : mRecursiveMutex("nsHttpResponseHead.mRecursiveMutex"),
+ mInVisitHeaders(false) {
+ 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(PromiseFlatCString(hdr).get());
+ if (!atom) {
+ NS_WARNING("failed to resolve atom");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return SetHeader_locked(atom, hdr, val, merge);
+}
+
+nsresult nsHttpResponseHead::SetHeader(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(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(nsHttpAtom h, nsACString& v) {
+ v.Truncate();
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mHeaders.GetHeader(h, v);
+}
+
+void nsHttpResponseHead::ClearHeader(nsHttpAtom h) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mHeaders.ClearHeader(h);
+}
+
+void nsHttpResponseHead::ClearHeaders() {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mHeaders.Clear();
+}
+
+bool nsHttpResponseHead::HasHeaderValue(nsHttpAtom h, const char* v) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ return mHeaders.HasHeaderValue(h, v);
+}
+
+bool nsHttpResponseHead::HasHeader(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 ;-)
+
+ char* p = PL_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 = PL_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 = PL_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.
+
+ switch (mStatus) {
+ // start with the most common
+ case 200:
+ mStatusText.AssignLiteral("OK");
+ break;
+ case 404:
+ mStatusText.AssignLiteral("Not Found");
+ break;
+ case 301:
+ mStatusText.AssignLiteral("Moved Permanently");
+ break;
+ case 304:
+ mStatusText.AssignLiteral("Not Modified");
+ break;
+ case 307:
+ mStatusText.AssignLiteral("Temporary Redirect");
+ break;
+ case 500:
+ mStatusText.AssignLiteral("Internal Server Error");
+ break;
+
+ // also well known
+ case 100:
+ mStatusText.AssignLiteral("Continue");
+ break;
+ case 101:
+ mStatusText.AssignLiteral("Switching Protocols");
+ break;
+ case 201:
+ mStatusText.AssignLiteral("Created");
+ break;
+ case 202:
+ mStatusText.AssignLiteral("Accepted");
+ break;
+ case 203:
+ mStatusText.AssignLiteral("Non Authoritative");
+ break;
+ case 204:
+ mStatusText.AssignLiteral("No Content");
+ break;
+ case 205:
+ mStatusText.AssignLiteral("Reset Content");
+ break;
+ case 206:
+ mStatusText.AssignLiteral("Partial Content");
+ break;
+ case 207:
+ mStatusText.AssignLiteral("Multi-Status");
+ break;
+ case 208:
+ mStatusText.AssignLiteral("Already Reported");
+ break;
+ case 300:
+ mStatusText.AssignLiteral("Multiple Choices");
+ break;
+ case 302:
+ mStatusText.AssignLiteral("Found");
+ break;
+ case 303:
+ mStatusText.AssignLiteral("See Other");
+ break;
+ case 305:
+ mStatusText.AssignLiteral("Use Proxy");
+ break;
+ case 308:
+ mStatusText.AssignLiteral("Permanent Redirect");
+ break;
+ case 400:
+ mStatusText.AssignLiteral("Bad Request");
+ break;
+ case 401:
+ mStatusText.AssignLiteral("Unauthorized");
+ break;
+ case 402:
+ mStatusText.AssignLiteral("Payment Required");
+ break;
+ case 403:
+ mStatusText.AssignLiteral("Forbidden");
+ break;
+ case 405:
+ mStatusText.AssignLiteral("Method Not Allowed");
+ break;
+ case 406:
+ mStatusText.AssignLiteral("Not Acceptable");
+ break;
+ case 407:
+ mStatusText.AssignLiteral("Proxy Authentication Required");
+ break;
+ case 408:
+ mStatusText.AssignLiteral("Request Timeout");
+ break;
+ case 409:
+ mStatusText.AssignLiteral("Conflict");
+ break;
+ case 410:
+ mStatusText.AssignLiteral("Gone");
+ break;
+ case 411:
+ mStatusText.AssignLiteral("Length Required");
+ break;
+ case 412:
+ mStatusText.AssignLiteral("Precondition Failed");
+ break;
+ case 413:
+ mStatusText.AssignLiteral("Request Entity Too Large");
+ break;
+ case 414:
+ mStatusText.AssignLiteral("Request URI Too Long");
+ break;
+ case 415:
+ mStatusText.AssignLiteral("Unsupported Media Type");
+ break;
+ case 416:
+ mStatusText.AssignLiteral("Requested Range Not Satisfiable");
+ break;
+ case 417:
+ mStatusText.AssignLiteral("Expectation Failed");
+ break;
+ case 418:
+ mStatusText.AssignLiteral("I'm a teapot");
+ break;
+ case 421:
+ mStatusText.AssignLiteral("Misdirected Request");
+ break;
+ case 422:
+ mStatusText.AssignLiteral("Unprocessable Entity");
+ break;
+ case 423:
+ mStatusText.AssignLiteral("Locked");
+ break;
+ case 424:
+ mStatusText.AssignLiteral("Failed Dependency");
+ break;
+ case 425:
+ mStatusText.AssignLiteral("Too Early");
+ break;
+ case 426:
+ mStatusText.AssignLiteral("Upgrade Required");
+ break;
+ case 428:
+ mStatusText.AssignLiteral("Precondition Required");
+ break;
+ case 429:
+ mStatusText.AssignLiteral("Too Many Requests");
+ break;
+ case 431:
+ mStatusText.AssignLiteral("Request Header Fields Too Large");
+ break;
+ case 451:
+ mStatusText.AssignLiteral("Unavailable For Legal Reasons");
+ break;
+ case 501:
+ mStatusText.AssignLiteral("Not Implemented");
+ break;
+ case 502:
+ mStatusText.AssignLiteral("Bad Gateway");
+ break;
+ case 503:
+ mStatusText.AssignLiteral("Service Unavailable");
+ break;
+ case 504:
+ mStatusText.AssignLiteral("Gateway Timeout");
+ break;
+ case 505:
+ mStatusText.AssignLiteral("HTTP Version Unsupported");
+ break;
+ case 506:
+ mStatusText.AssignLiteral("Variant Also Negotiates");
+ break;
+ case 507:
+ mStatusText.AssignLiteral("Insufficient Storage ");
+ break;
+ case 508:
+ mStatusText.AssignLiteral("Loop Detected");
+ break;
+ case 510:
+ mStatusText.AssignLiteral("Not Extended");
+ break;
+ case 511:
+ mStatusText.AssignLiteral("Network Authentication Required");
+ break;
+ default:
+ mStatusText.AssignLiteral("No Reason Phrase");
+ break;
+ }
+}
+
+void nsHttpResponseHead::ParseStatusLine(const nsACString& line) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ ParseStatusLine_locked(line);
+}
+
+void 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();
+ const char* p = start;
+
+ // HTTP-Version
+ ParseVersion(start);
+
+ int32_t index = line.FindChar(' ');
+
+ if ((mVersion == HttpVersion::v0_9) || (index == -1)) {
+ mStatus = 200;
+ AssignDefaultStatusText();
+ } else {
+ // Status-Code
+ p += 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()));
+}
+
+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;
+ }
+ 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) {
+ int64_t len;
+ const char* ignored;
+ // permit only a single value here.
+ if (nsHttp::ParseInt64(val.get(), &ignored, &len)) {
+ mContentLength = len;
+ } else {
+ // If this is a negative content length then just ignore it
+ LOG(("invalid content-length! %s\n", val.get()));
+ }
+ } else if (hdr == nsHttp::Content_Type) {
+ LOG(("ParseContentType [type=%s]\n", val.get()));
+ bool dummy;
+ net_ParseContentType(val, mContentType, mContentCharset, &dummy);
+ } 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;
+ }
+
+ if (now > stallValidUntil.value()) {
+ return false;
+ }
+
+ return true;
+}
+
+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;
+ const char* val =
+ aOther->mHeaders.PeekHeaderAt(i, header, headerNameOriginal);
+
+ if (!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));
+ } else {
+ LOG(("new response header [%s: %s]\n", header.get(), val));
+
+ // overwrite the current header value with the new value...
+ DebugOnly<nsresult> rv =
+ SetHeader_locked(header, headerNameOriginal, nsDependentCString(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(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 {
+ 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 (PL_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::VisitHeaders(
+ nsIHttpHeaderVisitor* visitor, nsHttpHeaderArray::VisitorFilter filter) {
+ RecursiveMutexAutoLock monitor(mRecursiveMutex);
+ mInVisitHeaders = true;
+ nsresult rv = mHeaders.VisitHeaders(visitor, filter);
+ mInVisitHeaders = false;
+ return rv;
+}
+
+nsresult nsHttpResponseHead::GetOriginalHeader(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;
+ Unused << GetHeader(nsHttp::X_Content_Type_Options, 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..4126acd133
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpResponseHead.h
@@ -0,0 +1,235 @@
+/* -*- 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()
+ : 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),
+ mRecursiveMutex("nsHttpResponseHead.mRecursiveMutex"),
+ mInVisitHeaders(false) {}
+
+ nsHttpResponseHead(const nsHttpResponseHead& aOther);
+ nsHttpResponseHead& operator=(const nsHttpResponseHead& aOther);
+
+ void Enter() { mRecursiveMutex.Lock(); }
+ void Exit() { mRecursiveMutex.Unlock(); }
+
+ 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(nsHttpAtom h, const nsACString& v,
+ bool m = false);
+ [[nodiscard]] nsresult GetHeader(nsHttpAtom h, nsACString& v);
+ void ClearHeader(nsHttpAtom h);
+ void ClearHeaders();
+ bool HasHeaderValue(nsHttpAtom h, const char* v);
+ bool HasHeader(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.
+ void 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* headers);
+
+ // reset the response head to it's initial state
+ void Reset();
+
+ [[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);
+ [[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(nsHttpAtom aHeader,
+ nsIHttpHeaderVisitor* aVisitor);
+
+ bool HasContentType() const;
+ bool HasContentCharset();
+ bool GetContentTypeOptionsHeader(nsACString& aOutput);
+
+ private:
+ [[nodiscard]] nsresult SetHeader_locked(nsHttpAtom atom, const nsACString& h,
+ const nsACString& v, bool m = false);
+ void AssignDefaultStatusText();
+ void ParseVersion(const char*);
+ void ParseCacheControl(const char*);
+ void ParsePragma(const char*);
+
+ void ParseStatusLine_locked(const nsACString& line);
+ [[nodiscard]] nsresult ParseHeaderLine_locked(const nsACString& line,
+ bool originalFromNetHeaders);
+
+ // these return failure if the header does not exist.
+ [[nodiscard]] nsresult ParseDateHeader(nsHttpAtom header,
+ uint32_t* result) const;
+
+ bool ExpiresInPast_locked() const;
+ [[nodiscard]] nsresult GetAgeValue_locked(uint32_t* result) const;
+ [[nodiscard]] nsresult GetExpiresValue_locked(uint32_t* result) const;
+ [[nodiscard]] nsresult GetMaxAgeValue_locked(uint32_t* result) const;
+ [[nodiscard]] nsresult GetStaleWhileRevalidateValue_locked(
+ uint32_t* result) const;
+
+ [[nodiscard]] nsresult GetDateValue_locked(uint32_t* result) const {
+ return ParseDateHeader(nsHttp::Date, result);
+ }
+
+ [[nodiscard]] nsresult GetLastModifiedValue_locked(uint32_t* result) const {
+ return ParseDateHeader(nsHttp::Last_Modified, result);
+ }
+
+ bool NoCache_locked() const {
+ // 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;
+ HttpVersion mVersion;
+ uint16_t mStatus;
+ nsCString mStatusText;
+ int64_t mContentLength;
+ nsCString mContentType;
+ nsCString mContentCharset;
+ bool mHasCacheControl;
+ bool mCacheControlPublic;
+ bool mCacheControlPrivate;
+ bool mCacheControlNoStore;
+ bool mCacheControlNoCache;
+ bool mCacheControlImmutable;
+ bool mCacheControlStaleWhileRevalidateSet;
+ uint32_t mCacheControlStaleWhileRevalidate;
+ bool mCacheControlMaxAgeSet;
+ uint32_t mCacheControlMaxAge;
+ bool mPragmaNoCache;
+
+ // We are using RecursiveMutex instead of a Mutex because VisitHeader
+ // function calls nsIHttpHeaderVisitor::VisitHeader while under lock.
+ mutable RecursiveMutex mRecursiveMutex;
+ // During VisitHeader we sould not allow cal to SetHeader.
+ bool mInVisitHeaders;
+
+ 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..8499af172d
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -0,0 +1,3347 @@
+/* -*- 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 "TCPFastOpenLayer.h"
+#include "TunnelUtils.h"
+#include "base/basictypes.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "nsCRT.h"
+#include "nsComponentManagerUtils.h" // do_CreateInstance
+#include "nsHttpBasicAuth.h"
+#include "nsHttpChunkedDecoder.h"
+#include "nsHttpDigestAuth.h"
+#include "nsHttpHandler.h"
+#include "nsHttpNTLMAuth.h"
+#include "nsHttpNegotiateAuth.h"
+#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 "nsISSLSocketControl.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"
+
+//-----------------------------------------------------------------------------
+
+static NS_DEFINE_CID(kMultiplexInputStream, NS_MULTIPLEXINPUTSTREAM_CID);
+
+// 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 {
+namespace net {
+
+//-----------------------------------------------------------------------------
+// nsHttpTransaction <public>
+//-----------------------------------------------------------------------------
+
+nsHttpTransaction::nsHttpTransaction()
+ : mLock("transaction lock"),
+ mChannelId(0),
+ mRequestSize(0),
+ mRequestHead(nullptr),
+ mResponseHead(nullptr),
+ mReader(nullptr),
+ mWriter(nullptr),
+ mContentLength(-1),
+ mContentRead(0),
+ mTransferSize(0),
+ mInvalidResponseBytesRead(0),
+ mPushedStream(nullptr),
+ mInitialRwin(0),
+ mChunkedDecoder(nullptr),
+ mStatus(NS_OK),
+ mPriority(0),
+ mRestartCount(0),
+ mCaps(0),
+ mHttpVersion(HttpVersion::UNKNOWN),
+ mHttpResponseCode(0),
+ mCurrentHttpResponseHeaderSize(0),
+ mThrottlingReadAllowance(THROTTLE_NO_LIMIT),
+ mCapsToClear(0),
+ mResponseIsComplete(false),
+ mClosed(false),
+ mReadingStopped(false),
+ mConnected(false),
+ mActivated(false),
+ mHaveStatusLine(false),
+ mHaveAllHeaders(false),
+ mTransactionDone(false),
+ mDidContentStart(false),
+ mNoContent(false),
+ mSentData(false),
+ mReceivedData(false),
+ mStatusEventPending(false),
+ mHasRequestBody(false),
+ mProxyConnectFailed(false),
+ mHttpResponseMatched(false),
+ mPreserveStream(false),
+ mDispatchedAsBlocking(false),
+ mResponseTimeoutEnabled(true),
+ mForceRestart(false),
+ mReuseOnRestart(false),
+ mContentDecoding(false),
+ mContentDecodingCheck(false),
+ mDeferredSendProgress(false),
+ mWaitingOnPipeOut(false),
+ mDoNotRemoveAltSvc(false),
+ mReportedStart(false),
+ mReportedResponseHeader(false),
+ mResponseHeadTaken(false),
+ mForTakeResponseTrailers(nullptr),
+ mResponseTrailersTaken(false),
+ mRestarted(false),
+ mTopLevelOuterContentWindowId(0),
+ mSubmittedRatePacing(false),
+ mPassedRatePacing(false),
+ mSynchronousRatePaceRequest(false),
+ mClassOfService(0),
+ mResolvedByTRR(false),
+ mEchConfigUsed(false),
+ m0RTTInProgress(false),
+ mDoNotTryEarlyData(false),
+ mEarlyDataDisposition(EARLY_NONE),
+ mFastOpenStatus(TFO_NOT_TRIED),
+ mTrafficCategory(HttpTrafficCategory::eInvalid),
+ mProxyConnectResponseCode(0),
+ mHTTPSSVCReceivedStage(HTTPSSVC_NOT_USED),
+ m421Received(false),
+ mDontRetryWithDirectRoute(false),
+ mFastFallbackTriggered(false),
+ mAllRecordsInH3ExcludedListBefore(false),
+ mHttp3BackupTimerCreated(false) {
+ this->mSelfAddr.inet = {};
+ this->mPeerAddr.inet = {};
+ 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 (mClassOfService &
+ (nsIClassOfService::Throttleable | nsIClassOfService::DontThrottle |
+ nsIClassOfService::Leader | nsIClassOfService::Unblocked)) ==
+ nsIClassOfService::Throttleable;
+}
+
+void nsHttpTransaction::SetClassOfService(uint32_t cos) {
+ if (mClosed) {
+ return;
+ }
+
+ bool wasThrottling = EligibleForThrottling();
+ mClassOfService = cos;
+ 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 ReleaseH2WSTrans final : public Runnable {
+ public:
+ explicit ReleaseH2WSTrans(RefPtr<SpdyConnectTransaction>&& trans)
+ : Runnable("ReleaseH2WSTrans"), mTrans(std::move(trans)) {}
+
+ NS_IMETHOD Run() override {
+ mTrans = nullptr;
+ 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:
+ RefPtr<SpdyConnectTransaction> mTrans;
+};
+
+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
+ mCallbacks = nullptr;
+ mConnection = nullptr;
+
+ delete mResponseHead;
+ delete mChunkedDecoder;
+ ReleaseBlockingTransaction();
+
+ if (mH2WSTransaction) {
+ RefPtr<ReleaseH2WSTrans> r =
+ new ReleaseH2WSTrans(std::move(mH2WSTransaction));
+ 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 topLevelOuterContentWindowId, HttpTrafficCategory trafficCategory,
+ nsIRequestContext* requestContext, uint32_t 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);
+ mTopLevelOuterContentWindowId = topLevelOuterContentWindowId;
+ LOG((" window-id = %" PRIx64, mTopLevelOuterContentWindowId));
+
+ mTrafficCategory = trafficCategory;
+
+ mActivityDistributor = services::GetHttpActivityDistributor();
+ if (!mActivityDistributor) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ bool activityDistributorActive;
+ rv = mActivityDistributor->GetIsActive(&activityDistributorActive);
+ if (NS_SUCCEEDED(rv) && activityDistributorActive) {
+ // there are some observers registered at activity distributor, gather
+ // nsISupports for the channel that called Init()
+ LOG(
+ ("nsHttpTransaction::Init() "
+ "mActivityDistributor is active "
+ "this=%p",
+ this));
+ } else {
+ // there is no observer, so don't use it
+ activityDistributorActive = false;
+ mActivityDistributor = nullptr;
+ }
+
+ 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;
+
+ 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 (mActivityDistributor) {
+ RefPtr<nsHttpTransaction> self = this;
+ nsCString requestBuf(mReqHeaderBuf);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("ObserveActivityWithArgs", [self, requestBuf]() {
+ nsresult rv = self->mActivityDistributor->ObserveActivityWithArgs(
+ HttpActivityArgs(self->mChannelId),
+ NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER, PR_Now(), 0, requestBuf);
+ if (NS_FAILED(rv)) {
+ LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ }));
+ }
+
+ // 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(
+ nullptr, 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
+ rv = NS_NewPipe2(getter_AddRefs(mPipeIn), getter_AddRefs(mPipeOut), true,
+ true, nsIOService::gDefaultSegmentSize,
+ nsIOService::gDefaultSegmentCount);
+ if (NS_FAILED(rv)) return rv;
+
+ if (transWithPushedStream && aPushedStreamId) {
+ RefPtr<nsHttpTransaction> trans =
+ transWithPushedStream->AsHttpTransaction();
+ MOZ_ASSERT(trans);
+ mPushedStream = trans->TakePushedStreamById(aPushedStreamId);
+ }
+
+ if (gHttpHandler->UseHTTPSRRAsAltSvcEnabled()) {
+ mHTTPSSVCReceivedStage = HTTPSSVC_NOT_PRESENT;
+
+ nsCOMPtr<nsIEventTarget> target;
+ Unused << gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
+ if (target) {
+ mResolver = new HTTPSRecordResolver(this);
+ nsCOMPtr<nsICancelable> dnsRequest;
+ rv = mResolver->FetchHTTPSRRInternal(target, getter_AddRefs(dnsRequest));
+ if (NS_FAILED(rv) && (mCaps & NS_HTTP_WAIT_HTTPSSVC_RESULT)) {
+ return rv;
+ }
+
+ {
+ MutexAutoLock lock(mLock);
+ mDNSRequest.swap(dnsRequest);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+static inline void CreateAndStartTimer(nsCOMPtr<nsITimer>& aTimer,
+ nsITimerCallback* aCallback,
+ uint32_t aTimeout) {
+ MOZ_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() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mConnInfo->IsHttp3() && !mResolver) {
+ // 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;
+ }
+
+ if (mH2WSTransaction) {
+ // Need to let the websocket transaction/connection know we've reached
+ // this point so it can stop forwarding information through us and
+ // instead communicate directly with the websocket channel.
+ mH2WSTransaction->SetConnRefTaken();
+ mH2WSTransaction = nullptr;
+ }
+}
+
+UniquePtr<nsHttpResponseHead> nsHttpTransaction::TakeResponseHead() {
+ MOZ_ASSERT(!mResponseHeadTaken, "TakeResponseHead called 2x");
+
+ // Lock TakeResponseHead() against main thread
+ MutexAutoLock lock(*nsHttp::GetLock());
+
+ 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(*nsHttp::GetLock());
+
+ 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) {
+ nsISocketTransport* socketTransport =
+ mConnection ? mConnection->Transport() : nullptr;
+ if (socketTransport) {
+ MutexAutoLock lock(mLock);
+ socketTransport->GetSelfAddr(&mSelfAddr);
+ socketTransport->GetPeerAddr(&mPeerAddr);
+ socketTransport->ResolvedByTRR(&mResolvedByTRR);
+ socketTransport->GetEchConfigUsed(&mEchConfigUsed);
+ }
+ }
+
+ // 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;
+ // After a socket is connected we know for sure whether data
+ // has been sent on SYN packet and if not we should update TLS
+ // start timing.
+ if ((mFastOpenStatus != TFO_DATA_SENT) &&
+ !mTimings.secureConnectionStart.IsNull()) {
+ mTimings.secureConnectionStart = 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.
+ if (mActivityDistributor) {
+ // upon STATUS_WAITING_FOR; report request body sent
+ if ((mHasRequestBody) && (status == NS_NET_STATUS_WAITING_FOR)) {
+ nsresult rv = mActivityDistributor->ObserveActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT, PR_Now(), 0, ""_ns);
+ if (NS_FAILED(rv)) {
+ LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ }
+
+ // report the status and progress
+ nsresult rv = mActivityDistributor->ObserveActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
+ static_cast<uint32_t>(status), PR_Now(), progress, ""_ns);
+ if (NS_FAILED(rv)) {
+ LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ }
+
+ // 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<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
+ if (!seekable) {
+ LOG1(
+ ("nsHttpTransaction::OnTransportStatus %p "
+ "SENDING_TO without seekable request stream\n",
+ this));
+ progress = 0;
+ } else {
+ int64_t prog = 0;
+ seekable->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)) 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;
+ nsCOMPtr<nsISupports> info;
+ mConnection->GetSecurityInfo(getter_AddRefs(info));
+ MutexAutoLock lock(mLock);
+ mSecurityInfo = std::move(info);
+ }
+
+ mDeferredSendProgress = false;
+ mReader = reader;
+ nsresult rv =
+ mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead);
+ mReader = nullptr;
+
+ if (m0RTTInProgress && (mEarlyDataDisposition == EARLY_NONE) &&
+ NS_SUCCEEDED(rv) && (*countRead > 0)) {
+ mEarlyDataDisposition = EARLY_SENT;
+ }
+
+ if (mDeferredSendProgress && mConnection && mConnection->Transport()) {
+ // 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)) 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 (mClassOfService & 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 (!(mClassOfService & 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()->CurrentTopLevelOuterContentWindowId() !=
+ mTopLevelOuterContentWindowId) {
+ 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<nsISupports> 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;
+ }
+
+ auto entry = mIDToStreamMap.LookupForAdd(stream->StreamID());
+ MOZ_ASSERT(!entry);
+ if (!entry) {
+ entry.OrInsert([&stream]() { return 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, 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;
+
+ nsTArray<RefPtr<nsISVCBRecord>> records;
+ Unused << mHTTPSSVCRecord->GetAllRecordsWithEchConfig(
+ mCaps & NS_HTTP_DISALLOW_SPDY, noHttp3, &aAllRecordsHaveEchConfig,
+ &mAllRecordsInH3ExcludedListBefore, 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);
+ if (name == aFailedDomainName) {
+ // Skip the failed one.
+ 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) {
+ if (aEchConfigUsed) {
+ LOG(
+ ("nsHttpTransaction::PrepareFastFallbackConnInfo [this=%p] no record "
+ "can be used",
+ this));
+ return nullptr;
+ }
+ fallbackConnInfo = mOrigConnInfo;
+ }
+
+ 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->GetEchConfig().IsEmpty();
+
+ if (mFastFallbackTriggered) {
+ mFastFallbackTriggered = false;
+ mConnInfo = PrepareFastFallbackConnInfo(echConfigUsed);
+ return;
+ }
+
+ if (!echConfigUsed) {
+ LOG((" echConfig is not used, fallback to origin conn info"));
+ mOrigConnInfo.swap(mConnInfo);
+ return;
+ }
+
+ Telemetry::HistogramID id = Telemetry::TRANSACTION_ECH_RETRY_OTHERS_COUNT;
+ auto updateCount = MakeScopeExit([&] {
+ uint32_t count = 0;
+ bool existed = mEchRetryCounterMap.Get(id, &count);
+ MOZ_ASSERT(existed, "table not initialized");
+ if (existed) {
+ mEchRetryCounterMap.Put(id, ++count);
+ }
+ });
+
+ 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<nsISupports> secInfo;
+ if (mConnection) {
+ mConnection->GetSecurityInfo(getter_AddRefs(secInfo));
+ }
+
+ nsCOMPtr<nsISSLSocketControl> socketControl = do_QueryInterface(secInfo);
+ 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(),
+ allRecordsHaveEchConfig)) {
+ LOG(
+ (" Can't find other records with echConfig, "
+ "allRecordsHaveEchConfig=%d",
+ allRecordsHaveEchConfig));
+ if (gHttpHandler->FallbackToOriginIfConfigsAreECHAndAllFailed() ||
+ !allRecordsHaveEchConfig) {
+ mOrigConnInfo.swap(mConnInfo);
+ }
+ return;
+ }
+ } else {
+ LOG(
+ (" No available records to retry, "
+ "mAllRecordsInH3ExcludedListBefore=%d",
+ mAllRecordsInH3ExcludedListBefore));
+ if (gHttpHandler->FallbackToOriginIfConfigsAreECHAndAllFailed() &&
+ !mAllRecordsInH3ExcludedListBefore) {
+ mOrigConnInfo.swap(mConnInfo);
+ }
+ return;
+ }
+ }
+
+ if (LOG5_ENABLED()) {
+ LOG(("SvcDomainName to retry: ["));
+ for (const auto& r : mRecordsForRetry) {
+ nsAutoCString name;
+ r->GetName(name);
+ LOG((" name=%s", name.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);
+ }
+}
+
+void nsHttpTransaction::Close(nsresult reason) {
+ LOG(("nsHttpTransaction::Close [this=%p reason=%" PRIx32 "]\n", this,
+ static_cast<uint32_t>(reason)));
+
+ 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;
+ }
+
+ // When we capture 407 from H2 proxy via CONNECT, prepare the response headers
+ // for authentication in http channel.
+ if (mTunnelProvider && reason == NS_ERROR_PROXY_AUTHENTICATION_FAILED) {
+ MOZ_ASSERT(mProxyConnectResponseCode == 407, "non-407 proxy auth failed");
+ MOZ_ASSERT(!mFlat407Headers.IsEmpty(), "Contain status line at least");
+ uint32_t unused = 0;
+
+ // Reset the reason to avoid nsHttpChannel::ProcessFallback
+ reason = ProcessData(mFlat407Headers.BeginWriting(),
+ mFlat407Headers.Length(), &unused);
+
+ if (NS_SUCCEEDED(reason)) {
+ // prevent restarting the transaction
+ mReceivedData = true;
+ }
+
+ LOG(("nsHttpTransaction::Close [this=%p] overwrite reason to %" PRIx32
+ " for 407 proxy via CONNECT\n",
+ this, static_cast<uint32_t>(reason)));
+ }
+
+ NotifyTransactionObserver(reason);
+
+ if (mTokenBucketCancel) {
+ mTokenBucketCancel->Cancel(reason);
+ mTokenBucketCancel = nullptr;
+ }
+
+ if (mActivityDistributor) {
+ // report the reponse is complete if not already reported
+ if (!mResponseIsComplete) {
+ nsresult rv = mActivityDistributor->ObserveActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, PR_Now(),
+ static_cast<uint64_t>(mContentRead), ""_ns);
+ if (NS_FAILED(rv)) {
+ LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ }
+
+ // report that this transaction is closing
+ nsresult rv = mActivityDistributor->ObserveActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE, PR_Now(), 0, ""_ns);
+ if (NS_FAILED(rv)) {
+ LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ }
+
+ // 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;
+ }
+ mConnected = false;
+ mTunnelProvider = nullptr;
+
+ //
+ // 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) ||
+ mOrigConnInfo) &&
+ (!(mCaps & NS_HTTP_STICKY_CONNECTION) ||
+ (mCaps & NS_HTTP_CONNECTION_RESTARTABLE) ||
+ (mEarlyDataDisposition == EARLY_425))) {
+ if (mForceRestart && 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"));
+ // Only record the first restart attempt.
+ if (!mRestartCount) {
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_RESTART_REASON,
+ TRANSACTION_RESTART_FORCED);
+ }
+ return;
+ }
+
+ // 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.
+ bool restartToFallbackConnInfo = !reallySentData && mOrigConnInfo;
+
+ if (reason ==
+ psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA) ||
+ (!mReceivedData && ((mRequestHead && mRequestHead->IsSafeMethod()) ||
+ !reallySentData || connReused)) ||
+ restartToFallbackConnInfo) {
+ if (restartToFallbackConnInfo) {
+ 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 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())) {
+ // Only record the first restart attempt.
+ if (!mRestartCount) {
+ if (restartToFallbackConnInfo) {
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_RESTART_REASON,
+ TRANSACTION_RESTART_HTTPSSVC_INVOLVED);
+ } else if (!reallySentData) {
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_RESTART_REASON,
+ TRANSACTION_RESTART_NO_DATA_SENT);
+ } else if (reason == psm::GetXPCOMFromNSSError(
+ SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA)) {
+ Telemetry::Accumulate(
+ Telemetry::HTTP_TRANSACTION_RESTART_REASON,
+ TRANSACTION_RESTART_DOWNGRADE_WITH_EARLY_DATA);
+ } else {
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_RESTART_REASON,
+ TRANSACTION_RESTART_OTHERS);
+ }
+ }
+ 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);
+ }
+ }
+ }
+
+ if (!mRestartCount) {
+ Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_RESTART_REASON,
+ TRANSACTION_RESTART_NONE);
+ }
+
+ 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';
+ uint32_t unused = 0;
+ Unused << ParseHead(&data, 1, &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());
+ }
+ }
+
+ 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;
+ }
+
+ mStatus = reason;
+ mTransactionDone = true; // forcibly flag the transaction as complete
+ mClosed = true;
+ mResolver = nullptr;
+ ReleaseBlockingTransaction();
+
+ // release some resources that we no longer need
+ mRequestStream = nullptr;
+ mReqHeaderBuf.Truncate();
+ mLineBuf.Truncate();
+ if (mChunkedDecoder) {
+ delete mChunkedDecoder;
+ mChunkedDecoder = nullptr;
+ }
+
+ for (auto iter = mEchRetryCounterMap.ConstIter(); !iter.Done(); iter.Next()) {
+ Telemetry::Accumulate(static_cast<Telemetry::HistogramID>(iter.Key()),
+ iter.UserData());
+ }
+
+ // 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>
+//-----------------------------------------------------------------------------
+
+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));
+ mTunnelProvider = nullptr;
+
+ 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);
+
+ // 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() &&
+ !mDontRetryWithDirectRoute) {
+ RefPtr<nsHttpConnectionInfo> ci;
+ mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
+ mConnInfo = ci;
+ if (mRequestHead) {
+ DebugOnly<nsresult> rv =
+ mRequestHead->SetHeader(nsHttp::Alternate_Service_Used, "0"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // Reset mDoNotRemoveAltSvc for the next try.
+ mDoNotRemoveAltSvc = false;
+ mRestarted = true;
+
+ 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 (PL_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(len, HTTPHeaderLen - mLineBuf.Length());
+ if (PL_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 (PL_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 &&
+ (PL_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 &&
+ (PL_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 &&
+ (PL_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) {
+ mResponseHead->ParseStatusLine(line);
+ 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 != 101) && (status / 100 == 1)) {
+ LOG(("ignoring 1xx response\n"));
+ mHaveStatusLine = false;
+ mHttpResponseMatched = false;
+ mConnection->SetLastTransactionExpectedNoContent(true);
+ mResponseHead->Reset();
+ return NS_OK;
+ }
+ 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 (mActivityDistributor && !mReportedStart) {
+ mReportedStart = true;
+ rv = mActivityDistributor->ObserveActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START, PR_Now(), 0, ""_ns);
+ if (NS_FAILED(rv)) {
+ LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+
+ 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;
+
+ mResponseHead->ParseStatusLine(""_ns);
+ 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;
+}
+
+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 (mFastOpenStatus == TFO_DATA_SENT) {
+ Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_TCP_Fast_Open,
+ "data sent"_ns);
+ } else if (mFastOpenStatus == TFO_TRIED) {
+ Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_TCP_Fast_Open,
+ "tried negotiating"_ns);
+ } else if (mFastOpenStatus == TFO_FAILED) {
+ Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_TCP_Fast_Open,
+ "failed"_ns);
+ } // no header on TFO_NOT_TRIED 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;
+ }
+
+ // 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 421:
+ LOG(("Misdirected Request.\n"));
+ gHttpHandler->ClearHostMapping(mConnInfo);
+
+ m421Received = true;
+
+ // Unsticky the connection to allow restart. This can get set when an
+ // NTLM proxy is successfully authenticated.
+ mCaps |= (NS_HTTP_REFRESH_DNS | NS_HTTP_CONNECTION_RESTARTABLE);
+ mCaps &= ~NS_HTTP_STICKY_CONNECTION;
+
+ // retry on a new connection - just in case
+ if (!mRestartCount) {
+ 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
+ if ((mHttpVersion >= HttpVersion::v2_0) &&
+ (mResponseHead->Status() < 500) && (mResponseHead->Status() != 421)) {
+ nsAutoCString altSvc;
+ Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, altSvc);
+ if (!altSvc.IsEmpty() || nsHttp::IsReasonableHeaderValue(altSvc)) {
+ for (uint32_t i = 0; i < kHttp3VersionCount; i++) {
+ if (PL_strstr(altSvc.get(), kHttp3Versions[i].get())) {
+ mSupportsHTTP3 = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // 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;
+ }
+
+ if (mResponseHead->Status() == 200 && mH2WSTransaction) {
+ // http/2 websockets do not have response bodies
+ 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(*nsHttp::GetLock());
+ 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
+ if (mActivityDistributor) {
+ rv = mActivityDistributor->ObserveActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, PR_Now(),
+ static_cast<uint64_t>(mContentRead), ""_ns);
+ if (NS_FAILED(rv)) {
+ LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+
+ 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);
+
+ // report the completed response header
+ if (mActivityDistributor && mResponseHead && mHaveAllHeaders &&
+ !mReportedResponseHeader) {
+ mReportedResponseHeader = true;
+ nsAutoCString completeResponseHeaders;
+ mResponseHead->Flatten(completeResponseHeaders, false);
+ completeResponseHeaders.AppendLiteral("\r\n");
+ rv = mActivityDistributor->ObserveActivityWithArgs(
+ HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
+ NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER, PR_Now(), 0,
+ completeResponseHeaders);
+ if (NS_FAILED(rv)) {
+ LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+
+ // 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::DisableHttp3() {
+ 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", this,
+ mOrigConnInfo->HashKey().get()));
+ 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));
+ if (mRequestHead) {
+ DebugOnly<nsresult> rv =
+ mRequestHead->SetHeader(nsHttp::Alternate_Service_Used, "0"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ 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")) {
+ authenticator = new nsHttpNegotiateAuth();
+ } else if (schema.EqualsLiteral("basic")) {
+ authenticator = new nsHttpBasicAuth();
+ } else if (schema.EqualsLiteral("digest")) {
+ authenticator = new nsHttpDigestAuth();
+ } else if (schema.EqualsLiteral("ntlm")) {
+ authenticator = new nsHttpNTLMAuth();
+ }
+ 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;
+}
+
+const 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)
+
+//-----------------------------------------------------------------------------
+// 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)) NS_ERROR("ResumeRecv failed");
+ }
+ return NS_OK;
+}
+
+void nsHttpTransaction::GetNetworkAddresses(NetAddr& self, NetAddr& peer,
+ bool& aResolvedByTRR,
+ bool& aEchConfigUsed) {
+ MutexAutoLock lock(mLock);
+ self = mSelfAddr;
+ peer = mPeerAddr;
+ aResolvedByTRR = mResolvedByTRR;
+ aEchConfigUsed = mEchConfigUsed;
+}
+
+bool nsHttpTransaction::CanDo0RTT() {
+ if (mRequestHead->IsSafeMethod() && !mDoNotTryEarlyData &&
+ (!mConnection || !mConnection->IsProxyConnectInProgress())) {
+ return true;
+ }
+ return false;
+}
+
+bool nsHttpTransaction::Do0RTT() {
+ 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) {
+ // 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;
+ nsCOMPtr<nsISupports> info;
+ mConnection->GetSecurityInfo(getter_AddRefs(info));
+ MutexAutoLock lock(mLock);
+ mSecurityInfo = std::move(info);
+ }
+ return NS_OK;
+}
+
+nsresult nsHttpTransaction::RestartOnFastOpenError() {
+ // This will happen on connection error during Fast Open or if connect
+ // during Fast Open takes too long. So we should not have received any
+ // data!
+ MOZ_ASSERT(!mReceivedData);
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ LOG(
+ ("nsHttpTransaction::RestartOnFastOpenError - restarting transaction "
+ "%p\n",
+ this));
+
+ // 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);
+ // clear old connection state...
+ {
+ MutexAutoLock lock(mLock);
+ mSecurityInfo = nullptr;
+ }
+
+ if (!mConnInfo->GetRoutedHost().IsEmpty()) {
+ RefPtr<nsHttpConnectionInfo> ci;
+ mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
+ mConnInfo = ci;
+ if (mRequestHead) {
+ DebugOnly<nsresult> rv =
+ mRequestHead->SetHeader(nsHttp::Alternate_Service_Used, "0"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+ mEarlyDataDisposition = EARLY_NONE;
+ m0RTTInProgress = false;
+ mFastOpenStatus = TFO_FAILED;
+ mTimings = TimingStruct();
+ return NS_OK;
+}
+
+void nsHttpTransaction::SetFastOpenStatus(uint8_t aStatus) {
+ LOG(("nsHttpTransaction::SetFastOpenStatus %d [this=%p]\n", aStatus, this));
+ mFastOpenStatus = aStatus;
+}
+
+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(*nsHttp::GetLock());
+ 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(*nsHttp::GetLock());
+ 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::SetH2WSTransaction(
+ SpdyConnectTransaction* aH2WSTransaction) {
+ MOZ_ASSERT(OnSocketThread());
+
+ mH2WSTransaction = aH2WSTransaction;
+}
+
+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<nsISupports> secInfo;
+ mConnection->GetSecurityInfo(getter_AddRefs(secInfo));
+ nsCOMPtr<nsISSLSocketControl> socketControl = do_QueryInterface(secInfo);
+ 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);
+ mDNSRequest = nullptr;
+ }
+
+ 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; });
+
+ MakeDontWaitHTTPSSVC();
+
+ 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 = !mConnInfo->IsHttp3() && newInfo->IsHttp3();
+ if (!gHttpHandler->ConnMgr()->MoveTransToNewConnEntry(this, newInfo)) {
+ // MoveTransToNewConnEntry() returning fail means this transaction is
+ // not in the connection entry's pending queue. This could happen if
+ // OnLookupComplete() is called before this transaction is added in the
+ // queue. We still need to update the connection info, so this transaction
+ // can be added to the right connection entry.
+ UpdateConnectionInfo(newInfo);
+ }
+
+ 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.Put(Telemetry::TRANSACTION_ECH_RETRY_WITH_ECH_COUNT, 0);
+ mEchRetryCounterMap.Put(Telemetry::TRANSACTION_ECH_RETRY_WITHOUT_ECH_COUNT,
+ 0);
+ mEchRetryCounterMap.Put(Telemetry::TRANSACTION_ECH_RETRY_ECH_FAILED_COUNT,
+ 0);
+ mEchRetryCounterMap.Put(Telemetry::TRANSACTION_ECH_RETRY_OTHERS_COUNT, 0);
+ }
+
+ return NS_OK;
+}
+
+uint32_t nsHttpTransaction::HTTPSSVCReceivedStage() {
+ return mHTTPSSVCReceivedStage;
+}
+
+void nsHttpTransaction::MaybeCancelFallbackTimer() {
+ if (mFastFallbackTimer) {
+ mFastFallbackTimer->Cancel();
+ mFastFallbackTimer = nullptr;
+ }
+
+ if (mHttp3BackupTimer) {
+ mHttp3BackupTimer->Cancel();
+ mHttp3BackupTimer = nullptr;
+ }
+}
+
+void nsHttpTransaction::OnBackupConnectionReady() {
+ LOG(("nsHttpTransaction::OnBackupConnectionReady [%p] mConnected=%d", this,
+ mConnected));
+ if (mConnected || mClosed) {
+ return;
+ }
+
+ HandleFallback(mBackupConnInfo);
+
+ mCaps |= NS_HTTP_DISALLOW_HTTP3;
+ DebugOnly<nsresult> rv =
+ mRequestHead->SetHeader(nsHttp::Alternate_Service_Used, "0"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+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();
+ }
+ };
+
+ RefPtr<SpeculativeTransaction> trans = new SpeculativeTransaction(
+ mBackupConnInfo, mCallbacks, mCaps | NS_HTTP_DISALLOW_HTTP3,
+ std::move(callback));
+ gHttpHandler->ConnMgr()->DoSpeculativeConnection(trans, false);
+}
+
+void nsHttpTransaction::OnFastFallbackTimer() {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ LOG(("nsHttpTransaction::OnFastFallbackTimer [%p] mConnected=%d", this,
+ mConnected));
+
+ mFastFallbackTimer = nullptr;
+ mFastFallbackTriggered = true;
+
+ MOZ_ASSERT(mHTTPSSVCRecord && mOrigConnInfo);
+ if (!mHTTPSSVCRecord || !mOrigConnInfo) {
+ return;
+ }
+
+ RefPtr<nsHttpConnectionInfo> fallbackConnInfo;
+ bool echConfigUsed =
+ gHttpHandler->EchConfigEnabled() && !mConnInfo->GetEchConfig().IsEmpty();
+ fallbackConnInfo = PrepareFastFallbackConnInfo(echConfigUsed);
+
+ // Need to backup the origin conn info, since UpdateConnectionInfo() will be
+ // called in HandleFallback() and mOrigConnInfo will be
+ // replaced.
+ RefPtr<nsHttpConnectionInfo> backup = mOrigConnInfo;
+ HandleFallback(fallbackConnInfo);
+ mOrigConnInfo.swap(backup);
+
+ if (mResolver) {
+ mResolver->PrefetchAddrRecord(fallbackConnInfo->GetRoutedHost(),
+ mCaps & NS_HTTP_REFRESH_DNS);
+ }
+}
+
+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()->MoveTransToNewConnEntry(this, aFallbackConnInfo);
+ 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);
+ }
+}
+
+NS_IMETHODIMP
+nsHttpTransaction::Notify(nsITimer* aTimer) {
+ MOZ_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;
+}
+
+bool nsHttpTransaction::GetSupportsHTTP3() { return mSupportsHTTP3; }
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h
new file mode 100644
index 0000000000..52e4953de0
--- /dev/null
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -0,0 +1,545 @@
+/* -*- 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 "nsHttp.h"
+#include "nsAHttpTransaction.h"
+#include "HttpTransactionShell.h"
+#include "nsAHttpConnection.h"
+#include "EventTokenBucket.h"
+#include "nsCOMPtr.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsThreadUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsITimer.h"
+#include "TimingStruct.h"
+#include "Http2Push.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "ARefBase.h"
+
+//-----------------------------------------------------------------------------
+
+class nsIHttpActivityObserver;
+class nsIDNSHTTPSSVCRecord;
+class nsIEventTarget;
+class nsIInputStream;
+class nsIOutputStream;
+class nsIRequestContext;
+class nsISVCBRecord;
+
+namespace mozilla {
+namespace net {
+
+class HTTPSRecordResolver;
+class nsHttpChunkedDecoder;
+class nsHttpHeaderArray;
+class nsHttpRequestHead;
+class nsHttpResponseHead;
+class NullHttpTransaction;
+class SpdyConnectTransaction;
+
+//-----------------------------------------------------------------------------
+// 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:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSAHTTPTRANSACTION
+ NS_DECL_HTTPTRANSACTIONSHELL
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSITIMERCALLBACK
+
+ 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 MakeDontWaitHTTPSSVC() { mCaps &= ~NS_HTTP_WAIT_HTTPSSVC_RESULT; }
+
+ // 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 mPendingTime to the current time stamp or to a null time stamp (if now
+ // is false)
+ void SetPendingTime(bool now = true) {
+ if (!now && !mPendingTime.IsNull()) {
+ // Remember how long it took. We will use this vaule to record
+ // TRANSACTION_WAIT_TIME_HTTP2_SUP_HTTP3 telemetry, but we need to wait
+ // for the response headers.
+ mPendingDurationTime = TimeStamp::Now() - mPendingTime;
+ }
+ mPendingTime = now ? TimeStamp::Now() : TimeStamp();
+ }
+ const TimeStamp GetPendingTime() { return mPendingTime; }
+
+ // overload of nsAHttpTransaction::RequestContext()
+ nsIRequestContext* RequestContext() override { return mRequestContext.get(); }
+ void DispatchedAsBlocking();
+ void RemoveDispatchedAsBlocking();
+
+ void DisableSpdy() override;
+ void DisableHttp3() 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();
+
+ [[nodiscard]] bool CanDo0RTT() override;
+ [[nodiscard]] nsresult RestartOnFastOpenError() override;
+
+ uint64_t TopLevelOuterContentWindowId() override {
+ return mTopLevelOuterContentWindowId;
+ }
+
+ void SetFastOpenStatus(uint8_t aStatus) override;
+
+ void SetHttpTrailers(nsCString& aTrailers);
+
+ bool IsWebsocketUpgrade();
+ void SetH2WSTransaction(SpdyConnectTransaction*);
+
+ 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(uint32_t cos);
+
+ virtual nsresult OnHTTPSRRAvailable(
+ nsIDNSHTTPSSVCRecord* aHTTPSSVCRecord,
+ nsISVCBRecord* aHighestPriorityRecord) override;
+
+ 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,
+ 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();
+ void OnFastFallbackTimer();
+ void HandleFallback(nsHttpConnectionInfo* aFallbackConnInfo);
+ void MaybeCancelFallbackTimer();
+
+ 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;
+
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsITransportEventSink> mTransportSink;
+ nsCOMPtr<nsIEventTarget> mConsumerTarget;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+ nsCOMPtr<nsIAsyncInputStream> mPipeIn;
+ nsCOMPtr<nsIAsyncOutputStream> mPipeOut;
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+
+ uint64_t mChannelId;
+ nsCOMPtr<nsIHttpActivityObserver> mActivityDistributor;
+
+ nsCString mReqHeaderBuf; // flattened request headers
+ nsCOMPtr<nsIInputStream> mRequestStream;
+ int64_t mRequestSize;
+
+ 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; // weak ref
+ nsHttpResponseHead* mResponseHead; // owning pointer
+
+ nsAHttpSegmentReader* mReader;
+ nsAHttpSegmentWriter* mWriter;
+
+ nsCString mLineBuf; // may contain a partial line
+
+ int64_t mContentLength; // equals -1 if unknown
+ int64_t mContentRead; // count of consumed content bytes
+ Atomic<int64_t, ReleaseAcquire> mTransferSize; // 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;
+
+ RefPtr<Http2PushedStreamWrapper> mPushedStream;
+ uint32_t mInitialRwin;
+
+ nsHttpChunkedDecoder* mChunkedDecoder;
+
+ TimingStruct mTimings;
+
+ nsresult mStatus;
+
+ int16_t mPriority;
+
+ uint16_t
+ mRestartCount; // the number of times this transaction has been restarted
+ uint32_t mCaps;
+
+ HttpVersion mHttpVersion;
+ uint16_t mHttpResponseCode;
+ nsCString mFlat407Headers;
+
+ uint32_t mCurrentHttpResponseHeaderSize;
+
+ 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;
+
+ // 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;
+ Atomic<bool, ReleaseAcquire> mResponseIsComplete;
+ Atomic<bool, ReleaseAcquire> mClosed;
+
+ // 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;
+
+ // state flags, all logically boolean, but not packed together into a
+ // bitfield so as to avoid bitfield-induced races. See bug 560579.
+ bool mConnected;
+ bool mActivated;
+ bool mHaveStatusLine;
+ bool mHaveAllHeaders;
+ bool mTransactionDone;
+ bool mDidContentStart;
+ bool mNoContent; // expecting an empty entity body
+ bool mSentData;
+ bool mReceivedData;
+ bool mStatusEventPending;
+ bool mHasRequestBody;
+ bool mProxyConnectFailed;
+ bool mHttpResponseMatched;
+ bool mPreserveStream;
+ bool mDispatchedAsBlocking;
+ bool mResponseTimeoutEnabled;
+ bool mForceRestart;
+ bool mReuseOnRestart;
+ bool mContentDecoding;
+ bool mContentDecodingCheck;
+ bool mDeferredSendProgress;
+ bool mWaitingOnPipeOut;
+
+ bool mIsHttp3Used = false;
+ bool mDoNotRemoveAltSvc;
+
+ // 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;
+ bool mReportedResponseHeader;
+
+ // protected by nsHttp::GetLock()
+ bool mResponseHeadTaken;
+ UniquePtr<nsHttpHeaderArray> mForTakeResponseTrailers;
+ bool mResponseTrailersTaken;
+
+ // Set when this transaction was restarted by call to Restart(). Used to tell
+ // the http channel to reset proxy authentication.
+ Atomic<bool> mRestarted;
+
+ // The time when the transaction was submitted to the Connection Manager
+ TimeStamp mPendingTime;
+ TimeDuration mPendingDurationTime;
+
+ uint64_t mTopLevelOuterContentWindowId;
+
+ // 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;
+ bool mPassedRatePacing;
+ bool mSynchronousRatePaceRequest;
+ nsCOMPtr<nsICancelable> mTokenBucketCancel;
+
+ public:
+ uint32_t ClassOfService() { return mClassOfService; }
+
+ private:
+ Atomic<uint32_t, Relaxed> mClassOfService;
+
+ public:
+ // setting TunnelProvider to non-null means the transaction should only
+ // be dispatched on a specific ConnectionInfo Hash Key (as opposed to a
+ // generic wild card one). That means in the specific case of carrying this
+ // transaction on an HTTP/2 tunnel it will only be dispatched onto an
+ // existing tunnel instead of triggering creation of a new one.
+ // The tunnel provider is used for ASpdySession::MaybeReTunnel() checks.
+
+ void SetTunnelProvider(ASpdySession* provider) { mTunnelProvider = provider; }
+ ASpdySession* TunnelProvider() { return mTunnelProvider; }
+ nsIInterfaceRequestor* SecurityCallbacks() { return mCallbacks; }
+ // Called when this transaction is inserted in the pending queue.
+ void OnPendingQueueInserted();
+
+ private:
+ RefPtr<ASpdySession> mTunnelProvider;
+ TransactionObserverFunc mTransactionObserver;
+ NetAddr mSelfAddr;
+ NetAddr mPeerAddr;
+ bool mResolvedByTRR;
+ bool mEchConfigUsed = false;
+
+ bool m0RTTInProgress;
+ bool mDoNotTryEarlyData;
+ enum {
+ EARLY_NONE,
+ EARLY_SENT,
+ EARLY_ACCEPTED,
+ EARLY_425
+ } mEarlyDataDisposition;
+
+ uint8_t mFastOpenStatus;
+
+ // H2 websocket support
+ RefPtr<SpdyConnectTransaction> mH2WSTransaction;
+
+ HttpTrafficCategory mTrafficCategory;
+ bool mThroughCaptivePortal;
+ Atomic<int32_t> mProxyConnectResponseCode;
+
+ OnPushCallback mOnPushCallback;
+ nsDataHashtable<nsUint32HashKey, RefPtr<Http2PushedStreamWrapper>>
+ mIDToStreamMap;
+
+ nsCOMPtr<nsICancelable> mDNSRequest;
+ Atomic<uint32_t, Relaxed> mHTTPSSVCReceivedStage;
+ bool m421Received = false;
+ nsCOMPtr<nsIDNSHTTPSSVCRecord> mHTTPSSVCRecord;
+ nsTArray<RefPtr<nsISVCBRecord>> mRecordsForRetry;
+ bool mDontRetryWithDirectRoute = false;
+ bool mFastFallbackTriggered = false;
+ bool mAllRecordsInH3ExcludedListBefore = false;
+ bool mHttp3BackupTimerCreated = false;
+ nsCOMPtr<nsITimer> mFastFallbackTimer;
+ nsCOMPtr<nsITimer> mHttp3BackupTimer;
+ RefPtr<nsHttpConnectionInfo> mBackupConnInfo;
+ RefPtr<HTTPSRecordResolver> mResolver;
+
+ // 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 = 1, // The transaction was forced to restart.
+ TRANSACTION_RESTART_HTTPSSVC_INVOLVED = 2,
+ TRANSACTION_RESTART_NO_DATA_SENT = 3,
+ TRANSACTION_RESTART_DOWNGRADE_WITH_EARLY_DATA = 4,
+ TRANSACTION_RESTART_OTHERS = 5,
+ };
+
+ nsDataHashtable<nsUint32HashKey, uint32_t> mEchRetryCounterMap;
+
+ bool mSupportsHTTP3 = false;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // nsHttpTransaction_h__
diff --git a/netwerk/protocol/http/nsIBackgroundChannelRegistrar.idl b/netwerk/protocol/http/nsIBackgroundChannelRegistrar.idl
new file mode 100644
index 0000000000..1368125450
--- /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.
+ */
+[builtinclass, 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/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/nsIHttpActivityObserver.idl b/netwerk/protocol/http/nsIHttpActivityObserver.idl
new file mode 100644
index 0000000000..d3286e2354
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpActivityObserver.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"
+
+%{ 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);
+
+ const unsigned long ACTIVITY_TYPE_SOCKET_TRANSPORT = 0x0001;
+ const unsigned long ACTIVITY_TYPE_HTTP_TRANSACTION = 0x0002;
+
+ 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;
+
+ /**
+ * 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_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
+
+%}
+
+/**
+ * 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);
+};
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..17ad88773d
--- /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;
+
+[builtinclass, 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..f9e19bed7d
--- /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.
+ */
+[builtinclass, 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 string 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 string aChallenge,
+ in boolean aProxyAuth,
+ in wstring aDomain,
+ in wstring aUser,
+ in wstring 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]
+ string generateCredentials(in nsIHttpAuthenticableChannel aChannel,
+ in string aChallenge,
+ in boolean aProxyAuth,
+ in wstring aDomain,
+ in wstring aUser,
+ in wstring 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..03803d490d
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannel.idl
@@ -0,0 +1,510 @@
+/* -*- 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"
+%}
+
+/**
+ * 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 no longer has any effect, it remains for backwards compat
+ *
+ * @throws NS_ERROR_FAILURE if set after the channel has been opened.
+ */
+ [must_use] attribute boolean allowPipelining;
+
+ /**
+ * 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;
+
+ /**
+ * Returns the allowing status for flash plugin for this channel.
+ */
+ cenum FlashPluginState : 8 {
+ FlashPluginUnknown = 0,
+ FlashPluginAllowed = 1,
+ FlashPluginDenied = 2,
+ FlashPluginDeniedInSubdocuments = 3,
+
+ // Keep this equal to the last value.
+ FlashPluginLastValue = 3,
+ };
+ [infallible] readonly attribute nsIHttpChannel_FlashPluginState flashPluginState;
+
+ /**
+ * ID of the top-level outer content window. Identifies this channel's
+ * top-level window it comes from.
+ *
+ * NOTE: The setter of this attribute is currently for xpcshell test only.
+ * Don't alter it otherwise.
+ */
+ [must_use] attribute uint64_t topLevelOuterContentWindowId;
+
+ /**
+ * 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.
+ */
+ void logBlockedCORSRequest(in AString aMessage, in ACString aCategory);
+
+ void logMimeTypeMismatch(in ACString aMessageName,
+ in boolean aWarning,
+ in AString aURL,
+ in AString aContentType);
+
+%{ C++
+ virtual void SetSource(mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> aSource) {}
+%}
+};
diff --git a/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl b/netwerk/protocol/http/nsIHttpChannelAuthProvider.idl
new file mode 100644
index 0000000000..73282a4bef
--- /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.
+ */
+
+[builtinclass, 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..c5509f011d
--- /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;
+
+[builtinclass, 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..ea079ee221
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -0,0 +1,439 @@
+/* -*- 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"
+
+%{C++
+#include "nsStringFwd.h"
+#include "nsTArrayForwardDeclare.h"
+template<class T> class nsCOMArray;
+namespace mozilla {
+class TimeStamp;
+}
+%}
+[ptr] native StringArray(nsTArray<nsCString>);
+[ref] native CStringArrayRef(const nsTArray<nsCString>);
+[ref] native securityMessagesArray(nsCOMArray<nsISecurityConsoleMessage>);
+
+native TimeStamp(mozilla::TimeStamp);
+
+interface nsIAsyncInputStream;
+interface nsIAsyncOutputStream;
+interface nsIPrincipal;
+interface nsIProxyInfo;
+interface nsISecurityConsoleMessage;
+interface nsISocketTransport;
+interface nsIURI;
+
+/**
+ * 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);
+};
+
+/**
+ * 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);
+
+ /**
+ * Setup this channel as an application cache fallback channel.
+ */
+ [must_use] void setupFallbackChannel(in string aFallbackKey);
+
+ /**
+ * 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.spdy.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.enabled 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;
+
+ /**
+ * 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.
+ */
+ [noscript, must_use] readonly attribute boolean isResolvedByTRR;
+
+ /**
+ * 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;
+
+ const unsigned long CORS_MODE_SAME_ORIGIN = 0;
+ const unsigned long CORS_MODE_NO_CORS = 1;
+ const unsigned long CORS_MODE_CORS = 2;
+ const unsigned long CORS_MODE_NAVIGATE = 3;
+ /**
+ * Set by nsCORSListenerProxy to indicate CORS load type. Defaults to CORS_MODE_NO_CORS.
+ */
+ [must_use] attribute unsigned long corsMode;
+
+ 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.
+ */
+ [noscript, notxpcom, nostdcall]
+ void setIPv4Disabled();
+
+ /**
+ * The channel will be loaded over IPv4, disabling IPv6.
+ */
+ [noscript, notxpcom, nostdcall]
+ 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();
+
+ [notxpcom, nostdcall] attribute boolean hasNonEmptySandboxingFlag;
+
+ [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;
+};
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..e3d6fb5d1f
--- /dev/null
+++ b/netwerk/protocol/http/nsIHttpProtocolHandler.idl
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "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 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);
+};
+
+%{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"
+
+/**
+ * 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/nsIRaceCacheWithNetwork.idl b/netwerk/protocol/http/nsIRaceCacheWithNetwork.idl
new file mode 100644
index 0000000000..b2b05ec8db
--- /dev/null
+++ b/netwerk/protocol/http/nsIRaceCacheWithNetwork.idl
@@ -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/. */
+
+#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 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/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..ef6b9319a6
--- /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;
+ 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/moz.build b/netwerk/protocol/moz.build
new file mode 100644
index 0000000000..35d389dfdb
--- /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", "ftp"]
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ DIRS += ["gio"]
+DIRS += ["http", "res", "viewsource", "websocket"]
diff --git a/netwerk/protocol/res/ExtensionProtocolHandler.cpp b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
new file mode 100644
index 0000000000..3e4e314577
--- /dev/null
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
@@ -0,0 +1,987 @@
+/* -*- 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/ContentChild.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "mozilla/ExtensionPolicyService.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ResultExtensions.h"
+
+#include "FileDescriptor.h"
+#include "FileDescriptorFile.h"
+#include "LoadInfo.h"
+#include "nsContentUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsDirectoryServiceDefs.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 : public RefCounted<ExtensionStreamGetter> {
+ 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();
+ }
+
+ ~ExtensionStreamGetter() = default;
+
+ void SetupEventTarget() {
+ mMainThreadEventTarget = nsContentUtils::GetEventTargetByLoadInfo(
+ mLoadInfo, TaskCategory::Other);
+ if (!mMainThreadEventTarget) {
+ mMainThreadEventTarget = GetMainThreadSerialEventTarget();
+ }
+ }
+
+ // Get an input stream or file descriptor from the parent asynchronously.
+ Result<Ok, nsresult> 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);
+
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(ExtensionStreamGetter)
+
+ private:
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIJARChannel> mJarChannel;
+ nsCOMPtr<nsIFile> mJarFile;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
+ bool mIsJarChannel;
+};
+
+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,
+ &prFileDesc.rwget());
+ }
+#else
+ nsresult rv = mFile->OpenNSPRFileDesc(PR_RDONLY, 0, &prFileDesc.rwget());
+#endif /* XP_WIN */
+
+ if (NS_SUCCEEDED(rv)) {
+ mFD = FileDescriptor(FileDescriptor::PlatformHandleType(
+ PR_FileDesc2NativeHandle(prFileDesc)));
+ }
+
+ 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.
+Result<Ok, nsresult> ExtensionStreamGetter::GetAsync(
+ nsIStreamListener* aListener, nsIChannel* aChannel) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mMainThreadEventTarget);
+
+ mListener = aListener;
+ mChannel = aChannel;
+
+ 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 Ok();
+ }
+
+ // 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 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->Cancel(NS_BINDING_ABORTED);
+}
+
+// Handle an input stream sent from the parent.
+void ExtensionStreamGetter::OnStream(already_AddRefed<nsIInputStream> aStream) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mListener);
+ MOZ_ASSERT(mMainThreadEventTarget);
+
+ nsCOMPtr<nsIInputStream> stream = std::move(aStream);
+
+ // We must keep an owning reference to the listener
+ // until we pass it on to AsyncRead.
+ nsCOMPtr<nsIStreamListener> listener = std::move(mListener);
+
+ MOZ_ASSERT(mChannel);
+
+ if (!stream) {
+ // The parent didn't send us back a stream.
+ CancelRequest(listener, mChannel, 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, mChannel, rv);
+ return;
+ }
+
+ rv = pump->AsyncRead(listener);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, mChannel, rv);
+ }
+}
+
+// Handle an FD sent from the parent.
+void ExtensionStreamGetter::OnFD(const FileDescriptor& aFD) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mListener);
+ MOZ_ASSERT(mChannel);
+
+ if (!aFD.IsValid()) {
+ OnStream(nullptr);
+ return;
+ }
+
+ // We must keep an owning reference to the listener
+ // until we pass it on to AsyncOpen.
+ nsCOMPtr<nsIStreamListener> listener = std::move(mListener);
+
+ RefPtr<FileDescriptorFile> fdFile = new FileDescriptorFile(aFD, mJarFile);
+ mJarChannel->SetJarFile(fdFile);
+ nsresult rv = mJarChannel->AsyncOpen(listener);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, mChannel, 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)
+#if !defined(XP_WIN)
+# if defined(XP_MACOSX)
+ ,
+ mAlreadyCheckedDevRepo(false)
+# endif /* XP_MACOSX */
+ ,
+ mAlreadyCheckedAppDir(false)
+#endif /* ! XP_WIN */
+{
+ // 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 a
+ // whitelisted subset are web-accessible (and cross-origin fetchable). Check
+ // that whitelist.
+ if (policy->IsPathWebAccessible(url.FilePath())) {
+ flags |= URI_LOADABLE_BY_ANYONE | URI_FETCHABLE_BY_ANYONE;
+ } 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::HandleValue aValue,
+ 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));
+ }
+ return RequestOrReason(origChannel);
+ });
+ } else if (readyPromise) {
+ size_t matchIdx;
+ if (BinarySearchIf(
+ sStaticFileExtensions, 0, ArrayLength(sStaticFileExtensions),
+ [&ext](const char* aOther) { return ext.Compare(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);
+ });
+
+ return RequestOrReason(origChannel);
+ });
+ } 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, dev builds don't use symlinks so we never need to
+ // allow a resource from outside of the extension dir.
+ return false;
+#else
+ if (!mozilla::IsDevelopmentBuild()) {
+ 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::IsDevelopmentBuild());
+ 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::IsDevelopmentBuild());
+ 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"_ns);
+ }
+
+ 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 {
+ MOZ_TRY(getter->GetAsync(listener, simpleChannel));
+ return RequestOrReason(nullptr);
+ });
+
+ 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);
+ }
+ return RequestOrReason(origChannel);
+ });
+
+ 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..ec253702e9
--- /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;
+#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;
+#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..02ee4e67cf
--- /dev/null
+++ b/netwerk/protocol/res/PageThumbProtocolHandler.cpp
@@ -0,0 +1,468 @@
+/* -*- 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 "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"
+
+#define PAGE_THUMB_HOST "thumbnails"
+#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;
+
+/**
+ * Helper class used with SimpleChannel to asynchronously obtain an input
+ * stream from the parent for a remote moz-page-thumb load from the child.
+ */
+class PageThumbStreamGetter : public RefCounted<PageThumbStreamGetter> {
+ public:
+ PageThumbStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo)
+ : mURI(aURI), mLoadInfo(aLoadInfo) {
+ MOZ_ASSERT(aURI);
+ MOZ_ASSERT(aLoadInfo);
+
+ SetupEventTarget();
+ }
+
+ ~PageThumbStreamGetter() = default;
+
+ void SetupEventTarget() {
+ mMainThreadEventTarget = nsContentUtils::GetEventTargetByLoadInfo(
+ mLoadInfo, TaskCategory::Other);
+ if (!mMainThreadEventTarget) {
+ mMainThreadEventTarget = GetMainThreadSerialEventTarget();
+ }
+ }
+
+ // Get an input stream from the parent asynchronously.
+ Result<Ok, nsresult> GetAsync(nsIStreamListener* aListener,
+ nsIChannel* aChannel);
+
+ // Handle an input stream being returned from the parent
+ void OnStream(already_AddRefed<nsIInputStream> aStream);
+
+ static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel,
+ nsresult aResult);
+
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(PageThumbStreamGetter)
+
+ private:
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIChannel> mChannel;
+ nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
+};
+
+// Request an input stream from the parent.
+Result<Ok, nsresult> PageThumbStreamGetter::GetAsync(
+ nsIStreamListener* aListener, nsIChannel* aChannel) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mMainThreadEventTarget);
+
+ mListener = aListener;
+ mChannel = aChannel;
+
+ RefPtr<PageThumbStreamGetter> self = this;
+
+ // Request an input stream for this moz-page-thumb URI.
+ gNeckoChild->SendGetPageThumbStream(mURI)->Then(
+ mMainThreadEventTarget, __func__,
+ [self](const RefPtr<nsIInputStream>& stream) {
+ self->OnStream(do_AddRef(stream));
+ },
+ [self](const mozilla::ipc::ResponseRejectReason) {
+ self->OnStream(nullptr);
+ });
+ return Ok();
+}
+
+// static
+void PageThumbStreamGetter::CancelRequest(nsIStreamListener* aListener,
+ nsIChannel* aChannel,
+ nsresult aResult) {
+ MOZ_ASSERT(aListener);
+ MOZ_ASSERT(aChannel);
+
+ aListener->OnStartRequest(aChannel);
+ aListener->OnStopRequest(aChannel, aResult);
+ aChannel->Cancel(NS_BINDING_ABORTED);
+}
+
+// Handle an input stream sent from the parent.
+void PageThumbStreamGetter::OnStream(already_AddRefed<nsIInputStream> aStream) {
+ MOZ_ASSERT(IsNeckoChild());
+ MOZ_ASSERT(mListener);
+ MOZ_ASSERT(mMainThreadEventTarget);
+
+ nsCOMPtr<nsIInputStream> stream = std::move(aStream);
+
+ // We must keep an owning reference to the listener until we pass it on
+ // to AsyncRead.
+ nsCOMPtr<nsIStreamListener> listener = mListener.forget();
+
+ MOZ_ASSERT(mChannel);
+
+ if (!stream) {
+ // The parent didn't send us back a stream.
+ CancelRequest(listener, mChannel, 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, mChannel, rv);
+ return;
+ }
+
+ rv = pump->AsyncRead(listener);
+ if (NS_FAILED(rv)) {
+ CancelRequest(listener, mChannel, rv);
+ }
+}
+
+NS_IMPL_QUERY_INTERFACE(PageThumbProtocolHandler,
+ nsISubstitutingProtocolHandler, nsIProtocolHandler,
+ nsIProtocolHandlerWithDynamicFlags,
+ 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);
+}
+
+PageThumbProtocolHandler::PageThumbProtocolHandler()
+ : SubstitutingProtocolHandler(PAGE_THUMB_SCHEME) {}
+
+nsresult PageThumbProtocolHandler::GetFlagsForURI(nsIURI* aURI,
+ uint32_t* aFlags) {
+ // 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.
+ *aFlags = URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE |
+ URI_NORELATIVE | URI_NOAUTH;
+
+ return NS_OK;
+}
+
+RefPtr<PageThumbStreamPromise> PageThumbProtocolHandler::NewStream(
+ nsIURI* aChildURI, bool* aTerminateSender) {
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aChildURI || !aTerminateSender) {
+ return PageThumbStreamPromise::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 PageThumbStreamPromise::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)) {
+ return PageThumbStreamPromise::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 PageThumbStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ nsAutoCString resolvedScheme;
+ rv = net_ExtractURLScheme(resolvedSpec, resolvedScheme);
+ if (NS_FAILED(rv) || !resolvedScheme.EqualsLiteral("file")) {
+ return PageThumbStreamPromise::CreateAndReject(NS_ERROR_UNEXPECTED,
+ __func__);
+ }
+
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) {
+ return PageThumbStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ nsCOMPtr<nsIURI> resolvedURI;
+ rv = ioService->NewURI(resolvedSpec, nullptr, nullptr,
+ getter_AddRefs(resolvedURI));
+ if (NS_FAILED(rv)) {
+ return PageThumbStreamPromise::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 PageThumbStreamPromise::CreateAndReject(rv, __func__);
+ }
+
+ auto promiseHolder = MakeUnique<MozPromiseHolder<PageThumbStreamPromise>>();
+ RefPtr<PageThumbStreamPromise> promise = promiseHolder->Ensure(__func__);
+
+ rv = NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction(
+ "PageThumbProtocolHandler::NewStream",
+ [channel, holder = std::move(promiseHolder)]() {
+ nsresult rv;
+
+ nsCOMPtr<nsIFileChannel> fileChannel =
+ do_QueryInterface(channel, &rv);
+ if (NS_FAILED(rv)) {
+ holder->Reject(rv, __func__);
+ }
+
+ 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;
+ }
+
+ holder->Resolve(inputStream, __func__);
+ }),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+
+ if (NS_FAILED(rv)) {
+ return PageThumbStreamPromise::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)) {
+ // 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, 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<PageThumbStreamGetter> streamGetter =
+ new PageThumbStreamGetter(aURI, aLoadInfo);
+
+ NewSimpleChannel(aURI, aLoadInfo, streamGetter, aRetVal);
+ return Ok();
+}
+
+nsresult PageThumbProtocolHandler::GetThumbnailPath(const nsACString& aPath,
+ 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;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIPageThumbsStorageService> pageThumbsStorage =
+ do_GetService("@mozilla.org/thumbnails/pagethumbs-service;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // 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;
+ }
+
+ // Use PageThumbsStorageService to get the local file path of the screenshot
+ // for the given URL.
+ rv = pageThumbsStorage->GetFilePathForURL(url, aThumbnailPath);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// static
+void PageThumbProtocolHandler::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);
+ }
+ }
+}
+
+// static
+void PageThumbProtocolHandler::NewSimpleChannel(
+ nsIURI* aURI, nsILoadInfo* aLoadinfo, PageThumbStreamGetter* aStreamGetter,
+ nsIChannel** aRetVal) {
+ nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
+ aURI, aLoadinfo, aStreamGetter,
+ [](nsIStreamListener* listener, nsIChannel* simpleChannel,
+ PageThumbStreamGetter* getter) -> RequestOrReason {
+ MOZ_TRY(getter->GetAsync(listener, simpleChannel));
+ return RequestOrReason(nullptr);
+ });
+
+ SetContentType(aURI, channel);
+ 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..8c5c90acff
--- /dev/null
+++ b/netwerk/protocol/res/PageThumbProtocolHandler.h
@@ -0,0 +1,123 @@
+/* -*- 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 "SubstitutingProtocolHandler.h"
+
+namespace mozilla {
+namespace net {
+
+using PageThumbStreamPromise =
+ mozilla::MozPromise<nsCOMPtr<nsIInputStream>, nsresult, false>;
+
+class PageThumbStreamGetter;
+
+class PageThumbProtocolHandler 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<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 PageThumbStreamPromise
+ * The PageThumbStreamPromise will resolve with an nsIInputStream on
+ * success, and reject with an nsresult on failure.
+ */
+ RefPtr<PageThumbStreamPromise> 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 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, 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;
+
+ // 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 channel.
+ static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo,
+ PageThumbStreamGetter* aStreamGetter,
+ nsIChannel** aRetVal);
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif /* PageThumbProtocolHandler_h___ */
diff --git a/netwerk/protocol/res/SubstitutingJARURI.h b/netwerk/protocol/res/SubstitutingJARURI.h
new file mode 100644
index 0000000000..fe9208750c
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingJARURI.h
@@ -0,0 +1,161 @@
+/* -*- 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 "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 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 {
+ return !mSource ? NS_ERROR_NULL_POINTER : mSource->Mutate(_retval);
+ }
+ NS_IMETHOD_(void) Serialize(mozilla::ipc::URIParams& aParams) override {
+ MOZ_ASSERT(mSource);
+ mSource->Serialize(aParams);
+ }
+};
+
+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..baf368761b
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -0,0 +1,653 @@
+/* -*- 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 "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(kSubstitutingURLCID, NS_SUBSTITUTINGURL_CID);
+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);
+ MOZ_ASSERT(substHandler);
+
+ 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);
+}
+
+// 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;
+}
+
+NS_IMPL_CLASSINFO(SubstitutingJARURI, nullptr, nsIClassInfo::MAIN_THREAD_ONLY,
+ 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)
+
+SubstitutingProtocolHandler::SubstitutingProtocolHandler(const char* aScheme,
+ uint32_t aFlags,
+ bool aEnforceFileOrJar)
+ : mScheme(aScheme),
+ mSubstitutionsLock("SubstitutingProtocolHandler::mSubstitutions"),
+ mSubstitutions(16),
+ mEnforceFileOrJar(aEnforceFileOrJar) {
+ mFlags.emplace(aFlags);
+ ConstructInternal();
+}
+
+SubstitutingProtocolHandler::SubstitutingProtocolHandler(const char* aScheme)
+ : mScheme(aScheme),
+ mSubstitutionsLock("SubstitutingProtocolHandler::mSubstitutions"),
+ mSubstitutions(16),
+ mEnforceFileOrJar(true) {
+ 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 (auto iter = mSubstitutions.ConstIter(); !iter.Done(); iter.Next()) {
+ SubstitutionEntry& entry = iter.Data();
+ nsCOMPtr<nsIURI> uri = entry.baseURI;
+ SerializedURI serialized;
+ if (uri) {
+ nsresult rv = uri->GetSpec(serialized.spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ SubstitutionMapping substitution = {mScheme, nsCString(iter.Key()),
+ 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::GetDefaultPort(int32_t* result) {
+ *result = -1;
+ return NS_OK;
+}
+
+nsresult SubstitutingProtocolHandler::GetProtocolFlags(uint32_t* result) {
+ if (mFlags.isNothing()) {
+ NS_WARNING(
+ "Trying to get protocol flags the wrong way - use "
+ "nsIProtocolHandlerWithDynamicFlags instead");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *result = mFlags.ref();
+ 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(NS_MutatorMethod(&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);
+ SubstitutionEntry& entry = mSubstitutions.GetOrInsert(root);
+ entry.baseURI = baseURI;
+ entry.flags = 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);
+ SubstitutionEntry& entry = mSubstitutions.GetOrInsert(root);
+ entry.baseURI = newBaseURI;
+ entry.flags = 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..57bce8c7fe
--- /dev/null
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.h
@@ -0,0 +1,134 @@
+/* -*- 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 "nsDataHashtable.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:
+ SubstitutingProtocolHandler(const char* aScheme, uint32_t aFlags,
+ bool aEnforceFileOrJar = true);
+ explicit SubstitutingProtocolHandler(const char* aScheme);
+
+ 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>& aResources);
+
+ 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 {
+ SubstitutionEntry() : flags(0) {}
+
+ ~SubstitutionEntry() = default;
+
+ nsCOMPtr<nsIURI> baseURI;
+ uint32_t flags;
+ };
+
+ // 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;
+ Maybe<uint32_t> mFlags;
+
+ RWLock mSubstitutionsLock;
+ nsDataHashtable<nsCStringHashKey, SubstitutionEntry> mSubstitutions;
+ 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..04bdfeff79
--- /dev/null
+++ b/netwerk/protocol/res/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/.
+
+XPIDL_SOURCES += [
+ "nsIResProtocolHandler.idl",
+ "nsISubstitutingProtocolHandler.idl",
+]
+
+XPIDL_MODULE = "necko_res"
+
+EXPORTS.mozilla.net += [
+ "ExtensionProtocolHandler.h",
+ "PageThumbProtocolHandler.h",
+ "SubstitutingJARURI.h",
+ "SubstitutingProtocolHandler.h",
+ "SubstitutingURL.h",
+]
+
+EXPORTS += [
+ "nsResProtocolHandler.h",
+]
+
+UNIFIED_SOURCES += [
+ "ExtensionProtocolHandler.cpp",
+ "nsResProtocolHandler.cpp",
+ "PageThumbProtocolHandler.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..6918799e35
--- /dev/null
+++ b/netwerk/protocol/res/nsResProtocolHandler.cpp
@@ -0,0 +1,194 @@
+/* -*- 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/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..2d672dfad6
--- /dev/null
+++ b/netwerk/protocol/res/nsResProtocolHandler.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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",
+ URI_STD | URI_IS_UI_RESOURCE | URI_IS_LOCAL_RESOURCE |
+ URI_IS_POTENTIALLY_TRUSTWORTHY,
+ /* 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..3fb4b8c70e
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsIViewSourceChannel.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 "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..cce3de1b36
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp
@@ -0,0 +1,1175 @@
+/* -*- 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 "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(nsIApplicationCacheChannel,
+ mApplicationCacheChannel)
+ 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
+
+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;
+ }
+
+ 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);
+ mApplicationCacheChannel = 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::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) ? true : false;
+
+ 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 = 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(nsISupports** 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 = mBaseURI;
+ NS_IF_ADDREF(*aBaseURI);
+ 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::GetTopLevelOuterContentWindowId(uint64_t* aWindowId) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetTopLevelOuterContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetTopLevelOuterContentWindowId(uint64_t aWindowId) {
+ return !mHttpChannel
+ ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetTopLevelOuterContentWindowId(aWindowId);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::GetFlashPluginState(
+ nsIHttpChannel::FlashPluginState* aResult) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetFlashPluginState(aResult);
+}
+
+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::GetAllowPipelining(bool* aAllowPipelining) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->GetAllowPipelining(aAllowPipelining);
+}
+
+NS_IMETHODIMP
+nsViewSourceChannel::SetAllowPipelining(bool aAllowPipelining) {
+ return !mHttpChannel ? NS_ERROR_NULL_POINTER
+ : mHttpChannel->SetAllowPipelining(aAllowPipelining);
+}
+
+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);
+}
+
+// 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) {
+ if (!mHttpChannel) {
+ NS_WARNING(
+ "nsViewSourceChannel::LogBlockedCORSRequest mHttpChannel is null");
+ return NS_ERROR_UNEXPECTED;
+ }
+ return mHttpChannel->LogBlockedCORSRequest(aMessage, aCategory);
+}
+
+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);
+}
+
+const nsTArray<mozilla::net::PreferredAlternativeDataTypeParams>&
+nsViewSourceChannel::PreferredAlternativeDataTypes() {
+ if (mCacheInfoChannel) {
+ return mCacheInfoChannel->PreferredAlternativeDataTypes();
+ }
+ return mEmptyArray;
+}
+
+void nsViewSourceChannel::SetIPv4Disabled() {
+ if (mHttpChannelInternal) {
+ mHttpChannelInternal->SetIPv4Disabled();
+ }
+}
+
+void nsViewSourceChannel::SetIPv6Disabled() {
+ if (mHttpChannelInternal) {
+ mHttpChannelInternal->SetIPv6Disabled();
+ }
+}
+
+bool nsViewSourceChannel::GetHasNonEmptySandboxingFlag() {
+ if (mHttpChannelInternal) {
+ return mHttpChannelInternal->GetHasNonEmptySandboxingFlag();
+ }
+ return false;
+}
+
+void nsViewSourceChannel::SetHasNonEmptySandboxingFlag(
+ bool aHasNonEmptySandboxingFlag) {
+ if (mHttpChannelInternal) {
+ mHttpChannelInternal->SetHasNonEmptySandboxingFlag(
+ aHasNonEmptySandboxingFlag);
+ }
+}
+
+void nsViewSourceChannel::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() {
+ if (mHttpChannelInternal) {
+ mHttpChannelInternal->DoDiagnosticAssertWhenOnStopNotCalledOnDestroy();
+ }
+}
+
+// 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);
+
+ 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..66704d1fa8
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceChannel.h
@@ -0,0 +1,104 @@
+/* -*- 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 "nsIApplicationCacheChannel.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 nsIApplicationCacheChannel,
+ 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_NSIAPPLICATIONCACHECHANNEL(mApplicationCacheChannel)
+ NS_FORWARD_SAFE_NSIAPPLICATIONCACHECONTAINER(mApplicationCacheChannel)
+ NS_FORWARD_SAFE_NSIUPLOADCHANNEL(mUploadChannel)
+ NS_FORWARD_SAFE_NSIFORMPOSTACTIONCHANNEL(mPostChannel)
+ NS_FORWARD_SAFE_NSIHTTPCHANNELINTERNAL(mHttpChannelInternal)
+
+ // nsViewSourceChannel methods:
+ nsViewSourceChannel()
+ : mIsDocument(false), mOpened(false), mIsSrcdocChannel(false) {}
+
+ [[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<nsIApplicationCacheChannel> mApplicationCacheChannel;
+ nsCOMPtr<nsIUploadChannel> mUploadChannel;
+ nsCOMPtr<nsIFormPOSTActionChannel> mPostChannel;
+ nsCOMPtr<nsIChildChannel> mChildChannel;
+ nsCOMPtr<nsIStreamListener> mListener;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mBaseURI;
+ nsCString mContentType;
+ bool mIsDocument; // keeps track of the LOAD_DOCUMENT_URI flag
+ bool mOpened;
+ bool mIsSrcdocChannel;
+};
+
+#endif /* nsViewSourceChannel_h___ */
diff --git a/netwerk/protocol/viewsource/nsViewSourceHandler.cpp b/netwerk/protocol/viewsource/nsViewSourceHandler.cpp
new file mode 100644
index 0000000000..3ee94f058e
--- /dev/null
+++ b/netwerk/protocol/viewsource/nsViewSourceHandler.cpp
@@ -0,0 +1,153 @@
+/* -*- 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::GetDefaultPort(int32_t* result) {
+ *result = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsViewSourceHandler::GetProtocolFlags(uint32_t* result) {
+ *result = DEFAULT_FLAGS;
+ 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(NS_MutatorMethod(&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..af3037e270
--- /dev/null
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.cpp
@@ -0,0 +1,367 @@
+/* -*- 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::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));
+ NS_IF_ADDREF(*aNotificationCallbacks = mCallbacks);
+ 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));
+ NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
+ 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) {
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ 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::GetDefaultPort(int32_t* aDefaultPort) {
+ LOG(("BaseWebSocketChannel::GetDefaultPort() %p\n", this));
+
+ if (mEncrypted)
+ *aDefaultPort = kDefaultWSSPort;
+ else
+ *aDefaultPort = kDefaultWSPort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetProtocolFlags(uint32_t* aProtocolFlags) {
+ LOG(("BaseWebSocketChannel::GetProtocolFlags() %p\n", this));
+
+ *aProtocolFlags = URI_NORELATIVE | URI_NON_PERSISTABLE | ALLOWS_PROXY |
+ ALLOWS_PROXY_HTTP | URI_DOES_NOT_RETURN_DATA |
+ URI_DANGEROUS_TO_LOAD;
+ if (mEncrypted) {
+ *aProtocolFlags |= URI_IS_POTENTIALLY_TRUSTWORTHY;
+ }
+ 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(nsIEventTarget* aTargetThread) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTargetThread);
+ MOZ_ASSERT(!mTargetThread,
+ "Delivery target should be set once, before AsyncOpen");
+ MOZ_ASSERT(!mWasOpened, "Should not be called after AsyncOpen!");
+
+ mTargetThread = aTargetThread;
+ MOZ_ASSERT(mTargetThread);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BaseWebSocketChannel::GetDeliveryTarget(nsIEventTarget** aTargetThread) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIEventTarget> target = mTargetThread;
+ if (!target) {
+ target = GetCurrentEventTarget();
+ }
+ target.forget(aTargetThread);
+ return NS_OK;
+}
+
+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..469b939e5d
--- /dev/null
+++ b/netwerk/protocol/websocket/BaseWebSocketChannel.h
@@ -0,0 +1,127 @@
+/* -*- 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 "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;
+
+ class ListenerAndContextContainer final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ListenerAndContextContainer)
+
+ ListenerAndContextContainer(nsIWebSocketListener* aListener,
+ nsISupports* aContext);
+
+ nsCOMPtr<nsIWebSocketListener> mListener;
+ nsCOMPtr<nsISupports> mContext;
+
+ private:
+ ~ListenerAndContextContainer();
+ };
+
+ protected:
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mURI;
+ RefPtr<ListenerAndContextContainer> mListenerMT;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+ nsCOMPtr<nsIEventTarget> mTargetThread;
+ nsCOMPtr<nsITransportProvider> mServerTransportProvider;
+
+ 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;
+
+ 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..a209769f97
--- /dev/null
+++ b/netwerk/protocol/websocket/IPCTransportProvider.cpp
@@ -0,0 +1,82 @@
+/* -*- 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 "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; }
+
+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..e4c8fe218a
--- /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..329a381b6b
--- /dev/null
+++ b/netwerk/protocol/websocket/PTransportProvider.ipdl
@@ -0,0 +1,27 @@
+/* -*- 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;
+
+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.
+ */
+
+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..f3d0eebe82
--- /dev/null
+++ b/netwerk/protocol/websocket/PWebSocket.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 PBrowser;
+include protocol PTransportProvider;
+include IPCStream;
+include NeckoChannelParams;
+
+include protocol PFileDescriptorSet; //FIXME: bug #792908
+include protocol PChildToParentStream; //FIXME: bug #792908
+include protocol PParentToChildStream; //FIXME: bug #792908
+
+using class IPC::SerializedLoadContext from "SerializedLoadContext.h";
+using refcounted class nsIURI from "mozilla/ipc/URIUtils.h";
+
+namespace mozilla {
+namespace net {
+
+async protocol PWebSocket
+{
+ manager PNecko;
+
+parent:
+ // Forwarded methods corresponding to methods on nsIWebSocketChannel
+ async AsyncOpen(nsIURI aURI,
+ nsCString aOrigin,
+ 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(nsDependentCSubstring aMsg, bool aMoreData);
+ async OnBinaryMessageAvailable(nsDependentCSubstring 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/PWebSocketEventListener.ipdl b/netwerk/protocol/websocket/PWebSocketEventListener.ipdl
new file mode 100644
index 0000000000..c29b99d757
--- /dev/null
+++ b/netwerk/protocol/websocket/PWebSocketEventListener.ipdl
@@ -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 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 {
+
+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..0193e4903e
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -0,0 +1,4023 @@
+/* -*- 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 "WebSocketFrame.h"
+#include "WebSocketLog.h"
+#include "WebSocketChannel.h"
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Utf8.h"
+#include "mozilla/net/WebSocketEventService.h"
+
+#include "nsIURI.h"
+#include "nsIChannel.h"
+#include "nsICryptoHash.h"
+#include "nsIRunnable.h"
+#include "nsIPrefBranch.h"
+#include "nsICancelable.h"
+#include "nsIClassOfService.h"
+#include "nsIDNSRecord.h"
+#include "nsIDNSService.h"
+#include "nsIIOService.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIProxyInfo.h"
+#include "nsIProxiedChannel.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIDashboardEventNotifier.h"
+#include "nsIEventTarget.h"
+#include "nsIHttpChannel.h"
+#include "nsIProtocolHandler.h"
+#include "nsIRandomGenerator.h"
+#include "nsISocketTransport.h"
+#include "nsThreadUtils.h"
+#include "nsINetworkLinkService.h"
+#include "nsIObserverService.h"
+#include "nsCharSeparatedTokenizer.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCRT.h"
+#include "nsThreadUtils.h"
+#include "nsError.h"
+#include "mozilla/Base64.h"
+#include "nsStringStream.h"
+#include "nsAlgorithm.h"
+#include "nsProxyRelease.h"
+#include "nsNetUtil.h"
+#include "nsINode.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "nsSocketTransportService2.h"
+#include "nsINSSErrorsService.h"
+
+#include "plbase64.h"
+#include "prmem.h"
+#include "prnetdb.h"
+#include "zlib.h"
+#include <algorithm>
+
+// 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 {
+namespace 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/port.
+class FailDelay {
+ public:
+ FailDelay(nsCString address, int32_t port)
+ : mAddress(std::move(address)), 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, port=%d: incremented delay to "
+ "%" PRIu32,
+ mAddress.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)
+ 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, int32_t port) {
+ if (mDelaysDisabled) return;
+
+ UniquePtr<FailDelay> record(new FailDelay(address, 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, 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->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;
+ } else 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->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;
+ 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, 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->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 found = (sManager->IndexOf(ws->mAddress) >= 0);
+
+ // Always add ourselves to queue, even if we'll connect immediately
+ UniquePtr<nsOpenConn> newdata(new nsOpenConn(ws->mAddress, ws));
+ sManager->mQueue.AppendElement(std::move(newdata));
+
+ if (found) {
+ 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->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);
+ }
+
+ // 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->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, %d completed"
+ " [this=%p]",
+ aChannel->mAddress.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, %d failed: [this=%p]",
+ aChannel->mAddress.get(), (int)aChannel->mPort, aChannel));
+ sManager->mFailures.Add(aChannel->mAddress, aChannel->mPort);
+ }
+ }
+
+ if (aChannel->mConnecting) {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ // 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.
+ MOZ_ASSERT(
+ NS_FAILED(aReason) || aChannel->mScriptCloseCode == CLOSE_GOING_AWAY,
+ "websocket closed while connecting w/o failing?");
+
+ 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);
+ }
+ }
+ }
+
+ 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, WebSocketChannel* channel)
+ : mAddress(addr), mChannel(channel) {
+ MOZ_COUNT_CTOR(nsOpenConn);
+ }
+ MOZ_COUNTED_DTOR(nsOpenConn)
+
+ nsCString mAddress;
+ WebSocketChannel* mChannel;
+ };
+
+ void ConnectNext(nsCString& hostName) {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ int32_t index = IndexOf(hostName);
+ 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& aStr) {
+ for (uint32_t i = 0; i < mQueue.Length(); i++)
+ if (aStr == (mQueue[i])->mAddress) 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;
+ }
+
+ // 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;
+ 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];
+};
+
+//-----------------------------------------------------------------------------
+// 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=%d, "
+ "original=%d]",
+ 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),
+ mAutoFollowRedirects(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),
+ 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::mLoadGroup", mLoadGroup.forget());
+ NS_ReleaseOnMainThread("WebSocketChannel::mLoadInfo", mLoadInfo.forget());
+ 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 (!mSocketThread) {
+ // there has not been an asyncopen yet on the object and then we need
+ // no ping.
+ LOG(("WebSocket: early object, no ping needed"));
+ } else {
+ // Next we check mDataStarted, which we need to do on mTargetThread.
+ if (!IsOnTargetThread()) {
+ mTargetThread->Dispatch(
+ NewRunnableMethod("net::WebSocketChannel::OnNetworkChanged", this,
+ &WebSocketChannel::OnNetworkChanged),
+ NS_DISPATCH_NORMAL);
+ } else {
+ nsresult rv = OnNetworkChanged();
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocket: OnNetworkChanged failed (%08" PRIx32 ")",
+ static_cast<uint32_t>(rv)));
+ }
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult WebSocketChannel::OnNetworkChanged() {
+ if (IsOnTargetThread()) {
+ LOG(("WebSocketChannel::OnNetworkChanged() - on target thread %p", this));
+
+ if (!mDataStarted) {
+ LOG(("WebSocket: data not started yet, no ping needed"));
+ return NS_OK;
+ }
+
+ return mSocketThread->Dispatch(
+ NewRunnableMethod("net::WebSocketChannel::OnNetworkChanged", this,
+ &WebSocketChannel::OnNetworkChanged),
+ NS_DISPATCH_NORMAL);
+ }
+
+ MOZ_ASSERT(OnSocketThread(), "not on socket 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(); }
+
+bool WebSocketChannel::IsOnTargetThread() {
+ MOZ_ASSERT(mTargetThread);
+ bool isOnTargetThread = false;
+ nsresult rv = mTargetThread->IsOnCurrentThread(&isOnTargetThread);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_FAILED(rv) ? false : isOnTargetThread;
+}
+
+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();
+ }
+}
+
+void WebSocketChannel::BeginOpenInternal() {
+ LOG(("WebSocketChannel::BeginOpenInternal() %p\n", this));
+
+ 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 = NS_MaybeOpenChannelUsingAsyncOpen(localChannel, this);
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebSocketChannel::BeginOpenInternal: cannot async open\n"));
+ AbortSession(NS_ERROR_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(OnSocketThread(), "not on socket 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
+ 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) {
+ bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated();
+ LOG(("WebSocketChannel:: %stext frame received\n",
+ isDeflated ? "deflated " : ""));
+
+ if (mListenerMT) {
+ nsCString utf8Data;
+
+ if (isDeflated) {
+ rv = mPMCECompressor->Inflate(payload, payloadLength, utf8Data);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ LOG(
+ ("WebSocketChannel:: message successfully inflated "
+ "[origLength=%d, newLength=%d]\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());
+ }
+
+ mTargetThread->Dispatch(new CallOnMessageAvailable(this, utf8Data, -1),
+ NS_DISPATCH_NORMAL);
+ 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) {
+ mTargetThread->Dispatch(
+ new CallOnServerClose(this, mServerCloseCode, mServerCloseReason),
+ NS_DISPATCH_NORMAL);
+ }
+
+ 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) {
+ bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated();
+ LOG(("WebSocketChannel:: %sbinary frame received\n",
+ isDeflated ? "deflated " : ""));
+
+ if (mListenerMT) {
+ nsCString binaryData;
+
+ if (isDeflated) {
+ rv = mPMCECompressor->Inflate(payload, payloadLength, binaryData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ LOG(
+ ("WebSocketChannel:: message successfully inflated "
+ "[origLength=%d, newLength=%d]\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());
+ }
+
+ mTargetThread->Dispatch(
+ new CallOnMessageAvailable(this, binaryData, binaryData.Length()),
+ NS_DISPATCH_NORMAL);
+ // 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(OnSocketThread(), "not on socket thread");
+
+ LOG(
+ ("WebSocketChannel::EnqueueOutgoingMessage %p "
+ "queueing msg %p [type=%s len=%d]\n",
+ this, aMsg, msgNames[aMsg->GetMsgType()], aMsg->Length()));
+
+ aQueue.Push(aMsg);
+ OnOutputStreamReady(mSocketOut);
+}
+
+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(OnSocketThread(), "not on socket 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)) {
+ 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
+ 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 {
+ uint8_t* buffer;
+ static_assert(4 == sizeof(mask), "Size of the mask should be equal to 4");
+ nsresult rv =
+ mRandomGenerator->GenerateRandomBytes(sizeof(mask), &buffer);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::PrimeNewOutgoingMessage(): "
+ "GenerateRandomBytes failure %" PRIx32 "\n",
+ static_cast<uint32_t>(rv)));
+ AbortSession(rv);
+ return;
+ }
+ memcpy(&mask, buffer, sizeof(mask));
+ free(buffer);
+ } 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() {
+ LOG(("WebSocketChannel::CleanupConnection() %p", this));
+
+ 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 (mConnectionLogService && !mPrivateBrowsing) {
+ mConnectionLogService->RemoveHost(mHost, mSerial);
+ }
+
+ // This method can run in any thread, but 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
+
+ MOZ_ASSERT(mStopped);
+ MOZ_ASSERT(OnSocketThread() || 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;
+ }
+
+ if (mOpenTimer) {
+ mOpenTimer->Cancel();
+ mOpenTimer = nullptr;
+ }
+
+ if (mReconnectDelayTimer) {
+ mReconnectDelayTimer->Cancel();
+ mReconnectDelayTimer = nullptr;
+ }
+
+ if (mPingTimer) {
+ mPingTimer->Cancel();
+ mPingTimer = nullptr;
+ }
+
+ if (mSocketIn && !mTCPClosed && mDataStarted) {
+ // 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);
+ }
+
+ int32_t sessionCount = kLingeringCloseThreshold;
+ nsWSAdmissionManager::GetSessionCount(sessionCount);
+
+ if (!mTCPClosed && mTransport && 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();
+ }
+
+ if (mCancelable) {
+ mCancelable->Cancel(NS_ERROR_UNEXPECTED);
+ mCancelable = nullptr;
+ }
+
+ mPMCECompressor = nullptr;
+
+ if (!mCalledOnStop) {
+ mCalledOnStop = true;
+
+ nsWSAdmissionManager::OnStopSession(this, reason);
+
+ RefPtr<CallOnStop> runnable = new CallOnStop(this, reason);
+ mTargetThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ }
+}
+
+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
+
+ // 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 && reason != NS_BASE_STREAM_CLOSED && !mRequestedClose &&
+ !mClientClosed && !mServerClosed && mDataStarted) {
+ mRequestedClose = true;
+ mStopOnClose = reason;
+ mSocketThread->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(OnSocketThread(), "not on socket 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;
+ }
+
+ 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;
+ 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 = GetMainThreadEventTarget();
+ MOZ_ASSERT(!mCancelable);
+ return dns->AsyncResolveNative(
+ hostName, nsIDNSService::RESOLVE_TYPE_DEFAULT, 0, nullptr, this, main,
+ mLoadInfo->GetOriginAttributes(), getter_AddRefs(mCancelable));
+}
+
+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();
+ }
+
+ MOZ_ASSERT(!mCancelable);
+
+ nsresult rv;
+ rv = pps->AsyncResolve(
+ mHttpChannel,
+ nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY |
+ nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
+ nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
+ this, nullptr, getter_AddRefs(mCancelable));
+ NS_ASSERTION(NS_FAILED(rv) || mCancelable,
+ "nsIProtocolProxyService::AsyncResolve succeeded but didn't "
+ "return a cancelable object!");
+ 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;
+ }
+
+ if (!IsOnTargetThread()) {
+ return mTargetThread->Dispatch(
+ NewRunnableMethod("net::WebSocketChannel::StartWebsocketData", this,
+ &WebSocketChannel::StartWebsocketData),
+ NS_DISPATCH_NORMAL);
+ }
+
+ return StartWebsocketData();
+}
+
+nsresult WebSocketChannel::StartWebsocketData() {
+ nsresult rv;
+
+ {
+ 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;
+ }
+
+ mDataStarted = true;
+ }
+
+ rv = mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::StartWebsocketData mSocketIn->AsyncWait() failed "
+ "with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ return mSocketThread->Dispatch(
+ NewRunnableMethod<nsresult>("net::WebSocketChannel::AbortSession", this,
+ &WebSocketChannel::AbortSession, rv),
+ NS_DISPATCH_NORMAL);
+ }
+
+ if (mPingInterval) {
+ rv = mSocketThread->Dispatch(
+ NewRunnableMethod("net::WebSocketChannel::StartPinging", this,
+ &WebSocketChannel::StartPinging),
+ NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::StartWebsocketData Could not start pinging, "
+ "rv=0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ }
+
+ LOG(("WebSocketChannel::StartWebsocketData Notifying Listener %p",
+ mListenerMT ? mListenerMT->mListener.get() : nullptr));
+
+ if (mListenerMT) {
+ rv = mListenerMT->mListener->OnStart(mListenerMT->mContext);
+ if (NS_FAILED(rv)) {
+ LOG(
+ ("WebSocketChannel::StartWebsocketData "
+ "mListenerMT->mListener->OnStart() failed with error 0x%08" PRIx32,
+ static_cast<uint32_t>(rv)));
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult WebSocketChannel::StartPinging() {
+ LOG(("WebSocketChannel::StartPinging() %p", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket 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",
+ 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");
+
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnLookupComplete: Request Already Stopped\n"));
+ mCancelable = nullptr;
+ return NS_OK;
+ }
+
+ mCancelable = nullptr;
+
+ // 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) {
+ if (mStopped) {
+ LOG(("WebSocketChannel::OnProxyAvailable: [%p] Request Already Stopped\n",
+ this));
+ mCancelable = nullptr;
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(!mCancelable || (aRequest == mCancelable));
+ mCancelable = nullptr;
+
+ 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);
+ }
+ }
+
+ 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");
+
+ if (!mAutoFollowRedirects) {
+ // Even if redirects configured off, still allow them 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(OnSocketThread(), "not on socket 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);
+ } 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);
+ } else if (timer == mReconnectDelayTimer) {
+ MOZ_ASSERT(mConnecting == CONNECTING_DELAYED,
+ "woke up from delay w/o being delayed?");
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ mReconnectDelayTimer = nullptr;
+ LOG(("WebSocketChannel: connecting [this=%p] after reconnect delay", this));
+ BeginOpen(false);
+ } else if (timer == mPingTimer) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket 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);
+ }
+ } 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(nsISupports** aSecurityInfo) {
+ LOG(("WebSocketChannel::GetSecurityInfo() %p\n", this));
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ if (mTransport) {
+ if (NS_FAILED(mTransport->GetSecurityInfo(aSecurityInfo)))
+ *aSecurityInfo = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannel::AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
+ uint64_t aInnerWindowID,
+ nsIWebSocketListener* aListener,
+ nsISupports* aContext) {
+ LOG(("WebSocketChannel::AsyncOpen() %p\n", this));
+
+ 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 (!mTargetThread) {
+ mTargetThread = GetMainThreadEventTarget();
+ }
+
+ mSocketThread = 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->GetBoolPref(
+ "network.websocket.auto-follow-http-redirects", &boolpref);
+ if (NS_SUCCEEDED(rv)) {
+ mAutoFollowRedirects = 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 mSocketThread->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=%d\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 mSocketThread->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;
+
+ 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;
+
+ 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;
+ }
+
+ 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) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket 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_CONNECTION_REFUSED);
+ return NS_ERROR_CONNECTION_REFUSED;
+ }
+
+ nsresult rv;
+ uint32_t status;
+ char *val, *token;
+
+ rv = mHttpChannel->GetResponseStatus(&status);
+ if (NS_FAILED(rv)) {
+ nsresult httpStatus;
+ rv = NS_ERROR_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_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_CONNECTION_REFUSED);
+ return NS_ERROR_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 (PL_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 (PL_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 (PL_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_ASSERT(OnSocketThread(), "not on socket 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, 2048, &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, mSocketThread);
+ 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_ASSERT(OnSocketThread(), "not on socket 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: "
+ "Try to send %u of data\n",
+ toSend));
+ }
+ }
+
+ if (toSend == 0) {
+ amtSent = 0;
+ } else {
+ rv = mSocketOut->Write(sndBuf, toSend, &amtSent);
+ LOG(("WebSocketChannel::OnOutputStreamReady: write %u rv %" PRIx32 "\n",
+ amtSent, static_cast<uint32_t>(rv)));
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
+ 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, mSocketThread);
+ }
+ } else {
+ if (amtSent == toSend) {
+ if (!mStopped) {
+ mTargetThread->Dispatch(
+ new CallAcknowledge(this, mCurrentOut->OrigLength()),
+ NS_DISPATCH_NORMAL);
+ }
+ DeleteCurrentOutGoingMessage();
+ PrimeNewOutgoingMessage();
+ } else {
+ mCurrentOutSent += amtSent;
+ mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
+ }
+ }
+ }
+
+ 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;
+}
+
+} // namespace net
+} // namespace mozilla
+
+#undef CLOSE_GOING_AWAY
diff --git a/netwerk/protocol/websocket/WebSocketChannel.h b/netwerk/protocol/websocket/WebSocketChannel.h
new file mode 100644
index 0000000000..950f76d0b1
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannel.h
@@ -0,0 +1,315 @@
+/* -*- 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 "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;
+
+[[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 {
+ 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,
+ 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(nsISupports** aSecurityInfo) override;
+
+ WebSocketChannel();
+ static void Shutdown();
+ bool IsOnTargetThread();
+
+ // Off main thread URI access.
+ void GetEffectiveURL(nsAString& aEffectiveURL) const override;
+ bool IsEncrypted() const 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:
+ virtual ~WebSocketChannel();
+
+ 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 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);
+ }
+ }
+ }
+
+ nsCOMPtr<nsIEventTarget> mSocketThread;
+ nsCOMPtr<nsIHttpChannelInternal> mChannel;
+ nsCOMPtr<nsIHttpChannel> mHttpChannel;
+ nsCOMPtr<nsICancelable> mCancelable;
+ nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
+ nsCOMPtr<nsIRandomGenerator> mRandomGenerator;
+
+ nsCString mHashedSecret;
+
+ // 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)
+ nsCString mAddress;
+ int32_t mPort; // WS server port
+
+ // Used for off main thread access to the URI string.
+ nsCString mHost;
+ nsString mEffectiveURL;
+
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+
+ nsCOMPtr<nsITimer> mCloseTimer;
+ uint32_t mCloseTimeout; /* milliseconds */
+
+ nsCOMPtr<nsITimer> mOpenTimer;
+ uint32_t mOpenTimeout; /* milliseconds */
+ wsConnectingState mConnecting; /* 0 if not connecting */
+ nsCOMPtr<nsITimer> mReconnectDelayTimer;
+
+ nsCOMPtr<nsITimer> mPingTimer;
+
+ nsCOMPtr<nsITimer> mLingeringCloseTimer;
+ const static int32_t kLingeringCloseTimeout = 1000;
+ const static int32_t kLingeringCloseThreshold = 50;
+
+ RefPtr<WebSocketEventService> mService;
+
+ int32_t mMaxConcurrentConnections;
+
+ uint64_t mInnerWindowID;
+
+ // following members are accessed only on the main thread
+ uint32_t mGotUpgradeOK : 1;
+ uint32_t mRecvdHttpUpgradeTransport : 1;
+ uint32_t mAutoFollowRedirects : 1;
+ uint32_t mAllowPMCE : 1;
+ uint32_t : 0;
+
+ // following members are accessed only on the socket thread
+ uint32_t mPingOutstanding : 1;
+ uint32_t mReleaseOnTransmit : 1;
+ uint32_t : 0;
+
+ Atomic<bool> mDataStarted;
+ Atomic<bool> mRequestedClose;
+ Atomic<bool> mClientClosed;
+ Atomic<bool> mServerClosed;
+ Atomic<bool> mStopped;
+ Atomic<bool> mCalledOnStop;
+ Atomic<bool> mTCPClosed;
+ Atomic<bool> mOpenedHttpChannel;
+ Atomic<bool> mIncrementedSessionCount;
+ Atomic<bool> mDecrementedSessionCount;
+
+ int32_t mMaxMessageSize;
+ nsresult mStopOnClose;
+ uint16_t mServerCloseCode;
+ nsCString mServerCloseReason;
+ uint16_t mScriptCloseCode;
+ nsCString mScriptCloseReason;
+
+ // 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;
+
+ 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;
+
+ 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];
+ UniquePtr<PMCECompression> mPMCECompressor;
+ uint32_t mDynamicOutputSize;
+ uint8_t* mDynamicOutput;
+ bool mPrivateBrowsing;
+
+ nsCOMPtr<nsIDashboardEventNotifier> mConnectionLogService;
+
+ 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..ae9da1028f
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.cpp
@@ -0,0 +1,729 @@
+/* -*- 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"
+
+using namespace mozilla::ipc;
+
+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));
+}
+
+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,
+ nsIEventTarget* aEventTarget)
+ : mChild(aChild),
+ mWebSocketEvent(aWebSocketEvent),
+ mEventTarget(aEventTarget) {}
+
+ void Run() override {
+ if (mEventTarget) {
+ mEventTarget->Dispatch(
+ new WrappedWebSocketEvent(mChild, std::move(mWebSocketEvent)),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ mWebSocketEvent->Run(mChild);
+ }
+
+ already_AddRefed<nsIEventTarget> GetEventTarget() override {
+ nsCOMPtr<nsIEventTarget> target = mEventTarget;
+ if (!target) {
+ target = GetMainThreadEventTarget();
+ }
+ 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 nsCString& aProtocol, const nsCString& aExtensions,
+ const nsString& 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 nsCString& aProtocol, const nsCString& aExtensions,
+ const nsString& aEffectiveURL, const bool& aEncrypted,
+ const uint64_t& aHttpChannelId) {
+ mEventQ->RunOrEnqueue(new EventTargetDispatcher(
+ this,
+ new StartEvent(aProtocol, aExtensions, aEffectiveURL, aEncrypted,
+ aHttpChannelId),
+ mTargetThread));
+
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnStart(const nsCString& aProtocol,
+ const nsCString& aExtensions,
+ const nsString& 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), mTargetThread));
+
+ 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 nsCString& 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 nsDependentCSubstring& 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), mTargetThread));
+ 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 nsDependentCSubstring& aMsg, const bool& aMoreData) {
+ if (!RecvOnMessageAvailableInternal(aMsg, aMoreData, false)) {
+ LOG(("WebSocketChannelChild %p append message failed", this));
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(this, new OnErrorEvent(), mTargetThread));
+ }
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnMessageAvailable(const nsCString& 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 nsDependentCSubstring& aMsg, const bool& aMoreData) {
+ if (!RecvOnMessageAvailableInternal(aMsg, aMoreData, true)) {
+ LOG(("WebSocketChannelChild %p append message failed", this));
+ mEventQ->RunOrEnqueue(
+ new EventTargetDispatcher(this, new OnErrorEvent(), mTargetThread));
+ }
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnBinaryMessageAvailable(const nsCString& 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), mTargetThread));
+
+ 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 nsCString& 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 nsCString& aReason) {
+ mEventQ->RunOrEnqueue(new EventTargetDispatcher(
+ this, new ServerCloseEvent(aCode, aReason), mTargetThread));
+
+ return IPC_OK();
+}
+
+void WebSocketChannelChild::OnServerClose(const uint16_t& aCode,
+ const nsCString& 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 = nsContentUtils::GetEventTargetByLoadInfo(
+ mLoadInfo, TaskCategory::Network);
+ if (!mNeckoTarget) {
+ return;
+ }
+
+ gNeckoChild->SetEventTargetForActor(this, mNeckoTarget);
+}
+
+NS_IMETHODIMP
+WebSocketChannelChild::AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
+ 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;
+ Maybe<LoadInfoArgs> loadInfoArgs;
+ Maybe<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(ipcChild);
+ }
+
+ // This must be called before sending constructor message.
+ SetupNeckoTarget();
+
+ gNeckoChild->SendPWebSocketConstructor(
+ this, browserChild, IPC::SerializedLoadContext(this), mSerial);
+ if (!SendAsyncOpen(uri, nsCString(aOrigin), 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(mTargetThread->IsOnCurrentThread());
+ 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, nsCString(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(nsCString(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(nsCString(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(mTargetThread->IsOnCurrentThread());
+ nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
+ return target->Dispatch(new BinaryStreamEvent(this, aStream, aLength),
+ NS_DISPATCH_NORMAL);
+ }
+
+ LOG(("WebSocketChannelChild::SendBinaryStream() %p\n", this));
+
+ AutoIPCStream autoStream;
+ autoStream.Serialize(aStream, static_cast<mozilla::dom::ContentChild*>(
+ gNeckoChild->Manager()));
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mIPCState != Opened) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ if (!SendSendBinaryStream(autoStream.TakeValue(), aLength)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketChannelChild::GetSecurityInfo(nsISupports** aSecurityInfo) {
+ LOG(("WebSocketChannelChild::GetSecurityInfo() %p\n", this));
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+bool WebSocketChannelChild::IsOnTargetThread() {
+ MOZ_ASSERT(mTargetThread);
+ bool isOnTargetThread = false;
+ nsresult rv = mTargetThread->IsOnCurrentThread(&isOnTargetThread);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return NS_FAILED(rv) ? false : isOnTargetThread;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/protocol/websocket/WebSocketChannelChild.h b/netwerk/protocol/websocket/WebSocketChannelChild.h
new file mode 100644
index 0000000000..4be4ada669
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.h
@@ -0,0 +1,111 @@
+/* -*- 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 WebSocketChannelChild final : public BaseWebSocketChannel,
+ public PWebSocketChild,
+ public NeckoTargetHolder {
+ friend class PWebSocketChild;
+
+ public:
+ explicit WebSocketChannelChild(bool aSecure);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIWebSocketChannel methods BaseWebSocketChannel didn't implement for us
+ //
+ NS_IMETHOD AsyncOpen(nsIURI* aURI, const nsACString& aOrigin,
+ 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(nsISupports** 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 nsCString& aProtocol,
+ const nsCString& aExtensions,
+ const nsString& aEffectiveURL,
+ const bool& aSecure,
+ const uint64_t& aHttpChannelId);
+ mozilla::ipc::IPCResult RecvOnStop(const nsresult& aStatusCode);
+ mozilla::ipc::IPCResult RecvOnMessageAvailable(
+ const nsDependentCSubstring& aMsg, const bool& aMoreData);
+ mozilla::ipc::IPCResult RecvOnBinaryMessageAvailable(
+ const nsDependentCSubstring& aMsg, const bool& aMoreData);
+ mozilla::ipc::IPCResult RecvOnAcknowledge(const uint32_t& aSize);
+ mozilla::ipc::IPCResult RecvOnServerClose(const uint16_t& aCode,
+ const nsCString& aReason);
+
+ void OnStart(const nsCString& aProtocol, const nsCString& aExtensions,
+ const nsString& aEffectiveURL, const bool& aSecure,
+ const uint64_t& aHttpChannelId);
+ void OnStop(const nsresult& aStatusCode);
+ void OnMessageAvailable(const nsCString& aMsg);
+ void OnBinaryMessageAvailable(const nsCString& aMsg);
+ void OnAcknowledge(const uint32_t& aSize);
+ void OnServerClose(const uint16_t& aCode, const nsCString& aReason);
+ void AsyncOpenFailed();
+
+ bool IsOnTargetThread();
+
+ void MaybeReleaseIPCObject();
+
+ // This function tries to get a labeled event target for |mNeckoTarget|.
+ void SetupNeckoTarget();
+
+ bool RecvOnMessageAvailableInternal(const nsDependentCSubstring& 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;
+
+ 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..a1c3524471
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.cpp
@@ -0,0 +1,351 @@
+/* -*- 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/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"
+
+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 uint64_t& aInnerWindowID,
+ const nsCString& aProtocol, const bool& aSecure,
+ const uint32_t& aPingInterval, const bool& aClientSetPingInterval,
+ const uint32_t& aPingTimeout, const bool& aClientSetPingTimeout,
+ const Maybe<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, 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->AsyncOpen(uri, aOrigin, 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, nsCString(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..0d19b9ff6b
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.h
@@ -0,0 +1,69 @@
+/* -*- 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 uint64_t& aInnerWindowID,
+ const nsCString& aProtocol, const bool& aSecure,
+ const uint32_t& aPingInterval, const bool& aClientSetPingInterval,
+ const uint32_t& aPingTimeout, const bool& aClientSetPingTimeout,
+ const Maybe<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/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..bb21483fb9
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventListenerParent.cpp
@@ -0,0 +1,119 @@
+/* -*- 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"
+
+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, nsString(aURI),
+ nsCString(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, nsString(aEffectiveURI),
+ nsCString(aProtocols), nsCString(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,
+ nsString(aReason));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebSocketEventListenerParent::WebSocketMessageAvailable(
+ uint32_t aWebSocketSerialID, const nsACString& aData,
+ uint16_t aMessageType) {
+ Unused << SendWebSocketMessageAvailable(aWebSocketSerialID, nsCString(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..13322bad4e
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventService.cpp
@@ -0,0 +1,563 @@
+/* -*- 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.Put(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;
+
+ WindowListener* listener = mWindows.Get(aInnerWindowID);
+ if (!listener) {
+ listener = new WindowListener();
+
+ if (IsChildProcess()) {
+ PWebSocketEventListenerChild* actor =
+ gNeckoChild->SendPWebSocketEventListenerConstructor(aInnerWindowID);
+
+ listener->mActor = static_cast<WebSocketEventListenerChild*>(actor);
+ MOZ_ASSERT(listener->mActor);
+ }
+
+ mWindows.Put(aInnerWindowID, listener);
+ }
+
+ 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..81e9ce8607
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketEventService.h
@@ -0,0 +1,122 @@
+/* -*- 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"
+
+class nsIWebSocketImpl;
+
+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();
+
+ typedef nsTArray<nsCOMPtr<nsIWebSocketEventListener>> WindowListeners;
+
+ nsDataHashtable<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..c73c170c49
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketFrame.cpp
@@ -0,0 +1,158 @@
+/* -*- 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) {
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ 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()
+ : mTimeStamp(0),
+ mFinBit(false),
+ mRsvBit1(false),
+ mRsvBit2(false),
+ mRsvBit3(false),
+ mMaskBit(false),
+ mOpCode(0),
+ mMask(0) {
+ MOZ_COUNT_CTOR(WebSocketFrameData);
+}
+
+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) {
+ MOZ_COUNT_CTOR(WebSocketFrameData);
+}
+
+WebSocketFrameData::WebSocketFrameData(const WebSocketFrameData& aData)
+ : mTimeStamp(aData.mTimeStamp),
+ mFinBit(aData.mFinBit),
+ mRsvBit1(aData.mRsvBit1),
+ mRsvBit2(aData.mRsvBit2),
+ mRsvBit3(aData.mRsvBit3),
+ mMaskBit(aData.mMaskBit),
+ mOpCode(aData.mOpCode),
+ mMask(aData.mMask),
+ mPayload(aData.mPayload) {
+ MOZ_COUNT_CTOR(WebSocketFrameData);
+}
+
+WebSocketFrameData::~WebSocketFrameData() {
+ MOZ_COUNT_DTOR(WebSocketFrameData);
+}
+
+void WebSocketFrameData::WriteIPCParams(IPC::Message* aMessage) const {
+ WriteParam(aMessage, mTimeStamp);
+ WriteParam(aMessage, mFinBit);
+ WriteParam(aMessage, mRsvBit1);
+ WriteParam(aMessage, mRsvBit2);
+ WriteParam(aMessage, mRsvBit3);
+ WriteParam(aMessage, mMaskBit);
+ WriteParam(aMessage, mOpCode);
+ WriteParam(aMessage, mMask);
+ WriteParam(aMessage, mPayload);
+}
+
+bool WebSocketFrameData::ReadIPCParams(const IPC::Message* aMessage,
+ PickleIterator* aIter) {
+ if (!ReadParam(aMessage, aIter, &mTimeStamp)) {
+ return false;
+ }
+
+#define ReadParamHelper(x) \
+ { \
+ bool bit; \
+ if (!ReadParam(aMessage, aIter, &bit)) { \
+ return false; \
+ } \
+ x = bit; \
+ }
+
+ ReadParamHelper(mFinBit);
+ ReadParamHelper(mRsvBit1);
+ ReadParamHelper(mRsvBit2);
+ ReadParamHelper(mRsvBit3);
+ ReadParamHelper(mMaskBit);
+
+#undef ReadParamHelper
+
+ return ReadParam(aMessage, aIter, &mOpCode) &&
+ ReadParam(aMessage, aIter, &mMask) &&
+ ReadParam(aMessage, aIter, &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..3f96d9a131
--- /dev/null
+++ b/netwerk/protocol/websocket/WebSocketFrame.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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.
+typedef double DOMHighResTimeStamp;
+
+namespace IPC {
+class Message;
+template <class P>
+struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+namespace net {
+
+class WebSocketFrameData final {
+ public:
+ WebSocketFrameData();
+
+ explicit WebSocketFrameData(const WebSocketFrameData& aData);
+
+ WebSocketFrameData(DOMHighResTimeStamp aTimeStamp, bool aFinBit,
+ bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
+ uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
+ const nsCString& aPayload);
+
+ ~WebSocketFrameData();
+
+ // For IPC serialization
+ void WriteIPCParams(IPC::Message* aMessage) const;
+ bool ReadIPCParams(const IPC::Message* aMessage, PickleIterator* aIter);
+
+ DOMHighResTimeStamp mTimeStamp;
+
+ bool mFinBit : 1;
+ bool mRsvBit1 : 1;
+ bool mRsvBit2 : 1;
+ bool mRsvBit3 : 1;
+ bool mMaskBit : 1;
+ uint8_t mOpCode;
+
+ uint32_t mMask;
+
+ 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> {
+ typedef mozilla::net::WebSocketFrameData paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam) {
+ aParam.WriteIPCParams(aMsg);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter,
+ paramType* aResult) {
+ return aResult->ReadIPCParams(aMsg, aIter);
+ }
+};
+
+} // 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..c650e390b6
--- /dev/null
+++ b/netwerk/protocol/websocket/moz.build
@@ -0,0 +1,62 @@
+# -*- 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",
+ "WebSocketEventListenerChild.h",
+ "WebSocketEventListenerParent.h",
+ "WebSocketEventService.h",
+ "WebSocketFrame.h",
+]
+
+UNIFIED_SOURCES += [
+ "BaseWebSocketChannel.cpp",
+ "IPCTransportProvider.cpp",
+ "WebSocketChannel.cpp",
+ "WebSocketChannelChild.cpp",
+ "WebSocketChannelParent.cpp",
+ "WebSocketEventListenerChild.cpp",
+ "WebSocketEventListenerParent.cpp",
+ "WebSocketEventService.cpp",
+ "WebSocketFrame.cpp",
+]
+
+IPDL_SOURCES += [
+ "PTransportProvider.ipdl",
+ "PWebSocket.ipdl",
+ "PWebSocketEventListener.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/netwerk/base",
+]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
+
+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..5c2137b01b
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketChannel.idl
@@ -0,0 +1,252 @@
+/* -*- 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 nsIURI;
+interface nsIInterfaceRequestor;
+interface nsILoadGroup;
+interface nsIWebSocketListener;
+interface nsIInputStream;
+interface nsILoadInfo;
+interface nsIPrincipal;
+interface nsITransportProvider;
+
+webidl Node;
+
+#include "nsISupports.idl"
+#include "nsIContentPolicy.idl"
+
+/**
+ * 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 nsISupports 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 aInnerWindowID the inner window ID
+ * @param aListener the nsIWebSocketListener implementation
+ * @param aContext an opaque parameter forwarded to aListener's methods
+ */
+ [must_use] void asyncOpen(in nsIURI aURI,
+ in ACString aOrigin,
+ 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..4a8416b758
--- /dev/null
+++ b/netwerk/protocol/websocket/nsIWebSocketListener.idl
@@ -0,0 +1,96 @@
+/* -*- 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/sctp/datachannel/DataChannel.cpp b/netwerk/sctp/datachannel/DataChannel.cpp
new file mode 100644
index 0000000000..2ab1d5b5ad
--- /dev/null
+++ b/netwerk/sctp/datachannel/DataChannel.cpp
@@ -0,0 +1,3368 @@
+/* -*- 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 "nsIObserverService.h"
+#include "nsIObserver.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"
+#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);
+ }
+}
+
+class DataChannelRegistry : public nsIObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ static uintptr_t Register(DataChannelConnection* aConnection) {
+ StaticMutexAutoLock lock(sInstanceMutex);
+ if (NS_WARN_IF(!Instance())) {
+ return 0;
+ }
+ return Instance()->RegisterImpl(aConnection);
+ }
+
+ static void Deregister(uintptr_t aId) {
+ StaticMutexAutoLock lock(sInstanceMutex);
+ if (NS_WARN_IF(!Instance())) {
+ return;
+ }
+ Instance()->DeregisterImpl(aId);
+ }
+
+ 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());
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) return;
+
+ nsresult rv =
+ observerService->AddObserver(this, "xpcom-will-shutdown", false);
+ MOZ_ASSERT(rv == NS_OK);
+ (void)rv;
+ // TODO(bug 1646716): usrsctp_finish is racy, so we init in the c'tor.
+ InitUsrSctp();
+ }
+
+ static RefPtr<DataChannelRegistry>& Instance() {
+ // Lazy-create static registry.
+ static RefPtr<DataChannelRegistry> sRegistry = new DataChannelRegistry;
+ return sRegistry;
+ }
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ if (strcmp(aTopic, "xpcom-will-shutdown") == 0) {
+ RefPtr<DataChannelRegistry> self = this;
+ {
+ StaticMutexAutoLock lock(sInstanceMutex);
+ Instance() = nullptr;
+ }
+
+ // |self| is the only reference now
+
+ if (NS_WARN_IF(!mConnections.empty())) {
+ MOZ_ASSERT(false);
+ mConnections.clear();
+ }
+
+ // TODO(bug 1646716): usrsctp_finish is racy, so we wait until xpcom
+ // shutdown for this.
+ DeinitUsrSctp();
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!observerService)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv =
+ observerService->RemoveObserver(this, "xpcom-will-shutdown");
+ MOZ_ASSERT(rv == NS_OK);
+ (void)rv;
+ }
+
+ return NS_OK;
+ }
+
+ uintptr_t RegisterImpl(DataChannelConnection* aConnection) {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ // TODO(bug 1646716): usrsctp_finish is racy, so we init in the c'tor.
+ // if (mConnections.empty()) {
+ // InitUsrSctp();
+ //}
+ mConnections.emplace(mNextId, aConnection);
+ return mNextId++;
+ }
+
+ void DeregisterImpl(uintptr_t aId) {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ mConnections.erase(aId);
+ // TODO(bug 1646716): usrsctp_finish is racy, so we wait until xpcom
+ // shutdown for this.
+ // if (mConnections.empty()) {
+ // DeinitUsrSctp();
+ //}
+ }
+
+ RefPtr<DataChannelConnection> LookupImpl(uintptr_t aId) {
+ auto it = mConnections.find(aId);
+ if (NS_WARN_IF(it == mConnections.end())) {
+ return nullptr;
+ }
+ return it->second;
+ }
+
+ virtual ~DataChannelRegistry() = default;
+
+#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)) {
+ return 0;
+ }
+ return connection->SctpDtlsOutput(addr, buffer, length, tos, set_df);
+ }
+#endif
+
+ void InitUsrSctp() {
+ DC_DEBUG(("sctp_init"));
+#ifdef MOZ_PEERCONNECTION
+ usrsctp_init(0, DataChannelRegistry::SctpDtlsOutput, debug_printf);
+#else
+ MOZ_CRASH("Trying to use SCTP/DTLS without dom/media/webrtc/transport");
+#endif
+
+ // 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(("Shutting down SCTP"));
+ usrsctp_finish();
+ }
+
+ uintptr_t mNextId = 1;
+ std::map<uintptr_t, RefPtr<DataChannelConnection>> mConnections;
+ static StaticMutex sInstanceMutex;
+};
+
+StaticMutex DataChannelRegistry::sInstanceMutex;
+
+NS_IMPL_ISUPPORTS(DataChannelRegistry, nsIObserver);
+
+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) {
+ MOZ_ASSERT(false);
+ 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
+static int threshold_event(struct socket* sock, uint32_t sb_free) {
+ RefPtr<DataChannelConnection> connection = GetConnectionFromSocket(sock);
+ 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
+ ASSERT_WEBRTC(mState == CLOSED);
+ MOZ_ASSERT(!mMasterSocket);
+ MOZ_ASSERT(mPending.GetSize() == 0);
+
+ if (!IsSTSThread()) {
+ ASSERT_WEBRTC(NS_IsMainThread());
+
+ 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
+
+ // 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;
+#endif
+
+ disconnect_all();
+ mTransportHandler = nullptr;
+ GetMainThreadEventTarget()->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, threshold_event,
+ 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. */
+ memset(&event, 0, sizeof(event));
+ 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;
+ }
+ }
+
+ memset(&initmsg, 0, sizeof(initmsg));
+ 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;
+}
+
+void DataChannelConnection::SetMaxMessageSize(bool aMaxMessageSizeSet,
+ uint64_t aMaxMessageSize) {
+ MutexAutoLock lock(mLock); // TODO: Needed?
+
+ 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() { 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 CONNECTING:
+ state = State::Connecting;
+ break;
+ case OPEN:
+ state = State::Open;
+ break;
+ case CLOSING:
+ state = State::Closing;
+ break;
+ case CLOSED:
+ state = State::Closed;
+ break;
+ default:
+ MOZ_ASSERT(false, "Unknown DataChannel state");
+ continue;
+ };
+ 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()));
+
+ const auto currentReadyState = GetReadyState();
+ if (currentReadyState == 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;
+ SetReadyState(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());
+ mTransportId = aTransportId;
+ mTransportHandler->SignalPacketReceived.connect(
+ this, &DataChannelConnection::SctpDtlsInput);
+ mTransportHandler->SignalStateChange.connect(
+ this, &DataChannelConnection::TransportStateChange);
+ // 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) {
+ 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;
+ memset(&addr, 0, sizeof(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;
+
+ memset(&paddrparams, 0, sizeof(struct sctp_paddrparams));
+ 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 {
+ // draft-ietf-rtcweb-data-channel-13 section 5: max initial MTU IPV4
+ // 1200, IPV6 1280
+ paddrparams.spp_pathmtu = 1200; // safe for either
+ 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));
+ SetReadyState(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::ON_CONNECTION, 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->mFlags & DATA_CHANNEL_FLAGS_FINISH_OPEN) {
+ DC_DEBUG(("Processing queued open for %p (%u)", channel.get(),
+ channel->mStream));
+ channel->mFlags &= ~DATA_CHANNEL_FLAGS_FINISH_OPEN;
+ // 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 FINISH_OPEN flag?");
+ }
+ }
+}
+
+void DataChannelConnection::SctpDtlsInput(const std::string& aTransportId,
+ const MediaPacket& packet) {
+ 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
+ MutexAutoLock lock(mLock);
+ 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) {
+ MOZ_DIAGNOSTIC_ASSERT(!mShutdown);
+
+ 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' */
+ memset((void*)&addr, 0, sizeof(addr));
+# 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);
+ SetReadyState(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);
+ SetReadyState(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::ON_CONNECTION, 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));
+ memset((void*)&addr4, 0, sizeof(struct sockaddr_in));
+ memset((void*)&addr6, 0, sizeof(struct sockaddr_in6));
+# 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);
+ SetReadyState(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);
+ SetReadyState(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::ON_CONNECTION, this,
+ (DataChannel*)nullptr)));
+ return true;
+}
+#endif
+
+DataChannel* DataChannelConnection::FindChannelByStream(uint16_t stream) {
+ return mChannels.Get(stream).get();
+}
+
+uint16_t DataChannelConnection::FindFreeStream() {
+ 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() {
+ 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
+ memset(&sas, 0, sizeof(sas));
+ 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 = {0};
+
+ // 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) {
+ mPendingType = PENDING_DCEP;
+ }
+ return error;
+}
+
+// Returns a POSIX error code.
+int DataChannelConnection::SendOpenAckMessage(uint16_t stream) {
+ struct rtcweb_datachannel_ack ack;
+
+ memset(&ack, 0, sizeof(struct rtcweb_datachannel_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, uint16_t prPolicy, uint32_t prValue) {
+ const int label_len = label.Length(); // not including nul
+ const int proto_len = protocol.Length(); // not including nul
+ // careful - request struct include one char for the label
+ const int 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 SCTP_PR_SCTP_NONE:
+ req->channel_type = DATA_CHANNEL_RELIABLE;
+ break;
+ case SCTP_PR_SCTP_TTL:
+ req->channel_type = DATA_CHANNEL_PARTIAL_RELIABLE_TIMED;
+ break;
+ case SCTP_PR_SCTP_RTX:
+ 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: %d", mPendingType));
+ if (!mPendingType) {
+ 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 == PENDING_DCEP)) {
+ if (SendBufferedMessages(mBufferedControl, nullptr)) {
+ return true;
+ }
+
+ // Note: There may or may not be pending data messages
+ mPendingType = PENDING_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() ? PENDING_NONE : PENDING_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;
+ uint16_t 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 = SCTP_PR_SCTP_NONE;
+ break;
+ case DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT:
+ case DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT_UNORDERED:
+ prPolicy = SCTP_PR_SCTP_RTX;
+ break;
+ case DATA_CHANNEL_PARTIAL_RELIABLE_TIMED:
+ case DATA_CHANNEL_PARTIAL_RELIABLE_TIMED_UNORDERED:
+ prPolicy = SCTP_PR_SCTP_TTL;
+ 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 %u/%u, value %u/%u, ordered %d/%d",
+ stream, prPolicy, 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, DataChannel::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::ON_CHANNEL_CREATED, 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)]() {
+ MutexAutoLock mLock(connection->mLock);
+ // Close the channel on failure
+ connection->CloseInt(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) {
+ 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->mFlags & DATA_CHANNEL_FLAGS_WAITING_ACK) ? 1 : 0));
+
+ channel->mFlags &= ~DATA_CHANNEL_FLAGS_WAITING_ACK;
+}
+
+// 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));
+ CloseInt(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;
+ }
+
+ bool is_binary = true;
+ uint8_t bufferFlags;
+ int32_t type;
+ const char* info = "";
+
+ if (ppid == DATA_CHANNEL_PPID_DOMSTRING_PARTIAL ||
+ ppid == DATA_CHANNEL_PPID_DOMSTRING) {
+ 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->mFlags & DATA_CHANNEL_FLAGS_CLOSING_TOO_LARGE) {
+ 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->mFlags &= ~DATA_CHANNEL_FLAGS_CLOSING_TOO_LARGE;
+ }
+ }
+
+ // 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->mFlags |= DATA_CHANNEL_FLAGS_CLOSING_TOO_LARGE;
+ CloseInt(channel);
+ return;
+ }
+ if (!(bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_COMPLETE)) {
+ DC_DEBUG(
+ ("DataChannel: Partial %s message of length %u (total %u) 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));
+ }
+
+ 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::ON_DATA_STRING;
+ 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_BINARY:
+ DC_DEBUG(
+ ("DataChannel: Received binary message of length %u on channel id %u",
+ data_length, channel->mStream));
+ type = DataChannelOnMessageAvailable::ON_DATA_BINARY;
+ if (bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_BUFFERED) {
+ info = " (binary fragmented)";
+ }
+
+ // else send using recvData normally
+ 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 ON_DATA_%s%s for %p", __FUNCTION__,
+ (type == DataChannelOnMessageAvailable::ON_DATA_STRING) ? "STRING"
+ : "BINARY",
+ 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(buffer, data_length); // copies (<64) or allocates
+ 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_BINARY_PARTIAL:
+ case DATA_CHANNEL_PPID_BINARY:
+ 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;
+ const auto readyState = GetReadyState();
+ switch (sac->sac_state) {
+ case SCTP_COMM_UP:
+ DC_DEBUG(("Association change: SCTP_COMM_UP"));
+ if (readyState == CONNECTING) {
+ mSocket = mMasterSocket;
+ SetReadyState(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::ON_CONNECTION, this)));
+ DC_DEBUG(("DTLS connect() succeeded! Entering connected mode"));
+
+ // Open any streams pending...
+ ProcessQueuedOpens();
+
+ } else if (readyState == OPEN) {
+ DC_DEBUG(("DataConnection Already OPEN"));
+ } else {
+ DC_ERROR(("Unexpected state: %d", readyState));
+ }
+ 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 %u 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));
+ }
+ DC_DEBUG(
+ ("message with PPID = %u, 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));
+ 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) {
+ ASSERT_WEBRTC(!NS_IsMainThread());
+ DC_DEBUG(("In ReceiveCallback"));
+
+ if (!data) {
+ DC_DEBUG(("ReceiveCallback: SCTP has finished shutting down"));
+ } else {
+ bool locked = false;
+ if (!IsSTSThread()) {
+ mLock.Lock();
+ locked = true;
+ } else {
+ mLock.AssertCurrentThreadOwns();
+ }
+ if (flags & MSG_NOTIFICATION) {
+ HandleNotification(static_cast<union sctp_notification*>(data), datalen);
+ } else {
+ HandleMessage(data, datalen, ntohl(rcv.rcv_ppid), rcv.rcv_sid, flags);
+ }
+ if (locked) {
+ 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, Type type,
+ bool inOrder, uint32_t prValue, DataChannelListener* aListener,
+ nsISupports* aContext, bool aExternalNegotiated, uint16_t aStream) {
+ ASSERT_WEBRTC(NS_IsMainThread());
+ 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;
+ }
+ }
+ uint16_t prPolicy = SCTP_PR_SCTP_NONE;
+
+ DC_DEBUG(
+ ("DC Open: label %s/%s, type %u, inorder %d, prValue %u, listener %p, "
+ "context %p, external: %s, stream %u",
+ PromiseFlatCString(label).get(), PromiseFlatCString(protocol).get(),
+ type, inOrder, prValue, aListener, aContext,
+ aExternalNegotiated ? "true" : "false", aStream));
+ switch (type) {
+ case DATA_CHANNEL_RELIABLE:
+ prPolicy = SCTP_PR_SCTP_NONE;
+ break;
+ case DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT:
+ prPolicy = SCTP_PR_SCTP_RTX;
+ break;
+ case DATA_CHANNEL_PARTIAL_RELIABLE_TIMED:
+ prPolicy = SCTP_PR_SCTP_TTL;
+ break;
+ default:
+ DC_ERROR(("unsupported channel type: %u", type));
+ MOZ_ASSERT(false);
+ return nullptr;
+ }
+ if ((prPolicy == SCTP_PR_SCTP_NONE) && (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, DataChannel::CONNECTING, label, protocol, prPolicy,
+ prValue, inOrder, aExternalNegotiated, aListener, aContext));
+ mChannels.Insert(channel);
+
+ MutexAutoLock lock(mLock); // OpenFinish assumes this
+ 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.
+ const auto readyState = GetReadyState();
+ if (readyState != OPEN || stream >= mNegotiatedIdLimit) {
+ if (readyState == 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->mFlags |= DATA_CHANNEL_FLAGS_FINISH_OPEN;
+ 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->mOrdered) {
+ // Don't send unordered until this gets cleared
+ channel->mFlags |= DATA_CHANNEL_FLAGS_WAITING_ACK;
+ }
+
+ if (!channel->mNegotiated) {
+ 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->mFlags & DATA_CHANNEL_FLAGS_FINISH_OPEN) {
+ // 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->mFlags & DATA_CHANNEL_FLAGS_FINISH_OPEN) {
+ // 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) {
+ auto& info = msg.GetInfo().sendv_sndinfo;
+ int error;
+
+ // EOR set?
+ bool eor_set = info.snd_flags & SCTP_EOR ? true : false;
+
+ // 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)) {
+ 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() != OPEN)) {
+ return EINVAL; // TODO: Find a better error code
+ }
+
+ struct sctp_sendv_spa info = {0};
+
+ // 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);
+
+ MutexAutoLock lock(mLock); // Need to protect mFlags... :(
+ // 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.mFlags & DATA_CHANNEL_FLAGS_WAITING_ACK)) {
+ info.sendv_sndinfo.snd_flags |= SCTP_UNORDERED;
+ }
+
+ // Partial reliability policy
+ if (channel.mPrPolicy != SCTP_PR_SCTP_NONE) {
+ info.sendv_prinfo.pr_policy = 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) {
+ 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) {
+ mPendingType = PENDING_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.
+
+ 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) {
+ 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;
+};
+
+static auto readyStateToCStr(const uint16_t state) -> const char* {
+ switch (state) {
+ case DataChannelConnection::CONNECTING:
+ return "CONNECTING";
+ case DataChannelConnection::OPEN:
+ return "OPEN";
+ case DataChannelConnection::CLOSING:
+ return "CLOSING";
+ case DataChannelConnection::CLOSED:
+ return "CLOSED";
+ default: {
+ MOZ_ASSERT(false);
+ return "UNKNOWW";
+ }
+ }
+};
+
+void DataChannelConnection::SetReadyState(const uint16_t aState) {
+ mLock.AssertCurrentThreadOwns();
+
+ DC_DEBUG(
+ ("DataChannelConnection labeled %s (%p) switching connection state %s -> "
+ "%s",
+ mTransportId.c_str(), this, readyStateToCStr(mState),
+ readyStateToCStr(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
+ }
+
+ auto& channel = *channelPtr;
+ int err = 0;
+ if (isBinary) {
+ err = SendDataMsg(channel, data, len, DATA_CHANNEL_PPID_BINARY_PARTIAL,
+ DATA_CHANNEL_PPID_BINARY);
+ } else {
+ err = SendDataMsg(channel, data, len, DATA_CHANNEL_PPID_DOMSTRING_PARTIAL,
+ 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::ON_DISCONNECTED, this)));
+}
+
+void DataChannelConnection::Close(DataChannel* aChannel) {
+ MutexAutoLock lock(mLock);
+ CloseInt(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::CloseInt(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 (GetReadyState() == 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
+ auto channelState = aChannel->mReadyState;
+ // re-test since it may have closed before the lock was grabbed
+ if (channelState == CLOSED || channelState == CLOSING) {
+ DC_DEBUG(("Channel already closing/closed (%u)", channelState));
+ return;
+ }
+
+ if (channel->mStream != INVALID_STREAM) {
+ ResetOutgoingStream(channel->mStream);
+ if (GetReadyState() != CLOSED) {
+ // Individual channel is being closed, send reset now.
+ SendOutgoingStreamReset();
+ }
+ }
+ aChannel->SetReadyState(CLOSING);
+ if (GetReadyState() == 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));
+ // Don't need to lock here
+
+ // Make sure no more channels will be opened
+ {
+ MutexAutoLock lock(mLock);
+ SetReadyState(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()) {
+ 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));
+ channel->Close(); // also releases the ref on each iteration
+ }
+ // It's more efficient to let the Resets queue in shutdown and then
+ // SendOutgoingStreamReset() here.
+ MutexAutoLock lock(mLock);
+ 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 == CLOSED || mReadyState == 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() {
+ mConnection->mLock.AssertCurrentThreadOwns();
+ ENSURE_DATACONNECTION;
+
+ 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)] {
+ auto 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 != CLOSING && state != CLOSED && mListener) {
+ SetReadyState(OPEN);
+ DC_DEBUG(("%s: sending ON_CHANNEL_OPEN for %s/%s: %u", __FUNCTION__,
+ mLabel.get(), mProtocol.get(), mStream));
+ mListener->OnChannelConnected(mContext);
+ }
+ }));
+}
+
+void DataChannel::AnnounceClosed() {
+ mMainThreadEventTarget->Dispatch(NS_NewRunnableFunction(
+ "DataChannel::AnnounceClosed", [this, self = RefPtr<DataChannel>(this)] {
+ if (GetReadyState() == CLOSED) {
+ return;
+ }
+ SetReadyState(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 uint16_t aState) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ DC_DEBUG(
+ ("DataChannelConnection labeled %s(%p) (stream %d) changing ready state "
+ "%s -> %s",
+ mLabel.get(), this, mStream, readyStateToCStr(mReadyState),
+ readyStateToCStr(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 == SCTP_PR_SCTP_TTL) {
+ return dom::Nullable<uint16_t>(mPrValue);
+ }
+ return dom::Nullable<uint16_t>();
+}
+
+dom::Nullable<uint16_t> DataChannel::GetMaxRetransmits() const {
+ if (mPrPolicy == SCTP_PR_SCTP_RTX) {
+ return dom::Nullable<uint16_t>(mPrValue);
+ }
+ return dom::Nullable<uint16_t>();
+}
+
+uint32_t DataChannel::GetBufferedAmountLowThreshold() {
+ 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);
+}
+
+} // namespace mozilla
diff --git a/netwerk/sctp/datachannel/DataChannel.h b/netwerk/sctp/datachannel/DataChannel.h
new file mode 100644
index 0000000000..24e6f785cf
--- /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;
+};
+
+// 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() = default;
+ ;
+ void Advance(size_t offset);
+ struct sctp_sendv_spa& GetInfo() {
+ return *mInfo;
+ };
+ size_t GetLength() { return mLength; };
+ size_t GetLeft() { return mLength - mPos; };
+ const uint8_t* GetData() { 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& message);
+ ~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() { 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 {
+ PENDING_NONE = 0U, // No outgoing messages are pending
+ PENDING_DCEP = 1U, // Outgoing DCEP messages are pending
+ PENDING_DATA = 2U, // Outgoing data channel messages are pending
+ };
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DataChannelConnection)
+
+ 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;
+ };
+
+ // 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);
+
+ 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
+
+ typedef enum {
+ RELIABLE = 0,
+ PARTIAL_RELIABLE_REXMIT = 1,
+ PARTIAL_RELIABLE_TIMED = 2
+ } Type;
+
+ [[nodiscard]] already_AddRefed<DataChannel> Open(
+ const nsACString& label, const nsACString& protocol, Type type,
+ bool inOrder, uint32_t prValue, DataChannelListener* aListener,
+ nsISupports* aContext, bool aExternalNegotiated, uint16_t aStream);
+
+ void Stop();
+ void Close(DataChannel* aChannel);
+ // CloseInt() must be called with mLock held
+ void CloseInt(DataChannel* aChannel);
+ 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
+ int ReceiveCallback(struct socket* sock, void* data, size_t datalen,
+ struct sctp_rcvinfo rcv, int flags);
+
+ // Find out state
+ enum { CONNECTING = 0U, OPEN = 1U, CLOSING = 2U, CLOSED = 3U };
+
+ Mutex mLock;
+
+ void ReadBlob(already_AddRefed<DataChannelConnection> aThis, uint16_t aStream,
+ nsIInputStream* aBlob);
+
+ bool SendDeferredMessages();
+
+#ifdef SCTP_DTLS_SUPPORTED
+ int SctpDtlsOutput(void* addr, void* buffer, size_t length, uint8_t tos,
+ uint8_t set_df);
+#endif
+
+ protected:
+ // Avoid cycles with PeerConnectionImpl
+ // Use from main thread only as WeakPtr is not threadsafe
+ WeakPtr<DataConnectionListener> mListener;
+
+ private:
+ DataChannelConnection(DataConnectionListener* aListener,
+ nsISerialEventTarget* aTarget,
+ MediaTransportHandler* aHandler);
+
+ bool Init(const uint16_t aLocalPort, const uint16_t aNumStreams,
+ const Maybe<uint64_t>& aMaxMessageSize);
+
+ // Caller must hold mLock
+ uint16_t GetReadyState() const {
+ mLock.AssertCurrentThreadOwns();
+
+ return mState;
+ }
+
+ // Caller must hold mLock
+ void SetReadyState(const uint16_t aState);
+
+#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);
+ uint16_t FindFreeStream();
+ bool RequestMoreStreams(int32_t aNeeded = 16);
+ uint32_t UpdateCurrentStreamIndex();
+ uint32_t GetCurrentStreamIndex();
+ int SendControlMessage(const uint8_t* data, uint32_t len, uint16_t stream);
+ int SendOpenAckMessage(uint16_t stream);
+ int SendOpenRequestMessage(const nsACString& label,
+ const nsACString& protocol, uint16_t stream,
+ bool unordered, uint16_t prPolicy,
+ uint32_t prValue);
+ 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);
+ int SendDataMsgInternalOrBuffer(DataChannel& channel, const uint8_t* data,
+ size_t len, uint32_t ppid);
+ int SendDataMsg(DataChannel& channel, const uint8_t* data, size_t len,
+ uint32_t ppidPartial, uint32_t ppidFinal);
+ int SendDataMsgCommon(uint16_t stream, const nsACString& aMsg, bool isBinary);
+
+ void DeliverQueuedData(uint16_t stream);
+
+ already_AddRefed<DataChannel> OpenFinish(
+ already_AddRefed<DataChannel>&& aChannel);
+
+ void ProcessQueuedOpens();
+ void ClearResets();
+ void SendOutgoingStreamReset();
+ void ResetOutgoingStream(uint16_t stream);
+ void HandleOpenRequestMessage(
+ const struct rtcweb_datachannel_open_request* req, uint32_t length,
+ uint16_t stream);
+ 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);
+ uint8_t BufferMessage(nsACString& recvBuffer, const void* data,
+ uint32_t length, uint32_t ppid, int flags);
+ void HandleDataMessage(const void* buffer, size_t length, uint32_t ppid,
+ uint16_t stream, int flags);
+ void HandleDCEPMessage(const void* buffer, size_t length, uint32_t ppid,
+ uint16_t stream, int flags);
+ void HandleMessage(const void* buffer, size_t length, uint32_t ppid,
+ uint16_t stream, int flags);
+ void HandleAssociationChangeEvent(const struct sctp_assoc_change* sac);
+ void HandlePeerAddressChangeEvent(const struct sctp_paddr_change* spc);
+ void HandleRemoteErrorEvent(const struct sctp_remote_error* sre);
+ void HandleShutdownEvent(const struct sctp_shutdown_event* sse);
+ void HandleAdaptationIndication(const struct sctp_adaptation_event* sai);
+ void HandlePartialDeliveryEvent(const struct sctp_pdapi_event* spde);
+ void HandleSendFailedEvent(const struct sctp_send_failed_event* ssfe);
+ void HandleStreamResetEvent(const struct sctp_stream_reset_event* strrst);
+ void HandleStreamChangeEvent(const struct sctp_stream_change_event* strchg);
+ void HandleNotification(const union sctp_notification* notif, size_t n);
+
+#ifdef SCTP_DTLS_SUPPORTED
+ bool IsSTSThread() const {
+ bool on = false;
+ if (mSTS) {
+ mSTS->IsOnCurrentThread(&on);
+ }
+ return on;
+ }
+#endif
+
+ class Channels {
+ public:
+ Channels() : mMutex("DataChannelConnection::Channels::mMutex") {}
+ void Insert(const RefPtr<DataChannel>& aChannel);
+ bool Remove(const RefPtr<DataChannel>& aChannel);
+ RefPtr<DataChannel> Get(uint16_t aId) const;
+ typedef AutoTArray<RefPtr<DataChannel>, 16> ChannelArray;
+ 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;
+ };
+
+ bool mSendInterleaved = false;
+ bool mMaxMessageSizeSet = false;
+ uint64_t mMaxMessageSize = 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 = 0; // GUARDED_BY(mConnection->mLock)
+ uint8_t mPendingType = PENDING_NONE;
+ // holds data that's come in before a channel is open
+ nsTArray<UniquePtr<QueuedDataMessage>> mQueuedData;
+ // holds outgoing control messages
+ nsTArray<UniquePtr<BufferedOutgoingMsg>>
+ mBufferedControl; // GUARDED_BY(mConnection->mLock)
+
+ // Streams pending reset. Accessed from main and STS.
+ AutoTArray<uint16_t, 4> mStreamsResetting; // GUARDED_BY(mConnection->mLock)
+ // accessed from STS thread
+ struct socket* mMasterSocket = nullptr;
+ // cloned from mMasterSocket on successful Connect on STS thread
+ struct socket* mSocket = nullptr;
+ uint16_t mState = CLOSED; // Protected with mLock
+
+#ifdef SCTP_DTLS_SUPPORTED
+ std::string mTransportId;
+ 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;
+};
+
+#define ENSURE_DATACONNECTION \
+ do { \
+ MOZ_ASSERT(mConnection); \
+ if (!mConnection) { \
+ return; \
+ } \
+ } while (0)
+
+class DataChannel {
+ friend class DataChannelOnMessageAvailable;
+ friend class DataChannelConnection;
+
+ public:
+ enum { CONNECTING = 0U, OPEN = 1U, CLOSING = 2U, CLOSED = 3U };
+
+ DataChannel(DataChannelConnection* connection, uint16_t stream,
+ uint16_t state, const nsACString& label,
+ const nsACString& protocol, uint16_t 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),
+ mFlags(0),
+ mIsRecvBinary(false),
+ mBufferedThreshold(0), // default from spec
+ mBufferedAmount(0),
+ mMainThreadEventTarget(connection->GetNeckoTarget()),
+ mStatsLock("netwer::sctp::DataChannel::mStatsLock") {
+ NS_ASSERTION(mConnection, "NULL connection");
+ }
+
+ 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);
+
+ uint16_t GetType() { return mPrPolicy; }
+
+ dom::Nullable<uint16_t> GetMaxPacketLifeTime() const;
+
+ dom::Nullable<uint16_t> GetMaxRetransmits() const;
+
+ bool GetNegotiated() { return mNegotiated; }
+
+ bool GetOrdered() { return mOrdered; }
+
+ void IncrementBufferedAmount(uint32_t aSize, ErrorResult& aRv);
+ void DecrementBufferedAmount(uint32_t aSize);
+
+ // Amount of data buffered to send
+ uint32_t GetBufferedAmount() {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mBufferedAmount;
+ }
+
+ // Trigger amount for generating BufferedAmountLow events
+ uint32_t GetBufferedAmountLowThreshold();
+ void SetBufferedAmountLowThreshold(uint32_t aThreshold);
+
+ void AnnounceOpen();
+ // TODO(bug 843625): Optionally pass an error here.
+ void AnnounceClosed();
+
+ // Find out state
+ uint16_t GetReadyState() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mReadyState;
+ }
+
+ // Set ready state
+ void SetReadyState(const uint16_t aState);
+
+ void GetLabel(nsAString& aLabel) { CopyUTF8toUTF16(mLabel, aLabel); }
+ void GetProtocol(nsAString& aProtocol) {
+ CopyUTF8toUTF16(mProtocol, aProtocol);
+ }
+ uint16_t GetStream() { return mStream; }
+
+ void SendOrQueue(DataChannelOnMessageAvailable* aMessage);
+
+ struct TrafficCounters {
+ uint32_t mMessagesSent = 0;
+ uint64_t mBytesSent = 0;
+ uint32_t mMessagesReceived = 0;
+ uint64_t mBytesReceived = 0;
+ };
+
+ TrafficCounters GetTrafficCounters() const;
+
+ protected:
+ // These are both mainthread only
+ DataChannelListener* mListener;
+ nsCOMPtr<nsISupports> mContext;
+
+ private:
+ nsresult AddDataToBinaryMsg(const char* data, uint32_t size);
+ bool EnsureValidStream(ErrorResult& aRv);
+ void WithTrafficCounters(const std::function<void(TrafficCounters&)>&);
+
+ RefPtr<DataChannelConnection> mConnection;
+ nsCString mLabel;
+ nsCString mProtocol;
+ // This is mainthread only
+ uint16_t mReadyState;
+ uint16_t mStream;
+ uint16_t mPrPolicy;
+ uint32_t mPrValue;
+ // Accessed on main and STS
+ const bool mNegotiated;
+ const bool mOrdered;
+ uint32_t mFlags;
+ 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; // GUARDED_BY(mConnection->mLock)
+ nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
+ mutable Mutex mStatsLock; // protects mTrafficCounters
+ TrafficCounters mTrafficCounters;
+};
+
+// 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 {
+ ON_CONNECTION,
+ ON_DISCONNECTED,
+ ON_CHANNEL_CREATED,
+ ON_DATA_STRING,
+ ON_DATA_BINARY,
+ }; /* types */
+
+ DataChannelOnMessageAvailable(
+ int32_t aType, DataChannelConnection* aConnection, DataChannel* aChannel,
+ nsCString& aData) // XXX this causes inefficiency
+ : Runnable("DataChannelOnMessageAvailable"),
+ mType(aType),
+ mChannel(aChannel),
+ mConnection(aConnection),
+ mData(aData) {}
+
+ DataChannelOnMessageAvailable(int32_t 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(int32_t aType,
+ DataChannelConnection* aConnection,
+ DataChannel* aChannel)
+ : Runnable("DataChannelOnMessageAvailable"),
+ mType(aType),
+ mChannel(aChannel),
+ mConnection(aConnection) {}
+
+ // for ON_CONNECTION/ON_DISCONNECTED
+ DataChannelOnMessageAvailable(int32_t aType,
+ DataChannelConnection* aConnection)
+ : Runnable("DataChannelOnMessageAvailable"),
+ mType(aType),
+ mConnection(aConnection) {}
+
+ NS_IMETHOD Run() override {
+ 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 ON_DATA_STRING:
+ case ON_DATA_BINARY:
+ if (!mChannel->mListener) {
+ DC_ERROR(("DataChannelOnMessageAvailable (%d) with null Listener!",
+ mType));
+ return NS_OK;
+ }
+
+ if (mChannel->GetReadyState() == DataChannel::CLOSED ||
+ mChannel->GetReadyState() == DataChannel::CLOSING) {
+ // Closed by JS, probably
+ return NS_OK;
+ }
+
+ if (mType == ON_DATA_STRING) {
+ mChannel->mListener->OnMessageAvailable(mChannel->mContext, mData);
+ } else {
+ mChannel->mListener->OnBinaryMessageAvailable(mChannel->mContext,
+ mData);
+ }
+ break;
+ case ON_DISCONNECTED:
+ // If we've disconnected, make sure we close all the streams - from
+ // mainthread!
+ mConnection->CloseAll();
+ break;
+ case ON_CHANNEL_CREATED:
+ if (!mConnection->mListener) {
+ DC_ERROR(("DataChannelOnMessageAvailable (%d) with null Listener!",
+ mType));
+ return NS_OK;
+ }
+
+ // important to give it an already_AddRefed pointer!
+ mConnection->mListener->NotifyDataChannel(mChannel.forget());
+ break;
+ case ON_CONNECTION:
+ // TODO: Notify someday? How? What does the spec say about this?
+ break;
+ }
+ return NS_OK;
+ }
+
+ private:
+ ~DataChannelOnMessageAvailable() = default;
+
+ int32_t 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..e5aeb6ab94
--- /dev/null
+++ b/netwerk/sctp/datachannel/DataChannelProtocol.h
@@ -0,0 +1,87 @@
+/* -*- 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_MAX_BINARY_FRAGMENT 0x4000
+
+#define DATA_CHANNEL_FLAGS_FINISH_OPEN 0x00000004
+#define DATA_CHANNEL_FLAGS_WAITING_ACK 0x00000010
+#define DATA_CHANNEL_FLAGS_CLOSING_TOO_LARGE 0x00000020
+
+#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..27a60d88bd
--- /dev/null
+++ b/netwerk/sctp/datachannel/moz.build
@@ -0,0 +1,37 @@
+# -*- 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",
+]
+
+DEFINES["SCTP_DEBUG"] = 1
+
+if CONFIG["OS_TARGET"] == "WINNT":
+ DEFINES["__Userspace_os_Windows"] = 1
+else:
+ DEFINES["__Userspace_os_%s" % CONFIG["OS_TARGET"]] = 1
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/netwerk/sctp/sctp_update.log b/netwerk/sctp/sctp_update.log
new file mode 100644
index 0000000000..935bf11c27
--- /dev/null
+++ b/netwerk/sctp/sctp_update.log
@@ -0,0 +1,20 @@
+sctp updated from CVS on Tue Mar 20 01:04:08 EDT 2012
+sctp updated from CVS on Fri Jun 1 01:31:43 EDT 2012
+sctp updated from CVS on Tue Jul 10 12:30:59 EDT 2012
+sctp updated from CVS on Thu Jul 12 00:31:32 EDT 2012
+sctp updated from CVS on Tue Jul 24 15:38:37 EDT 2012
+sctp updated from CVS on Sun Aug 5 03:51:50 EDT 2012
+sctp updated from CVS on Mon Aug 6 04:17:12 EDT 2012
+sctp updated to version 8119 from SVN on Wed Aug 29 21:52:12 EDT 2012
+sctp updated to version 8131 from SVN on Tue Sep 4 00:26:11 EDT 2012
+sctp updated to version 8165 from SVN on Wed Sep 5 09:39:43 EDT 2012
+sctp updated to version 8176 from SVN on Wed Sep 5 18:02:08 EDT 2012
+sctp updated to version 8263 from SVN on Sun Sep 16 00:48:48 EDT 2012
+sctp updated to version 8279 from SVN on Thu Sep 20 18:19:24 EDT 2012
+sctp updated to version 8397 from SVN on Wed Jan 9 00:41:16 EST 2013
+sctp updated to version 8443 from SVN on Sun Mar 31 09:05:07 EDT 2013
+sctp updated to version 8815 from SVN on Tue Mar 4 08:50:51 EST 2014
+sctp updated to version 9168 from SVN on Tue Mar 3 12:11:40 EST 2015
+sctp updated to version 9209 from SVN on Tue Mar 24 18:11:59 EDT 2015
+sctp updated to version 0e076261b832121cf120ddc04aaff87ac3a34d30 from git on Tue Nov 28 15:20:51 EST 2017
+sctp updated to version ea345b6d0c8a0f8701cf49445dba5ec8d34e2305 from git on Tue 30 Jun 14:01:18 CEST 2020
diff --git a/netwerk/sctp/src/LocalArray.h b/netwerk/sctp/src/LocalArray.h
new file mode 100644
index 0000000000..2ab708affc
--- /dev/null
+++ b/netwerk/sctp/src/LocalArray.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LOCAL_ARRAY_H_included
+#define LOCAL_ARRAY_H_included
+
+#include <cstddef>
+#include <new>
+
+/**
+ * A fixed-size array with a size hint. That number of bytes will be allocated
+ * on the stack, and used if possible, but if more bytes are requested at
+ * construction time, a buffer will be allocated on the heap (and deallocated
+ * by the destructor).
+ *
+ * The API is intended to be a compatible subset of C++0x's std::array.
+ */
+template <size_t STACK_BYTE_COUNT>
+class LocalArray {
+public:
+ /**
+ * Allocates a new fixed-size array of the given size. If this size is
+ * less than or equal to the template parameter STACK_BYTE_COUNT, an
+ * internal on-stack buffer will be used. Otherwise a heap buffer will
+ * be allocated.
+ */
+ LocalArray(size_t desiredByteCount) : mSize(desiredByteCount) {
+ if (desiredByteCount > STACK_BYTE_COUNT) {
+ mPtr = new char[mSize];
+ } else {
+ mPtr = &mOnStackBuffer[0];
+ }
+ }
+
+ /**
+ * Frees the heap-allocated buffer, if there was one.
+ */
+ ~LocalArray() {
+ if (mPtr != &mOnStackBuffer[0]) {
+ delete[] mPtr;
+ }
+ }
+
+ // Capacity.
+ size_t size() { return mSize; }
+ bool empty() { return mSize == 0; }
+
+ // Element access.
+ char& operator[](size_t n) { return mPtr[n]; }
+ const char& operator[](size_t n) const { return mPtr[n]; }
+
+private:
+ char mOnStackBuffer[STACK_BYTE_COUNT];
+ char* mPtr;
+ size_t mSize;
+
+ // Disallow copy and assignment.
+ LocalArray(const LocalArray&);
+ void operator=(const LocalArray&);
+};
+
+#endif // LOCAL_ARRAY_H_included
diff --git a/netwerk/sctp/src/README.sctp b/netwerk/sctp/src/README.sctp
new file mode 100644
index 0000000000..14fbb7c695
--- /dev/null
+++ b/netwerk/sctp/src/README.sctp
@@ -0,0 +1,16 @@
+This code is imported from the usrsctp library via netwerk/sctp/update_sctp.sh.
+
+The project is accessed via:
+
+svn co --username your.username https://sctp-refimpl.googlecode.com/svn/trunk sctpSVN
+
+If you just want a read-only copy use
+
+svn co http://sctp-refimpl.googlecode.com/svn/trunk sctpSVN
+
+The usrsctp library is based on the FreeBSD kernel implementation, and the
+userspace implementation was largely done my Michael Tuexen and Irene
+Rungler of the Munster University of Applied Sciences. Thanks also to
+Randall Stewart for his help.
+
+The library is covered by a BSD license - see headers in files.
diff --git a/netwerk/sctp/src/ScopedFd.h b/netwerk/sctp/src/ScopedFd.h
new file mode 100644
index 0000000000..d2b7935fab
--- /dev/null
+++ b/netwerk/sctp/src/ScopedFd.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SCOPED_FD_H_included
+#define SCOPED_FD_H_included
+
+#include <unistd.h>
+
+// A smart pointer that closes the given fd on going out of scope.
+// Use this when the fd is incidental to the purpose of your function,
+// but needs to be cleaned up on exit.
+class ScopedFd {
+public:
+ explicit ScopedFd(int fd) : fd(fd) {
+ }
+
+ ~ScopedFd() {
+ close(fd);
+ }
+
+ int get() const {
+ return fd;
+ }
+
+private:
+ int fd;
+
+ // Disallow copy and assignment.
+ ScopedFd(const ScopedFd&);
+ void operator=(const ScopedFd&);
+};
+
+#endif // SCOPED_FD_H_included
diff --git a/netwerk/sctp/src/ifaddrs-android-ext.h b/netwerk/sctp/src/ifaddrs-android-ext.h
new file mode 100644
index 0000000000..abddae735b
--- /dev/null
+++ b/netwerk/sctp/src/ifaddrs-android-ext.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IFADDRS_ANDROID_EXT_H_included
+#define IFADDRS_ANDROID_EXT_H_included
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdio.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+// Android (bionic) doesn't have getifaddrs(3)/freeifaddrs(3).
+// We fake it here, so java_net_NetworkInterface.cpp can use that API
+// with all the non-portable code being in this file.
+
+// Source-compatible subset of the BSD struct.
+typedef struct ifaddrs {
+ // Pointer to next struct in list, or NULL at end.
+ struct ifaddrs* ifa_next;
+
+ // Interface name.
+ char* ifa_name;
+
+ // Interface flags.
+ unsigned int ifa_flags;
+
+ // Interface network address.
+ struct sockaddr* ifa_addr;
+
+ // Interface netmask.
+ struct sockaddr* ifa_netmask;
+} ifaddrs;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ int getifaddrs(ifaddrs** result);
+ void freeifaddrs(ifaddrs* addresses);
+#ifdef __cplusplus
+}
+#endif
+
+#endif // IFADDRS_ANDROID_H_included
diff --git a/netwerk/sctp/src/ifaddrs_android.cpp b/netwerk/sctp/src/ifaddrs_android.cpp
new file mode 100644
index 0000000000..78eb90a1a6
--- /dev/null
+++ b/netwerk/sctp/src/ifaddrs_android.cpp
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ifaddrs-android-ext.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include "ScopedFd.h"
+#include "LocalArray.h"
+
+// Returns a pointer to the first byte in the address data (which is
+// stored in network byte order).
+uint8_t* sockaddrBytes(int family, sockaddr_storage* ss) {
+ if (family == AF_INET) {
+ sockaddr_in* ss4 = reinterpret_cast<sockaddr_in*>(ss);
+ return reinterpret_cast<uint8_t*>(&ss4->sin_addr);
+ } else if (family == AF_INET6) {
+ sockaddr_in6* ss6 = reinterpret_cast<sockaddr_in6*>(ss);
+ return reinterpret_cast<uint8_t*>(&ss6->sin6_addr);
+ }
+ return NULL;
+}
+
+// Sadly, we can't keep the interface index for portability with BSD.
+// We'll have to keep the name instead, and re-query the index when
+// we need it later.
+bool ifa_setNameAndFlagsByIndex(ifaddrs *self, int interfaceIndex) {
+ // Get the name.
+ char buf[IFNAMSIZ];
+ char* name = if_indextoname(interfaceIndex, buf);
+ if (name == NULL) {
+ return false;
+ }
+ self->ifa_name = new char[strlen(name) + 1];
+ strcpy(self->ifa_name, name);
+
+ // Get the flags.
+ ScopedFd fd(socket(AF_INET, SOCK_DGRAM, 0));
+ if (fd.get() == -1) {
+ return false;
+ }
+ ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ strcpy(ifr.ifr_name, name);
+ int rc = ioctl(fd.get(), SIOCGIFFLAGS, &ifr);
+ if (rc == -1) {
+ return false;
+ }
+ self->ifa_flags = ifr.ifr_flags;
+ return true;
+}
+
+// Netlink gives us the address family in the header, and the
+// sockaddr_in or sockaddr_in6 bytes as the payload. We need to
+// stitch the two bits together into the sockaddr that's part of
+// our portable interface.
+void ifa_setAddress(ifaddrs *self, int family, void* data, size_t byteCount) {
+ // Set the address proper...
+ sockaddr_storage* ss = new sockaddr_storage;
+ memset(ss, 0, sizeof(*ss));
+ self->ifa_addr = reinterpret_cast<sockaddr*>(ss);
+ ss->ss_family = family;
+ uint8_t* dst = sockaddrBytes(family, ss);
+ memcpy(dst, data, byteCount);
+}
+
+// Netlink gives us the prefix length as a bit count. We need to turn
+// that into a BSD-compatible netmask represented by a sockaddr*.
+void ifa_setNetmask(ifaddrs *self, int family, size_t prefixLength) {
+ // ...and work out the netmask from the prefix length.
+ sockaddr_storage* ss = new sockaddr_storage;
+ memset(ss, 0, sizeof(*ss));
+ self->ifa_netmask = reinterpret_cast<sockaddr*>(ss);
+ ss->ss_family = family;
+ uint8_t* dst = sockaddrBytes(family, ss);
+ memset(dst, 0xff, prefixLength / 8);
+ if ((prefixLength % 8) != 0) {
+ dst[prefixLength/8] = (0xff << (8 - (prefixLength % 8)));
+ }
+}
+
+// FIXME: use iovec instead.
+struct addrReq_struct {
+ nlmsghdr netlinkHeader;
+ ifaddrmsg msg;
+};
+
+inline bool sendNetlinkMessage(int fd, const void* data, size_t byteCount) {
+ ssize_t sentByteCount = TEMP_FAILURE_RETRY(send(fd, data, byteCount, 0));
+ return (sentByteCount == static_cast<ssize_t>(byteCount));
+}
+
+inline ssize_t recvNetlinkMessage(int fd, char* buf, size_t byteCount) {
+ return TEMP_FAILURE_RETRY(recv(fd, buf, byteCount, 0));
+}
+
+// Source-compatible with the BSD function.
+int getifaddrs(ifaddrs** result)
+{
+ // Simplify cleanup for callers.
+ *result = NULL;
+
+ // Create a netlink socket.
+ ScopedFd fd(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE));
+ if (fd.get() < 0) {
+ return -1;
+ }
+
+ // Ask for the address information.
+ addrReq_struct addrRequest;
+ memset(&addrRequest, 0, sizeof(addrRequest));
+ addrRequest.netlinkHeader.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH;
+ addrRequest.netlinkHeader.nlmsg_type = RTM_GETADDR;
+ addrRequest.netlinkHeader.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(addrRequest)));
+ addrRequest.msg.ifa_family = AF_UNSPEC; // All families.
+ addrRequest.msg.ifa_index = 0; // All interfaces.
+ if (!sendNetlinkMessage(fd.get(), &addrRequest, addrRequest.netlinkHeader.nlmsg_len)) {
+ return -1;
+ }
+
+ // Read the responses.
+ LocalArray<0> buf(65536); // We don't necessarily have std::vector.
+ ssize_t bytesRead;
+ while ((bytesRead = recvNetlinkMessage(fd.get(), &buf[0], buf.size())) > 0) {
+ nlmsghdr* hdr = reinterpret_cast<nlmsghdr*>(&buf[0]);
+ for (; NLMSG_OK(hdr, (size_t)bytesRead); hdr = NLMSG_NEXT(hdr, bytesRead)) {
+ switch (hdr->nlmsg_type) {
+ case NLMSG_DONE:
+ return 0;
+ case NLMSG_ERROR:
+ return -1;
+ case RTM_NEWADDR:
+ {
+ ifaddrmsg* address = reinterpret_cast<ifaddrmsg*>(NLMSG_DATA(hdr));
+ rtattr* rta = IFA_RTA(address);
+ size_t ifaPayloadLength = IFA_PAYLOAD(hdr);
+ while (RTA_OK(rta, ifaPayloadLength)) {
+ if (rta->rta_type == IFA_LOCAL) {
+ int family = address->ifa_family;
+ if (family == AF_INET || family == AF_INET6) {
+ ifaddrs *next = *result;
+ *result = new ifaddrs;
+ memset(*result, 0, sizeof(ifaddrs));
+ (*result)->ifa_next = next;
+ if (!ifa_setNameAndFlagsByIndex(*result, address->ifa_index)) {
+ return -1;
+ }
+ ifa_setAddress(*result, family, RTA_DATA(rta), RTA_PAYLOAD(rta));
+ ifa_setNetmask(*result, family, address->ifa_prefixlen);
+ }
+ }
+ rta = RTA_NEXT(rta, ifaPayloadLength);
+ }
+ }
+ break;
+ }
+ }
+ }
+ // We only get here if recv fails before we see a NLMSG_DONE.
+ return -1;
+}
+
+// Source-compatible with the BSD function.
+void freeifaddrs(ifaddrs* addresses) {
+ ifaddrs* self = addresses;
+ while (self != NULL) {
+ delete[] self->ifa_name;
+ delete self->ifa_addr;
+ delete self->ifa_netmask;
+ ifaddrs* next = self->ifa_next;
+ delete self;
+ self = next;
+ }
+}
diff --git a/netwerk/sctp/src/moz.build b/netwerk/sctp/src/moz.build
new file mode 100644
index 0000000000..45d4e82ad2
--- /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',
+]
+
+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/netinet/sctp.h b/netwerk/sctp/src/netinet/sctp.h
new file mode 100755
index 0000000000..8d8bf31fe8
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp.h
@@ -0,0 +1,672 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp.h 356357 2020-01-04 20:33:12Z tuexen $");
+#endif
+
+#ifndef _NETINET_SCTP_H_
+#define _NETINET_SCTP_H_
+
+#if defined(__APPLE__) || defined(__linux__)
+#include <stdint.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
+
+/*
+ * 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_PLUGGABLE_SS 0x00001203
+#define SCTP_SS_VALUE 0x00001204
+#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_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
+
+
+/* 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_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 100755
index 0000000000..9e14fc8cf3
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_asconf.c
@@ -0,0 +1,3548 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_asconf.c 362377 2020-06-19 12:35:29Z tuexen $");
+#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)) &&
+ (stcb->asoc.alternate)) {
+ 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_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 exsist 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, 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_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 unspecifed 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 unspecifed 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 unspecifed 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 unspecifed 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;
+ break;
+ }
+
+ 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 unspecifed 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 unspecifed 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;
+ 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);
+
+ /*
+ * 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,
+ "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,
+ "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) ||
+ (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:
+ p_size = 0;
+ addr_size = 0;
+ addr_ptr = NULL;
+ break;
+ }
+ 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,
+ "compose_asconf: no lookup addr!\n");
+ /* XXX for now, we send a IPv4 address of 0.0.0.0 */
+ lookup->ph.param_type = htons(SCTP_IPV4_ADDRESS);
+ lookup->ph.param_length = htons(SCTP_SIZE32(sizeof(struct sctp_ipv4addr_param)));
+ memset(lookup->addr, 0, sizeof(struct in_addr));
+ SCTP_BUF_LEN(m_asconf_chk) += SCTP_SIZE32(sizeof(struct sctp_ipv4addr_param));
+ }
+ }
+ /* 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;
+ 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:
+ * - vtagparam(my_vtag/peer_vtag)
+ * - add(0.0.0.0)
+ * - del(0.0.0.0)
+ * - Any global addresses add(addr)
+ */
+ 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_send_nat_state_update: failed to get memory!\n");
+ return;
+ }
+ aa->special_del = 0;
+ /* fill in asconf address parameter fields */
+ /* top level elements are "networked" during send */
+ aa->ifa = NULL;
+ aa->sent = 0; /* clear sent flag */
+ vtag = (struct sctp_asconf_tag_param *)&aa->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);
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa, next);
+
+ 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_send_nat_state_update: failed to get memory!\n");
+ return;
+ }
+ memset(aa, 0, sizeof(struct sctp_asconf_addr));
+ /* fill in asconf address parameter fields */
+ /* ADD(0.0.0.0) */
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ aa->ap.aph.ph.param_type = SCTP_ADD_IP_ADDRESS;
+ aa->ap.aph.ph.param_length = sizeof(struct sctp_asconf_addrv4_param);
+ aa->ap.addrp.ph.param_type = SCTP_IPV4_ADDRESS;
+ aa->ap.addrp.ph.param_length = sizeof (struct sctp_ipv4addr_param);
+ /* No need to add an address, we are using 0.0.0.0 */
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa, next);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ aa->ap.aph.ph.param_type = SCTP_ADD_IP_ADDRESS;
+ aa->ap.aph.ph.param_length = sizeof(struct sctp_asconf_addr_param);
+ aa->ap.addrp.ph.param_type = SCTP_IPV6_ADDRESS;
+ aa->ap.addrp.ph.param_length = sizeof (struct sctp_ipv6addr_param);
+ /* No need to add an address, we are using 0.0.0.0 */
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa, next);
+ break;
+#endif
+ default:
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "sctp_asconf_send_nat_state_update: unknown address family\n");
+ SCTP_FREE(aa, SCTP_M_ASC_ADDR);
+ return;
+ }
+ 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_send_nat_state_update: failed to get memory!\n");
+ return;
+ }
+ memset(aa, 0, sizeof(struct sctp_asconf_addr));
+ /* fill in asconf address parameter fields */
+ /* ADD(0.0.0.0) */
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ aa->ap.aph.ph.param_type = SCTP_ADD_IP_ADDRESS;
+ aa->ap.aph.ph.param_length = sizeof(struct sctp_asconf_addrv4_param);
+ aa->ap.addrp.ph.param_type = SCTP_IPV4_ADDRESS;
+ aa->ap.addrp.ph.param_length = sizeof (struct sctp_ipv4addr_param);
+ /* No need to add an address, we are using 0.0.0.0 */
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa, next);
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ aa->ap.aph.ph.param_type = SCTP_DEL_IP_ADDRESS;
+ aa->ap.aph.ph.param_length = sizeof(struct sctp_asconf_addr_param);
+ aa->ap.addrp.ph.param_type = SCTP_IPV6_ADDRESS;
+ aa->ap.addrp.ph.param_length = sizeof (struct sctp_ipv6addr_param);
+ /* No need to add an address, we are using 0.0.0.0 */
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa, next);
+ break;
+#endif
+ default:
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "sctp_asconf_send_nat_state_update: unknown address family\n");
+ SCTP_FREE(aa, SCTP_M_ASC_ADDR);
+ return;
+ }
+ /* 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 100755
index 0000000000..e171a0b561
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_asconf.h
@@ -0,0 +1,96 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_asconf.h 362377 2020-06-19 12:35:29Z tuexen $");
+#endif
+
+#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 100755
index 0000000000..3a8fac0731
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_auth.c
@@ -0,0 +1,2314 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_auth.c 362054 2020-06-11 13:34:09Z tuexen $");
+#endif
+
+#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,
+ key_id, 0, 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, keyid, 0,
+ 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__)
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ return (0);
+#endif
+#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,
+ * shared_key_id, (void
+ * *)stcb->asoc.authinfo.recv_keyid);
+ */
+ sctp_notify_authentication(stcb, SCTP_AUTH_NEW_KEY,
+ shared_key_id, stcb->asoc.authinfo.recv_keyid,
+ 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, uint16_t alt_keyid, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_authkey_event *auth;
+ struct sctp_queued_to_read *control;
+
+ if ((stcb == NULL) ||
+ (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)
+ ) {
+ /* If the socket is gone we are out of here */
+ return;
+ }
+
+ 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;
+ auth->auth_altkeynumber = alt_keyid;
+ 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_NOT_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 100755
index 0000000000..3dc5a59e28
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_auth.h
@@ -0,0 +1,216 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_auth.h 338749 2018-09-18 10:53:07Z tuexen $");
+#endif
+
+#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, uint16_t alt_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 100755
index 0000000000..40e308f8f2
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_bsd_addr.c
@@ -0,0 +1,996 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_bsd_addr.c 358080 2020-02-18 19:41:55Z tuexen $");
+#endif
+
+#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,
+ RFPROC,
+ 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__)
+#ifdef MALLOC
+#undef MALLOC
+#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
+#endif
+#ifdef FREE
+#undef FREE
+#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
+#endif
+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);
+ 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;
+ }
+ }
+ }
+ }
+ 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);
+ 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;
+ }
+ }
+ }
+ }
+ 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 unspecifed 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 unspecifed 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 unspecifed 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 unspecifed 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", __FUNCTION__));
+ }
+#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 tocopy, this_copy;
+ int *lenat;
+ int did_delay = 0;
+
+ tocopy = length;
+ 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 100755
index 0000000000..05d1b3feef
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_bsd_addr.h
@@ -0,0 +1,73 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_bsd_addr.h 353480 2019-10-13 18:17:08Z tuexen $");
+#endif
+
+#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 100755
index 0000000000..4c9be7568b
--- /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)) {
+ 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 100755
index 0000000000..7d6b419c97
--- /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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#endif
+
+#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
+#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 100755
index 0000000000..dcf6298579
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_cc_functions.c
@@ -0,0 +1,2508 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_cc_functions.c 359405 2020-03-28 20:25:45Z tuexen $");
+#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_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
+
+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, probepoint;
+#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, probepoint;
+#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, probepoint;
+
+#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, 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;
+#endif
+ uint32_t t_ssthresh, t_cwnd, 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_cwnd = 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;
+ t_cwnd += net->cwnd;
+ /* 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, probepoint;
+
+ 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;
+
+ 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 measurment 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, probepoint;
+
+#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, probepoint;
+
+#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)
+ factor = 1;
+ }
+
+ ca->alpha = 2*factor*((1<<7)-ca->beta);
+ if (!ca->alpha)
+ 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 100755
index 0000000000..efda7ac9d1
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_constants.h
@@ -0,0 +1,1077 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_constants.h 362107 2020-06-12 16:40:10Z tuexen $");
+#endif
+
+#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
+
+/* 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 (3000) /* 3 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_DEFAULT_MAXSEGMENT 65535
+
+#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_AUTH_NEW_KEY 23
+#define SCTP_NOTIFY_AUTH_FREE_KEY 24
+#define SCTP_NOTIFY_NO_PEER_AUTH 25
+#define SCTP_NOTIFY_SENDER_DRY 26
+#define SCTP_NOTIFY_REMOTE_ERROR 27
+
+/* 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 */
+#define SCTP_DIAG_INFO_LEN 128
+
+/* 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
+
+/* 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) { \
+ inp->sctp_flags |= 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) { \
+ SOCKBUF_UNLOCK(&((so)->so_snd)); \
+ inp->sctp_flags |= SCTP_PCB_FLAGS_WAKEOUTPUT; \
+ } else { \
+ sowwakeup_locked(so); \
+ } \
+} while (0)
+#else
+#define sctp_sowwakeup_locked(inp, so) \
+do { \
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) { \
+ SOCKBUF_UNLOCK(&((so)->so_snd)); \
+ inp->sctp_flags |= SCTP_PCB_FLAGS_WAKEOUTPUT; \
+ } else { \
+ sowwakeup(so); \
+ } \
+} while (0)
+#endif
+
+#define sctp_sorwakeup(inp, so) \
+do { \
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) { \
+ inp->sctp_flags |= 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) { \
+ inp->sctp_flags |= 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) { \
+ inp->sctp_flags |= 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 100755
index 0000000000..0b5a06e067
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_crc32.c
@@ -0,0 +1,831 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_crc32.c 362498 2020-06-22 14:36:14Z tuexen $");
+
+#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)
+{
+ uint32_t result;
+#if BYTE_ORDER == BIG_ENDIAN
+ uint8_t byte0, byte1, byte2, byte3;
+#endif
+
+ /* Complement the result */
+ result = ~crc32c;
+#if BYTE_ORDER == BIG_ENDIAN
+ /*
+ * For BIG-ENDIAN platforms the result is in little-endian form. So we
+ * must swap the bytes to return the result in network byte order.
+ */
+ byte0 = result & 0x000000ff;
+ byte1 = (result >> 8) & 0x000000ff;
+ byte2 = (result >> 16) & 0x000000ff;
+ byte3 = (result >> 24) & 0x000000ff;
+ crc32c = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3);
+#else
+ /*
+ * For LITTLE ENDIAN platforms the result is in already in network
+ * byte order.
+ */
+ crc32c = result;
+#endif
+ return (crc32c);
+}
+
+/*
+ * 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, uint32_t offset)
+{
+ uint32_t base = 0xffffffff;
+
+ while (offset > 0) {
+ KASSERT(m != NULL, ("sctp_calculate_cksum, offset > length of mbuf chain"));
+ if (offset < (uint32_t)m->m_len) {
+ break;
+ }
+ offset -= m->m_len;
+ m = m->m_next;
+ }
+ if (offset > 0) {
+ base = calculate_crc32c(base,
+ (unsigned char *)(m->m_data + offset),
+ (unsigned int)(m->m_len - offset));
+ m = m->m_next;
+ }
+ while (m != NULL) {
+ base = calculate_crc32c(base,
+ (unsigned char *)m->m_data,
+ (unsigned int)m->m_len);
+ m = m->m_next;
+ }
+ base = sctp_finalize_crc32c(base);
+ return (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 100755
index 0000000000..5e76ff21bf
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_crc32.h
@@ -0,0 +1,56 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_crc32.h 362338 2020-06-18 19:32:34Z markj $");
+#endif
+
+#ifndef _NETINET_SCTP_CRC32_H_
+#define _NETINET_SCTP_CRC32_H_
+
+#if defined(_KERNEL)
+uint32_t sctp_calculate_cksum(struct mbuf *, uint32_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 *, uint32_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 100755
index 0000000000..602921d0e2
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_header.h
@@ -0,0 +1,611 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_header.h 309682 2016-12-07 19:30:59Z tuexen $");
+#endif
+
+#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 5
+#else
+#define SCTP_RESERVE_SPACE 6
+#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 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;
+
+/*
+ * 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 100755
index 0000000000..256bd9ab0f
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_indata.c
@@ -0,0 +1,5819 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_indata.c 363076 2020-07-10 11:15:10Z tuexen $");
+#endif
+
+#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, cumackp1;
+ int fnd = 0;
+ int in_r=0, in_nr=0;
+ if (SCTP_BASE_SYSCTL(sctp_do_drain) == 0) {
+ return;
+ }
+ cumackp1 = asoc->cumulative_tsn + 1;
+ if (SCTP_TSN_GT(cumackp1, 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);
+ if ((in_r == 0) && (in_nr == 0)) {
+#ifdef INVARIANTS
+ panic("Things are really messed up now");
+#else
+ SCTP_PRINTF("gap:%x tsn:%x\n", gap, tsn);
+ sctp_print_mapping_array(asoc);
+#endif
+ }
+ if (in_nr == 0)
+ SCTP_SET_TSN_PRESENT(asoc->nr_mapping_array, gap);
+ if (in_r)
+ SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap);
+ if (SCTP_TSN_GT(tsn, asoc->highest_tsn_inside_nr_map)) {
+ asoc->highest_tsn_inside_nr_map = tsn;
+ }
+ 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;
+ fnd = 1;
+ break;
+ }
+ }
+ if (!fnd) {
+ 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, 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 hybred 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, 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, 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, SCTP_READ_LOCK_NOT_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, SCTP_READ_LOCK_NOT_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 completly 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;
+ int i_locked = 0;
+
+ if (control->on_read_q && (hold_rlock == 0)) {
+ /*
+ * Its being pd-api'd so we must
+ * do some locks.
+ */
+ SCTP_INP_READ_LOCK(stcb->sctp_ep);
+ i_locked = 1;
+ }
+ 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;
+ }
+ 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)) {
+ /*
+ * 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_14);
+ 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_15;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, 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, 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_16;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, 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 (stcb->sctp_socket->so_rcv.sb_cc) {
+ /* 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_17;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, 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_18);
+ }
+ 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_19);
+ 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", "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_20;
+ sctp_abort_an_association(inp, stcb, op_err, 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", "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, 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_22;
+ sctp_abort_an_association(inp, stcb, op_err, 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, 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_23;
+ sctp_abort_an_association(inp, stcb, op_err, 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;
+ int tot_retrans = 0;
+ 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 receieved 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 receieved 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;
+ tot_retrans++;
+ /* 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? \n");
+#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_24;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, 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);
+ 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)) {
+ /* 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_25);
+ 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_26);
+ }
+ }
+ }
+ 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_27;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, 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 consequtive 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_28;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, 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_29);
+ 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-
+ * amibguious 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_30);
+
+ }
+ } 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_31);
+ }
+ }
+ }
+ /********************************************/
+ /* 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);
+ 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)) {
+ /* 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_32);
+ 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_33);
+ 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_34;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, 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_35);
+ }
+ }
+ }
+ 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,
+ uint16_t stream, uint32_t mid, int ordered, uint32_t cumtsn)
+{
+ struct sctp_queued_to_read *control;
+ struct sctp_stream_in *strm;
+ struct sctp_tmit_chunk *chk, *nchk;
+ int cnt_removed=0;
+
+ /*
+ * 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.
+ */
+ strm = &asoc->strmin[stream];
+ control = sctp_find_reasm_entry(strm, mid, ordered, asoc->idata_supported);
+ if (control == NULL) {
+ /* Not found */
+ return;
+ }
+ if (!asoc->idata_supported && !ordered && 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 == 0)) {
+ if (SCTP_TSN_GT(chk->rec.data.tsn, cumtsn)) {
+ break;
+ }
+ }
+ cnt_removed++;
+ 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;
+ uint32_t str_seq;
+ struct sctp_stream_in *strm;
+ struct sctp_queued_to_read *control, *sv;
+
+ 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_36;
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, 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++) {
+ sctp_flush_reassm_for_str_seq(stcb, asoc, sid, 0, 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, cur_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];
+ for (cur_mid = strm->last_mid_delivered; SCTP_MID_GE(asoc->idata_supported, mid, cur_mid); cur_mid++) {
+ sctp_flush_reassm_for_str_seq(stcb, asoc, sid, cur_mid, 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))) {
+ str_seq = (sid << 16) | (0x0000ffff & mid);
+ control->pdapi_aborted = 1;
+ sv = stcb->asoc.control_pdapi;
+ 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;
+ stcb->asoc.control_pdapi = control;
+ sctp_ulp_notify(SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION,
+ stcb,
+ SCTP_PARTIAL_DELIVERY_ABORTED,
+ (void *)&str_seq,
+ SCTP_SO_NOT_LOCKED);
+ stcb->asoc.control_pdapi = sv;
+ 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 100755
index 0000000000..829a014f2d
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_indata.h
@@ -0,0 +1,124 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_indata.h 361116 2020-05-16 19:26:39Z tuexen $");
+#endif
+
+#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 100755
index 0000000000..46c196002e
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_input.c
@@ -0,0 +1,6450 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_input.c 362153 2020-06-13 18:38:59Z tuexen $");
+#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_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, int *abort_no_unlock,
+#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 length */
+ if (ntohs(cp->ch.chunk_length) < sizeof(struct sctp_init_chunk)) {
+ op_err = sctp_generate_cause(SCTP_CAUSE_INVALID_PARAM, "");
+ sctp_abort_association(inp, stcb, m, iphlen, src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ if (stcb)
+ *abort_no_unlock = 1;
+ goto outnow;
+ }
+ /* validate parameters */
+ init = &cp->init;
+ if (init->initiate_tag == 0) {
+ /* protocol error... send abort */
+ op_err = sctp_generate_cause(SCTP_CAUSE_INVALID_PARAM, "");
+ sctp_abort_association(inp, stcb, m, iphlen, src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ if (stcb)
+ *abort_no_unlock = 1;
+ goto outnow;
+ }
+ if (ntohl(init->a_rwnd) < SCTP_MIN_RWND) {
+ /* invalid parameter... send abort */
+ op_err = sctp_generate_cause(SCTP_CAUSE_INVALID_PARAM, "");
+ sctp_abort_association(inp, stcb, m, iphlen, src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ if (stcb)
+ *abort_no_unlock = 1;
+ goto outnow;
+ }
+ if (init->num_inbound_streams == 0) {
+ /* protocol error... send abort */
+ op_err = sctp_generate_cause(SCTP_CAUSE_INVALID_PARAM, "");
+ sctp_abort_association(inp, stcb, m, iphlen, src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ if (stcb)
+ *abort_no_unlock = 1;
+ goto outnow;
+ }
+ if (init->num_outbound_streams == 0) {
+ /* protocol error... send abort */
+ op_err = sctp_generate_cause(SCTP_CAUSE_INVALID_PARAM, "");
+ sctp_abort_association(inp, stcb, m, iphlen, src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ if (stcb)
+ *abort_no_unlock = 1;
+ 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_abort_association(inp, stcb, m, iphlen, src, dst, sh, op_err,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ if (stcb)
+ *abort_no_unlock = 1;
+ 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;
+
+ /* 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;
+ SCTP_TCB_SEND_LOCK(stcb);
+ 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, 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);
+ if (!TAILQ_EMPTY(&stcb->asoc.strmout[i].outqueue)) {
+ unsent_data++;
+ }
+ } else {
+ unsent_data++;
+ }
+ if (unsent_data > 0) {
+ break;
+ }
+ }
+ }
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ 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;
+
+ 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);
+ }
+
+ }
+ }
+ SCTP_TCB_SEND_LOCK(stcb);
+ 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, 1);
+ 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;
+ }
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ 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_an_association(stcb->sctp_ep, stcb, op_err, SCTP_SO_NOT_LOCKED);
+ *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 */
+ retval = sctp_process_init((struct sctp_init_chunk *)cp, stcb);
+ if (retval < 0) {
+ if (op_err != NULL) {
+ sctp_m_freem(op_err);
+ }
+ return (retval);
+ }
+ 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))) {
+ 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__);
+ }
+ stcb->asoc.overall_error_count = 0;
+ net->error_count = 0;
+
+ /*
+ * Cancel the INIT timer, We do this first before queueing the
+ * cookie. We always cancel at the primary to assue 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 */
+ sctp_calculate_rto(stcb, asoc, net, &asoc->time_entered,
+ SCTP_RTT_FROM_NON_DATA);
+#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);
+ 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)) {
+ 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)) {
+ new_vtag = sctp_select_a_tag(stcb->sctp_ep, stcb->sctp_ep->sctp_lport, stcb->rport, 1);
+ 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);
+ }
+ 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, 1, 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
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_WAS_ABORTED);
+ (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_handle_shutdown(struct sctp_shutdown_chunk *cp,
+ struct sctp_tcb *stcb, struct sctp_nets *net, int *abort_flag)
+{
+ struct sctp_association *asoc;
+ 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;
+ asoc = &stcb->asoc;
+ 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;
+ }
+ if (asoc->control_pdapi) {
+ /* With a normal shutdown
+ * we assume the end of last record.
+ */
+ SCTP_INP_READ_LOCK(stcb->sctp_ep);
+ if (asoc->control_pdapi->on_strm_q) {
+ struct sctp_stream_in *strm;
+
+ strm = &asoc->strmin[asoc->control_pdapi->sinfo_stream];
+ if (asoc->control_pdapi->on_strm_q == SCTP_ON_UNORDERED) {
+ /* Unordered */
+ TAILQ_REMOVE(&strm->uno_inqueue, asoc->control_pdapi, next_instrm);
+ asoc->control_pdapi->on_strm_q = 0;
+ } else if (asoc->control_pdapi->on_strm_q == SCTP_ON_ORDERED) {
+ /* Ordered */
+ TAILQ_REMOVE(&strm->inqueue, asoc->control_pdapi, next_instrm);
+ asoc->control_pdapi->on_strm_q = 0;
+#ifdef INVARIANTS
+ } else {
+ panic("Unknown state on ctrl:%p on_strm_q:%d",
+ asoc->control_pdapi,
+ asoc->control_pdapi->on_strm_q);
+#endif
+ }
+ }
+ asoc->control_pdapi->end_added = 1;
+ asoc->control_pdapi->pdapi_aborted = 1;
+ asoc->control_pdapi = NULL;
+ SCTP_INP_READ_UNLOCK(stcb->sctp_ep);
+#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
+ if (stcb->sctp_socket) {
+ sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket);
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+ /* goto SHUTDOWN_RECEIVED state to block new requests */
+ 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(&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(&asoc->send_queue) ||
+ !TAILQ_EMPTY(&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)
+{
+ struct sctp_association *asoc;
+#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;
+
+ asoc = &stcb->asoc;
+ /* 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;
+ }
+ if (asoc->control_pdapi) {
+ /* With a normal shutdown
+ * we assume the end of last record.
+ */
+ SCTP_INP_READ_LOCK(stcb->sctp_ep);
+ asoc->control_pdapi->end_added = 1;
+ asoc->control_pdapi->pdapi_aborted = 1;
+ asoc->control_pdapi = NULL;
+ SCTP_INP_READ_UNLOCK(stcb->sctp_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);
+ if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) {
+ /* assoc was freed while we were unlocked */
+ SCTP_SOCKET_UNLOCK(so, 1);
+ return;
+ }
+#endif
+ sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket);
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 1);
+#endif
+ }
+#ifdef INVARIANTS
+ if (!TAILQ_EMPTY(&asoc->send_queue) ||
+ !TAILQ_EMPTY(&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)) {
+ stcb->sctp_socket->so_snd.sb_cc = 0;
+ }
+ 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 abort 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 abort 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 sctp_error_stale_cookie *stale_cookie;
+
+ stale_cookie = (struct sctp_error_stale_cookie *)cause;
+ asoc->cookie_preserve_req = ntohl(stale_cookie->stale_time);
+ /* Double it to be more robust on RTX */
+ if (asoc->cookie_preserve_req <= UINT32_MAX / 2) {
+ asoc->cookie_preserve_req *= 2;
+ } else {
+ asoc->cookie_preserve_req = UINT32_MAX;
+ }
+ asoc->stale_cookie_count++;
+ if (asoc->stale_cookie_count >
+ asoc->max_init_times) {
+ sctp_abort_notification(stcb, 0, 0, NULL, SCTP_SO_NOT_LOCKED);
+ /* now free the asoc */
+#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);
+ }
+ /* blast back to INIT state */
+ sctp_toss_old_cookies(stcb, &stcb->asoc);
+ SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_WAIT);
+ sctp_stop_all_cookie_timers(stcb);
+ 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);
+ }
+ if (ntohs(cp->ch.chunk_length) < sizeof(struct sctp_init_ack_chunk)) {
+ /* Invalid length */
+ 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);
+ }
+ init_ack = &cp->init;
+ /* validate parameters */
+ if (init_ack->initiate_tag == 0) {
+ /* protocol error... send an abort */
+ 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 (ntohl(init_ack->a_rwnd) < SCTP_MIN_RWND) {
+ /* protocol error... send an abort */
+ 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 (init_ack->num_inbound_streams == 0) {
+ /* protocol error... send an abort */
+ 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 (init_ack->num_outbound_streams == 0) {
+ /* protocol error... send an abort */
+ 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);
+ }
+ /* process according to association state... */
+ switch (SCTP_GET_STATE(stcb)) {
+ case SCTP_STATE_COOKIE_WAIT:
+ /* this is the expected state for this chunk */
+ /* process the INIT-ACK parameters */
+ 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 calc */
+ 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 send at the end of the inbound data processing will
+ * cause the cookie to be sent
+ */
+ break;
+ case SCTP_STATE_SHUTDOWN_SENT:
+ /* incorrect state... discard */
+ break;
+ case SCTP_STATE_COOKIE_ECHOED:
+ /* incorrect state... discard */
+ break;
+ case SCTP_STATE_OPEN:
+ /* incorrect state... discard */
+ break;
+ case SCTP_STATE_EMPTY:
+ case SCTP_STATE_INUSE:
+ default:
+ /* incorrect state... discard */
+ return (-1);
+ break;
+ }
+ SCTPDBG(SCTP_DEBUG_INPUT1, "Leaving handle-init-ack end\n");
+ return (0);
+}
+
+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;
+ struct timeval old;
+ 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;
+ 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 */
+ return (NULL);
+ }
+ if (init_cp->ch.chunk_type != SCTP_INITIATION) {
+ 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 */
+ return (NULL);
+ }
+ if (initack_cp->ch.chunk_type != SCTP_INITIATION_ACK) {
+ 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;
+ 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 !! */
+ retval = sctp_process_init(init_cp, stcb);
+ if (retval < 0) {
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 3;
+ 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);
+ if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, NULL);
+ }
+ 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..
+ */
+ stcb->sctp_ep->sctp_flags |=
+ 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_add_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
+ }
+ /* notify upper layer */
+ *notification = SCTP_NOTIFY_ASSOC_UP;
+ /*
+ * since we did not send a HB make sure we
+ * don't double things
+ */
+ old.tv_sec = cookie->time_entered.tv_sec;
+ old.tv_usec = cookie->time_entered.tv_usec;
+ net->hb_responded = 1;
+ sctp_calculate_rto(stcb, asoc, net, &old,
+ SCTP_RTT_FROM_NON_DATA);
+
+ 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);
+ /*
+ * We ignore the return code here.. not sure if we should
+ * somehow abort.. but we do have an existing asoc. This
+ * really should not fail.
+ */
+ if (sctp_load_addresses_from_init(stcb, m,
+ init_offset + sizeof(struct sctp_init_chunk),
+ initack_offset, src, dst, init_src, stcb->asoc.port)) {
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 4;
+ 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;
+ 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);
+ 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 normnally 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;
+
+ 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) */
+ retval = sctp_process_init(init_cp, stcb);
+ if (retval < 0) {
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 9;
+ return (NULL);
+ }
+ if (sctp_load_addresses_from_init(stcb, m,
+ init_offset + sizeof(struct sctp_init_chunk),
+ initack_offset, src, dst, init_src, stcb->asoc.port)) {
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 10;
+ 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
+ stcb->sctp_ep->sctp_flags |=
+ 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_add_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
+ }
+ 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);
+ if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, NULL);
+ }
+ 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) {
+ /* This is a gross gross hack.
+ * Just call the cookie_new code since we
+ * are allowing a duplicate association.
+ * I hope this works...
+ */
+ return (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));
+ }
+ /*
+ * 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_timer_stop(SCTP_TIMER_TYPE_INIT, inp, stcb, net,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_16);
+ sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net,
+ SCTP_FROM_SCTP_INPUT + SCTP_LOC_17);
+
+ /* 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);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, NULL);
+
+ } 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;
+ 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_add_int(&stcb->asoc.refcnt, -1);
+ /* send up all the data */
+ SCTP_TCB_SEND_LOCK(stcb);
+
+ sctp_report_all_outbound(stcb, 0, 1, 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].sid = i;
+ stcb->asoc.strmout[i].next_mid_ordered = 0;
+ stcb->asoc.strmout[i].next_mid_unordered = 0;
+ 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();
+ }
+ 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);
+ }
+
+
+ /* 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_TCB_SEND_UNLOCK(stcb);
+ 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) */
+ retval = sctp_process_init(init_cp, stcb);
+ if (retval < 0) {
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 13;
+
+ return (NULL);
+ }
+ /*
+ * since we did not send a HB make sure we don't double
+ * things
+ */
+ net->hb_responded = 1;
+
+ if (sctp_load_addresses_from_init(stcb, m,
+ init_offset + sizeof(struct sctp_init_chunk),
+ initack_offset, src, dst, init_src, stcb->asoc.port)) {
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 14;
+
+ return (NULL);
+ }
+ /* respond with a COOKIE-ACK */
+ sctp_stop_all_cookie_timers(stcb);
+ sctp_toss_old_cookies(stcb, asoc);
+ sctp_send_cookie_ack(stcb);
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 15;
+
+ return (stcb);
+ }
+ if (how_indx < sizeof(asoc->cookie_how))
+ asoc->cookie_how[how_indx] = 16;
+ /* all other cases... */
+ 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 retval;
+ 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 popluate
+ */
+
+ /*
+ * 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), 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);
+ }
+ /* 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);
+ 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;
+
+ /* process the INIT info (peer's info) */
+ retval = sctp_process_init(init_cp, stcb);
+ if (retval < 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)) {
+#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);
+ if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, NULL);
+ }
+ 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..
+ */
+ stcb->sctp_ep->sctp_flags |= 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) {
+ struct timeval old;
+
+ /*
+ * Since we did not send a HB, make sure we don't double
+ * things.
+ */
+ (*netp)->hb_responded = 1;
+ /* Calculate the RTT. */
+ old.tv_sec = cookie->time_entered.tv_sec;
+ old.tv_usec = cookie->time_entered.tv_usec;
+ sctp_calculate_rto(stcb, asoc, *netp, &old, SCTP_RTT_FROM_NON_DATA);
+ }
+ /* 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;
+ uint8_t cookie_ok = 0;
+ unsigned int sig_offset, cookie_offset;
+ unsigned int cookie_len;
+ struct timeval now;
+ struct timeval 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
+ */
+ ep = &(*inp_p)->sctp_ep;
+ l_inp = *inp_p;
+ if (l_stcb) {
+ SCTP_TCB_UNLOCK(l_stcb);
+ }
+ SCTP_INP_RLOCK(l_inp);
+ if (l_stcb) {
+ SCTP_TCB_LOCK(l_stcb);
+ }
+ /* 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);
+ }
+
+ /*
+ * check the cookie timestamps to be sure it's not stale
+ */
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+ /* Expire time is in Ticks, so we convert to seconds */
+ time_expires.tv_sec = cookie->time_entered.tv_sec + sctp_ticks_to_secs(cookie->cookie_life);
+ time_expires.tv_usec = cookie->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_paramhdr) +
+ (sizeof(uint32_t))));
+#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 = diff.tv_sec * 1000000;
+ }
+ if (UINT32_MAX - staleness >= (uint32_t)diff.tv_usec) {
+ staleness += 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) {
+ /* 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;
+ inp->inp_starting_point_for_iterator = NULL;
+#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__);
+ }
+ asoc->overall_error_count = 0;
+ sctp_stop_all_cookie_timers(stcb);
+ /* 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);
+ if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD,
+ stcb->sctp_ep, stcb, NULL);
+
+ }
+ /* 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);
+ }
+ (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
+ stcb->sctp_ep->sctp_flags |= 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
+ }
+ /*
+ * since we did not send a HB make sure we don't double
+ * things
+ */
+ net->hb_responded = 1;
+
+ 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:
+ /* Toss the cookie if I can */
+ sctp_toss_old_cookies(stcb, asoc);
+ /* 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 sctp_ecne_chunk)) &&
+ (len != sizeof(struct old_sctp_ecne_chunk))) {
+ return;
+ }
+ 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:
+ /* 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)) {
+ uint8_t *ddp;
+
+ 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);
+ }
+ ddp = (uint8_t *) (mtod(tp1->data, caddr_t) +
+ sizeof(struct sctp_data_chunk));
+ {
+ unsigned int iii;
+
+ for (iii = 0; iii < sizeof(desc->data_bytes);
+ iii++) {
+ if (ddp[iii] != desc->data_bytes[iii]) {
+ 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;
+ if (TAILQ_EMPTY(&stcb->asoc.control_send_queue)) {
+ asoc->stream_reset_outstanding = 0;
+ return (NULL);
+ }
+ if (stcb->asoc.str_reset == NULL) {
+ asoc->stream_reset_outstanding = 0;
+ return (NULL);
+ }
+ chk = stcb->asoc.str_reset;
+ if (chk->data == NULL) {
+ return (NULL);
+ }
+ if (bchk) {
+ /* 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_notify_stream_reset_add(stcb, stcb->asoc.streamincnt, stcb->asoc.streamoutcnt, 0);
+ } else if (action == SCTP_STREAM_RESET_RESULT_DENIED) {
+ sctp_notify_stream_reset_add(stcb, stcb->asoc.streamincnt, stcb->asoc.streamoutcnt,
+ SCTP_STREAM_CHANGE_DENIED);
+ } else {
+ sctp_notify_stream_reset_add(stcb, stcb->asoc.streamincnt, stcb->asoc.streamoutcnt,
+ SCTP_STREAM_CHANGE_FAILED);
+ }
+ } 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_notify_stream_reset_add(stcb, stcb->asoc.streamincnt, stcb->asoc.streamoutcnt,
+ SCTP_STREAM_CHANGE_DENIED);
+ } else if (action != SCTP_STREAM_RESET_RESULT_PERFORMED) {
+ sctp_notify_stream_reset_add(stcb, stcb->asoc.streamincnt, stcb->asoc.streamoutcnt,
+ SCTP_STREAM_CHANGE_FAILED);
+ }
+ } 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_notify_stream_reset_tsn(stcb, stcb->asoc.sending_seq, (stcb->asoc.mapping_array_base_tsn + 1), 0);
+ } else if (action == SCTP_STREAM_RESET_RESULT_DENIED) {
+ sctp_notify_stream_reset_tsn(stcb, stcb->asoc.sending_seq, (stcb->asoc.mapping_array_base_tsn + 1),
+ SCTP_ASSOC_RESET_DENIED);
+ } else {
+ sctp_notify_stream_reset_tsn(stcb, stcb->asoc.sending_seq, (stcb->asoc.mapping_array_base_tsn + 1),
+ SCTP_ASSOC_RESET_FAILED);
+ }
+ }
+ /* 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)) {
+ 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)) {
+ 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_notify_stream_reset_tsn(stcb, asoc->sending_seq, (asoc->mapping_array_base_tsn + 1), 0);
+ }
+ 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)) {
+ 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)) {
+ 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_notify_stream_reset_add(stcb, stcb->asoc.streamincnt, stcb->asoc.streamoutcnt, 0);
+ }
+ 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)) {
+ 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 disectting for us.
+ */
+static void
+sctp_handle_packet_dropped(struct sctp_pktdrop_chunk *cp,
+ struct sctp_tcb *stcb, struct sctp_nets *net, uint32_t limit)
+{
+ uint32_t bottle_bw, on_queue;
+ uint16_t trunc_len;
+ unsigned int chlen;
+ unsigned int at;
+ struct sctp_chunk_desc desc;
+ struct sctp_chunkhdr *ch;
+
+ chlen = ntohs(cp->ch.chunk_length);
+ chlen -= sizeof(struct sctp_pktdrop_chunk);
+ /* XXX possible chlen underflow */
+ if (chlen == 0) {
+ ch = NULL;
+ if (cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX)
+ SCTP_STAT_INCR(sctps_pdrpbwrpt);
+ } else {
+ ch = (struct sctp_chunkhdr *)(cp->data + sizeof(struct sctphdr));
+ chlen -= sizeof(struct sctphdr);
+ /* XXX possible chlen underflow */
+ memset(&desc, 0, sizeof(desc));
+ }
+ trunc_len = (uint16_t) ntohs(cp->trunc_len);
+ if (trunc_len > limit) {
+ trunc_len = limit;
+ }
+
+ /* now the chunks themselves */
+ while ((ch != NULL) && (chlen >= sizeof(struct sctp_chunkhdr))) {
+ desc.chunk_type = ch->chunk_type;
+ /* get amount we need to move */
+ at = ntohs(ch->chunk_length);
+ if (at < sizeof(struct sctp_chunkhdr)) {
+ /* corrupt chunk, maybe at the end? */
+ SCTP_STAT_INCR(sctps_pdrpcrupt);
+ break;
+ }
+ if (trunc_len == 0) {
+ /* we are supposed to have all of it */
+ if (at > chlen) {
+ /* corrupt skip it */
+ SCTP_STAT_INCR(sctps_pdrpcrupt);
+ break;
+ }
+ } else {
+ /* is there enough of it left ? */
+ if (desc.chunk_type == SCTP_DATA) {
+ if (chlen < (sizeof(struct sctp_data_chunk) +
+ sizeof(desc.data_bytes))) {
+ break;
+ }
+ } else {
+ if (chlen < sizeof(struct sctp_chunkhdr)) {
+ break;
+ }
+ }
+ }
+ if (desc.chunk_type == SCTP_DATA) {
+ /* can we get out the tsn? */
+ if ((cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX))
+ SCTP_STAT_INCR(sctps_pdrpmbda);
+
+ if (chlen >= (sizeof(struct sctp_data_chunk) + sizeof(uint32_t))) {
+ /* yep */
+ struct sctp_data_chunk *dcp;
+ uint8_t *ddp;
+ unsigned int iii;
+
+ dcp = (struct sctp_data_chunk *)ch;
+ ddp = (uint8_t *) (dcp + 1);
+ for (iii = 0; iii < sizeof(desc.data_bytes); iii++) {
+ desc.data_bytes[iii] = ddp[iii];
+ }
+ desc.tsn_ifany = dcp->dp.tsn;
+ } else {
+ /* nope we are done. */
+ SCTP_STAT_INCR(sctps_pdrpnedat);
+ break;
+ }
+ } else {
+ if ((cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX))
+ SCTP_STAT_INCR(sctps_pdrpmbct);
+ }
+
+ if (process_chunk_drop(stcb, &desc, net, cp->ch.chunk_flags)) {
+ SCTP_STAT_INCR(sctps_pdrppdbrk);
+ break;
+ }
+ if (SCTP_SIZE32(at) > chlen) {
+ break;
+ }
+ chlen -= SCTP_SIZE32(at);
+ if (chlen < sizeof(struct sctp_chunkhdr)) {
+ /* done, none left */
+ break;
+ }
+ ch = (struct sctp_chunkhdr *)((caddr_t)ch + SCTP_SIZE32(at));
+ }
+ /* Now update any rwnd --- possibly */
+ if ((cp->ch.chunk_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 ((cp->ch.chunk_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;
+ /*
+ * 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 if (ch->chunk_type == SCTP_SHUTDOWN_ACK) {
+ if (vtag_in != asoc->my_vtag) {
+ /*
+ * this could be a stale SHUTDOWN-ACK or the
+ * peer never got the SHUTDOWN-COMPLETE and
+ * is still hung; we have started a new asoc
+ * but it won't complete until the shutdown
+ * is completed
+ */
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ 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);
+ 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;
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ return (NULL);
+ }
+
+ 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 4960 requires that no ABORT is sent */
+ *offset = length;
+ if (stcb != NULL) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ return (NULL);
+ }
+ /* Honor our resource limit. */
+ if (chk_length > SCTP_LARGEST_INIT_ACCEPTED) {
+ 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);
+ }
+ sctp_handle_init(m, iphlen, *offset, src, dst, sh,
+ (struct sctp_init_chunk *)ch, inp,
+ stcb, *netp, &abort_no_unlock,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ mflowtype, mflowid,
+#endif
+ vrf_id, port);
+ *offset = length;
+ if ((!abort_no_unlock) && (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 */
+ *offset = length;
+ return (stcb);
+ }
+ 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))) {
+ *offset = length;
+ return (stcb);
+ }
+ if ((netp != NULL) && (*netp != NULL)) {
+ int 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 ((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) &&
+#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 (stcb) {
+ linp = NULL;
+ } else {
+ linp = inp;
+ }
+
+ if (linp != NULL) {
+ SCTP_ASOC_CREATE_LOCK(linp);
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE)) {
+ SCTP_ASOC_CREATE_UNLOCK(linp);
+ goto abend;
+ }
+ }
+
+ 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)) {
+ return (stcb);
+ }
+ 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) || (chk_length != sizeof(struct sctp_ecne_chunk))) {
+ /* Its not ours */
+ *offset = length;
+ return (stcb);
+ }
+ if (stcb->asoc.ecn_supported == 0) {
+ goto unknown_chunk;
+ }
+ 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) || (chk_length != sizeof(struct sctp_cwr_chunk))) {
+ *offset = length;
+ return (stcb);
+ }
+ if (stcb->asoc.ecn_supported == 0) {
+ goto unknown_chunk;
+ }
+ 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 ((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 (chk_length < sizeof(struct sctp_asconf_ack_chunk)) {
+ /* Its not ours */
+ *offset = length;
+ return (stcb);
+ }
+ if ((stcb != NULL) && (netp != NULL) && (*netp != NULL)) {
+ if (stcb->asoc.asconf_supported == 0) {
+ goto unknown_chunk;
+ }
+ /* 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, "SCTP_FWD_TSN\n");
+ if (chk_length < sizeof(struct sctp_forward_tsn_chunk)) {
+ /* Its not ours */
+ *offset = length;
+ return (stcb);
+ }
+
+ if (stcb != NULL) {
+ int abort_flag = 0;
+
+ if (stcb->asoc.prsctp_supported == 0) {
+ goto unknown_chunk;
+ }
+ *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;
+ 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) || (ch == NULL) || (chk_length < sizeof(struct sctp_stream_reset_tsn_req)))) {
+ /* Its not ours */
+ *offset = length;
+ return (stcb);
+ }
+ if (stcb->asoc.reconfig_supported == 0) {
+ goto unknown_chunk;
+ }
+ 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");
+ /* re-get it all please */
+ if (chk_length < sizeof(struct sctp_pktdrop_chunk)) {
+ /* Its not ours */
+ *offset = length;
+ return (stcb);
+ }
+
+ if ((ch != NULL) && (stcb != NULL) && (netp != NULL) && (*netp != NULL)) {
+ if (stcb->asoc.pktdrop_supported == 0) {
+ goto unknown_chunk;
+ }
+ 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) */
+ goto next_chunk;
+ }
+ 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 */
+ goto next_chunk;
+ }
+ got_auth = 1;
+ if ((ch == NULL) || 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)) {
+ 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)
+{
+ uint32_t high_tsn;
+ int fwd_tsn_seen = 0, data_processed = 0;
+ struct mbuf *m = *mm, *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+ int un_sent;
+ int cnt_ctrl_ready = 0;
+ 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
+
+ SCTP_STAT_INCR(sctps_recvdatagrams);
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xE0, 1);
+ sctp_auditing(0, inp, stcb, net);
+#endif
+ if (compute_crc != 0) {
+ uint32_t check, calc_check;
+
+ check = sh->checksum;
+ sh->checksum = 0;
+ calc_check = sctp_calculate_cksum(m, iphlen);
+ sh->checksum = check;
+ if (calc_check != check) {
+ SCTPDBG(SCTP_DEBUG_INPUT1, "Bad CSUM on SCTP packet calc_check:%x check:%x m:%p mlen:%d iphlen:%d\n",
+ calc_check, check, (void *)m, length, iphlen);
+ 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);
+ }
+ } 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)) {
+ 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;
+ }
+ }
+ /* Destination port of 0 is illegal, based on RFC4960. */
+ if (sh->dest_port == 0) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ goto out;
+ }
+ 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);
+ }
+ } 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) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ SCTP_PROBE5(receive, NULL, stcb, m, stcb, sh);
+#endif
+ SCTP_STAT_INCR(sctps_noport);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (badport_bandlim(BANDLIM_SCTP_OOTB) < 0) {
+ goto out;
+ }
+#endif
+ if (ch->chunk_type == SCTP_SHUTDOWN_ACK) {
+ 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) {
+ 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) &&
+ (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);
+ }
+ } 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) &&
+ (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;
+ }
+ 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 100755
index 0000000000..55a174baef
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_input.h
@@ -0,0 +1,66 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_input.h 326672 2017-12-07 22:19:08Z tuexen $");
+#endif
+
+#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 100755
index 0000000000..f09f665da9
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_lock_userspace.h
@@ -0,0 +1,243 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+#endif
+
+#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_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_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_INIT(_inp)
+#define SCTP_INP_READ_DESTROY(_inp)
+#define SCTP_INP_READ_LOCK(_inp)
+#define SCTP_INP_READ_UNLOCK(_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_TCB_SEND_LOCK_INIT(_tcb)
+#define SCTP_TCB_SEND_LOCK_DESTROY(_tcb)
+#define SCTP_TCB_SEND_LOCK(_tcb)
+#define SCTP_TCB_SEND_UNLOCK(_tcb)
+
+#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 100755
index 0000000000..e01b95777a
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_os.h
@@ -0,0 +1,92 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_os.h 361872 2020-06-06 18:20:09Z tuexen $");
+#endif
+
+#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 100755
index 0000000000..3a9407a822
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_os_userspace.h
@@ -0,0 +1,1140 @@
+/*-
+ * 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
+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
+#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(__DragonFly__) || defined(__FreeBSD__) || defined(__linux__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__native_client__) || defined(__Fuchsia__)
+#include <pthread.h>
+#endif
+typedef pthread_mutex_t userland_mutex_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)
+#include "user_ip6_var.h"
+#else
+#include <netinet6/ip6_var.h>
+#endif
+#if defined(__FreeBSD__)
+#include <netinet6/in6_pcb.h>
+#include <netinet6/ip6protosw.h>
+/* #include <netinet6/nd6.h> was a 0 byte file */
+#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, int af);
+
+#define SCTP_GATHER_MTU_FROM_IFN_INFO(ifn, ifn_index, af) sctp_userspace_get_mtu_from_ifn(ifn_index, af)
+
+#define SCTP_GATHER_MTU_FROM_ROUTE(sctp_ifa, sa, rt) ((rt != NULL) ? rt->rt_rmx.rmx_mtu : 0)
+
+#define SCTP_GATHER_MTU_FROM_INTFC(sctp_ifn) sctp_userspace_get_mtu_from_ifn(if_nametoindex(((struct ifaddrs *) (sctp_ifn))->ifa_name), AF_INET)
+
+#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)
+/* 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, stcb, vrf_id) sctp_userspace_ip_output(&result, o_pak, ro, stcb, 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, stcb, vrf_id) sctp_userspace_ip6_output(&result, o_pak, ro, stcb, 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__)
+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 100755
index 0000000000..a494b2c0ae
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_output.c
@@ -0,0 +1,14993 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_output.c 362178 2020-06-14 16:05:08Z tuexen $");
+#endif
+
+#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 unspecifed 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.
+ *
+ * Cavets 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 int
+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;
+ int found;
+
+ /*
+ * Independent of how many mbufs, find the c_type inside the control
+ * structure and copy out the data.
+ */
+ found = 0;
+ 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 == 0) {
+ 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 = 1;
+ }
+ }
+ }
+ 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.strmout[i].chunks_on_queues = 0;
+ stcb->asoc.strmout[i].next_mid_ordered = 0;
+ stcb->asoc.strmout[i].next_mid_unordered = 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].sid = i;
+ stcb->asoc.strmout[i].last_msg_incomplete = 0;
+ stcb->asoc.strmout[i].state = SCTP_STREAM_OPENING;
+ stcb->asoc.ss_functions.sctp_ss_init_stream(stcb, &stcb->asoc.strmout[i], NULL);
+ }
+ }
+ 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
+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) {
+ 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 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, stcb, 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) {
+ if ((stcb != NULL) && (stcb->asoc.smallest_mtu > mtu)) {
+ sctp_mtu_size_reset(inp, &stcb->asoc, mtu);
+ }
+ net->mtu = mtu;
+ }
+ }
+#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, stcb, vrf_id);
+#else
+ SCTP_IP6_OUTPUT(ret, o_pak, (struct route_in6 *)ro, NULL, stcb, 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) {
+ if ((stcb != NULL) && (stcb->asoc.smallest_mtu > mtu)) {
+ sctp_mtu_size_reset(inp, &stcb->asoc, mtu);
+ }
+ net->mtu = mtu;
+ }
+ }
+ }
+#if !defined(__Userspace__)
+ else if (ifp) {
+#if defined(_WIN32)
+#define ND_IFINFO(ifp) (ifp)
+#define linkmtu if_mtu
+#endif
+ if (ND_IFINFO(ifp)->linkmtu &&
+ (stcb->asoc.smallest_mtu > ND_IFINFO(ifp)->linkmtu)) {
+ sctp_mtu_size_reset(inp,
+ &stcb->asoc,
+ ND_IFINFO(ifp)->linkmtu);
+ }
+ }
+#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);
+ 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_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;
+ }
+
+ /* 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) {
+ 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
+ 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;
+ /* fall through */
+ 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);
+}
+
+static int
+sctp_are_there_new_addresses(struct sctp_association *asoc,
+ struct mbuf *in_initpkt, int offset, struct sockaddr *src)
+{
+ /*
+ * Given a INIT packet, look through the packet to verify that there
+ * are NO new addresses. As we go through the parameters add reports
+ * of any un-understood parameters that require an error. Also we
+ * must return (1) to drop the packet if we see a un-understood
+ * parameter that tells us to drop the chunk.
+ */
+ struct sockaddr *sa_touse;
+ struct sockaddr *sa;
+ struct sctp_paramhdr *phdr, params;
+ uint16_t ptype, plen;
+ uint8_t fnd;
+ struct sctp_nets *net;
+ int check_src;
+#ifdef INET
+ struct sockaddr_in sin4, *sa4;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 sin6, *sa6;
+#endif
+#if defined(__Userspace__)
+ struct sockaddr_conn *sac;
+#endif
+
+#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 = 0;
+ switch (src->sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (asoc->scope.ipv4_addr_legal) {
+ check_src = 1;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (asoc->scope.ipv6_addr_legal) {
+ check_src = 1;
+ }
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ if (asoc->scope.conn_addr_legal) {
+ check_src = 1;
+ }
+ break;
+#endif
+ default:
+ /* TSNH */
+ break;
+ }
+ if (check_src) {
+ fnd = 0;
+ 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 = 1;
+ 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 = 1;
+ 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 = 1;
+ break;
+ }
+ }
+#endif
+ }
+ }
+ if (fnd == 0) {
+ /* New address added! no need to look further. */
+ return (1);
+ }
+ }
+ /* 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);
+ switch (ptype) {
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ {
+ struct sctp_ipv4addr_param *p4, p4_buf;
+
+ if (plen != sizeof(struct sctp_ipv4addr_param)) {
+ return (1);
+ }
+ phdr = sctp_get_next_param(in_initpkt, offset,
+ (struct sctp_paramhdr *)&p4_buf, sizeof(p4_buf));
+ if (phdr == NULL) {
+ return (1);
+ }
+ 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)) {
+ return (1);
+ }
+ phdr = sctp_get_next_param(in_initpkt, offset,
+ (struct sctp_paramhdr *)&p6_buf, sizeof(p6_buf));
+ if (phdr == NULL) {
+ return (1);
+ }
+ 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 = 0;
+ 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 = 1;
+ break;
+ }
+ }
+#endif
+#ifdef INET6
+ if (sa->sa_family == AF_INET6) {
+ sa6 = (struct sockaddr_in6 *)sa;
+ if (SCTP6_ARE_ADDR_EQUAL(
+ sa6, &sin6)) {
+ fnd = 1;
+ break;
+ }
+ }
+#endif
+ }
+ if (!fnd) {
+ /* New addr added! no need to look further */
+ return (1);
+ }
+ }
+ offset += SCTP_SIZE32(plen);
+ phdr = sctp_get_next_param(in_initpkt, offset, &params, sizeof(params));
+ }
+ return (0);
+}
+
+/*
+ * 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_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, src)) {
+ /*
+ * new addresses, out of here in non-cookie-wait states
+ *
+ * Send an ABORT, without 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");
+ 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
+ }
+ }
+ /* 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:
+ vtag = sctp_select_a_tag(inp, inp->sctp_lport, sh->src_port, 1);
+ 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_add_int(&asoc->refcnt, -1);
+ } else {
+ SCTP_INP_INCR_REF(inp);
+ SCTP_INP_RUNLOCK(inp);
+ vtag = sctp_select_a_tag(inp, inp->sctp_lport, sh->src_port, 1);
+ 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;
+ }
+
+ /* 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
+ 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_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 */
+}
+
+int
+sctp_get_frag_point(struct sctp_tcb *stcb,
+ struct sctp_association *asoc)
+{
+ int siz, ovh;
+
+ /*
+ * For endpoints that have both v6 and v4 addresses we must reserve
+ * room for the ipv6 header, for those that are only dealing with V4
+ * we use a larger frag point.
+ */
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ ovh = SCTP_MIN_OVERHEAD;
+ } else {
+#if defined(__Userspace__)
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_CONN) {
+ ovh = sizeof(struct sctphdr);
+ } else {
+ ovh = SCTP_MIN_V4_OVERHEAD;
+ }
+#else
+ ovh = SCTP_MIN_V4_OVERHEAD;
+#endif
+ }
+ ovh += SCTP_DATA_CHUNK_OVERHEAD(stcb);
+ if (stcb->asoc.sctp_frag_point > asoc->smallest_mtu)
+ siz = asoc->smallest_mtu - ovh;
+ else
+ siz = (stcb->asoc.sctp_frag_point - ovh);
+ /*
+ * if (siz > (MCLBYTES-sizeof(struct sctp_data_chunk))) {
+ */
+ /* A data chunk MUST fit in a cluster */
+ /* siz = (MCLBYTES - sizeof(struct sctp_data_chunk)); */
+ /* } */
+
+ /* adjust for an AUTH chunk if DATA requires auth */
+ if (sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.peer_auth_chunks))
+ siz -= sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id);
+
+ if (siz % 4) {
+ /* make it an even word boundary please */
+ siz -= (siz % 4);
+ }
+ return (siz);
+}
+
+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_sndrcvinfo *srcv, int hold_stcb_lock)
+{
+ int error = 0;
+ struct mbuf *at;
+ struct sctp_stream_queue_pending *sp = NULL;
+ struct sctp_stream_out *strm;
+
+ /* 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;
+ }
+ strm = &stcb->asoc.strmout[srcv->sinfo_stream];
+ /* 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, ECONNRESET);
+ error = ECONNRESET;
+ 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;
+ }
+ if (hold_stcb_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ 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, 1);
+ m = NULL;
+ if (hold_stcb_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+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, int 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, 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_add_int(&stcb->asoc.refcnt, -1);
+ goto no_chunk_output;
+ } else {
+ if (m) {
+ ret = sctp_msg_append(stcb, net, m,
+ &ca->sndrcv, 1);
+ }
+ 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, SCTP_SO_NOT_LOCKED);
+ atomic_add_int(&stcb->asoc.refcnt, -1);
+ goto no_chunk_output;
+ }
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb,
+ NULL);
+ }
+ }
+
+ }
+ }
+ 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) {
+ int num_out, reason, now_filled = 0;
+ struct timeval now;
+ int frag_point;
+
+ frag_point = sctp_get_frag_point(stcb, &stcb->asoc);
+ (void)sctp_med_chunk_output(inp, stcb, &stcb->asoc, &num_out,
+ &reason, 1, 1, &now, &now_filled, frag_point, 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. */
+ ca->inp->sctp_flags &= ~SCTP_PCB_FLAGS_SND_ITERATOR_UP;
+ }
+ 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_sndrcvinfo *srcv)
+{
+ int ret;
+ struct sctp_copy_all *ca;
+
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SND_ITERATOR_UP) {
+ /* There is another. */
+ return (EBUSY);
+ }
+#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) {
+ memcpy(&ca->sndrcv, srcv, sizeof(struct sctp_nonpad_sndrcvinfo));
+ }
+ /*
+ * 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_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);
+ }
+ }
+ inp->sctp_flags |= SCTP_PCB_FLAGS_SND_ITERATOR_UP;
+ 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) {
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_SND_ITERATOR_UP;
+ 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 measurment 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-optimial 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_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;
+ uint8_t send_lock_up = 0;
+
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ asoc = &stcb->asoc;
+one_more_time:
+ /*sa_ignore FREED_MEMORY*/
+ sp = TAILQ_FIRST(&strq->outqueue);
+ if (sp == NULL) {
+ if (send_lock_up == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ send_lock_up = 1;
+ }
+ 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;
+ if (send_lock_up) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ send_lock_up = 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 send_lock:%d\n",
+ sp->sender_all_done,
+ sp->length,
+ sp->msg_is_complete,
+ sp->put_last_out,
+ send_lock_up);
+ }
+ if ((TAILQ_NEXT(sp, next) == NULL) && (send_lock_up == 0)) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ send_lock_up = 1;
+ }
+ 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, send_lock_up);
+ 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);
+ /* we can't be locked to it */
+ if (send_lock_up) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ send_lock_up = 0;
+ }
+ /* 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) {
+ if (send_lock_up == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ send_lock_up = 1;
+ }
+ /* 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))) {
+ atomic_subtract_int(&stcb->sctp_socket->so_snd.sb_cc, 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;
+re_look:
+ 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) {
+ /*-
+ * We use a snapshot of length in case it
+ * is expanding during the compare.
+ */
+ uint32_t llen;
+
+ llen = length;
+ if (to_move >= llen) {
+ to_move = llen;
+ if (send_lock_up == 0) {
+ /*-
+ * We are taking all of an incomplete msg
+ * thus we need a send lock.
+ */
+ SCTP_TCB_SEND_LOCK(stcb);
+ send_lock_up = 1;
+ if (sp->msg_is_complete) {
+ /* the sender finished the msg */
+ goto re_look;
+ }
+ }
+ }
+ 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 ((sp->sender_all_done == 0) && (send_lock_up == 0)) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ send_lock_up = 1;
+ }
+ 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, freing 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 (send_lock_up == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ send_lock_up = 1;
+ }
+ 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 failes 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;
+ }
+#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.
+ */
+
+#ifdef SCTP_ASOCLOG_OF_TSNS
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ 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 send_lock:%d\n",
+ sp->sender_all_done,
+ sp->length,
+ sp->msg_is_complete,
+ sp->put_last_out,
+ send_lock_up);
+ }
+ if ((send_lock_up == 0) && (TAILQ_NEXT(sp, next) == NULL)) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ send_lock_up = 1;
+ }
+ 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, send_lock_up);
+ 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:
+ if (send_lock_up) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ return (to_move);
+}
+
+
+static void
+sctp_fill_outqueue(struct sctp_tcb *stcb,
+ struct sctp_nets *net, int 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, strq, space_left, frag_point,
+ &giveup, eeor_mode, &bail, so_locked);
+ stcb->asoc.ss_functions.sctp_ss_scheduled(stcb, net, asoc, strq, moved);
+ 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, int 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 applys 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
+ * fomulate 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;
+
+#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)) ||
+ (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 ocmpletely. */
+ 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
+ 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);
+ }
+ *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;
+ 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.
+ */
+ if (asconf) {
+ 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 (cookie) {
+ sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, net);
+ 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
+ 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);
+ }
+ *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;
+ 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);
+ }
+ /* upate 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) {
+ /* We may need to start a control timer or two */
+ if (asconf) {
+ 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 (cookie) {
+ sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, net);
+ 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
+ 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);
+ }
+ *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 = bundle_at = 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;
+ 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, cnt_thru;
+ 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;
+
+#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 = bundle_at = error = 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;
+ cnt_thru = 0;
+ /* do we have control chunks to retransmit? */
+ if (m != NULL) {
+ /* Start a timer no matter if we succeed or fail */
+ if (chk->rec.chunk_id.id == SCTP_COOKIE_ECHO) {
+ sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, chk->whoTo);
+ } else if (chk->rec.chunk_id.id == SCTP_ASCONF)
+ sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp, stcb, chk->whoTo);
+ 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
+ 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,
+ 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;
+ }
+ /* upate 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;
+ }
+ /* upate 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;
+ }
+ /* 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
+ 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 */
+ cnt_thru++;
+ 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 fomulate 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;
+ int frag_point = sctp_get_frag_point(stcb, &stcb->asoc);
+ 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 cookiess 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)) {
+ /*-
+ * 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
+ * inefficent 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)) {
+ /*-
+ * 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;
+
+#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;
+ }
+ 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
+ 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;
+
+ 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;
+ }
+ 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
+ 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(mout, 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(mout, 0, len, buffer);
+ ret = SCTP_BASE_VAR(conn_output)(sconn->sconn_addr, buffer, len, 0, 0);
+ free(buffer);
+ } else {
+ ret = ENOMEM;
+ }
+ sctp_m_freem(mout);
+ 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 = 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 +
+ stcb->sctp_socket->so_rcv.sb_cc);
+ } 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 = &stcb->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(&stcb->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;
+
+ 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.
+ */
+ SCTP_TCB_SEND_LOCK(stcb);
+ stcb->asoc.ss_functions.sctp_ss_clear(stcb, &stcb->asoc, 0, 1);
+ for (i = 0; i < stcb->asoc.streamoutcnt; i++) {
+ TAILQ_INIT(&stcb->asoc.strmout[i].outqueue);
+ stcb->asoc.strmout[i].chunks_on_queues = oldstream[i].chunks_on_queues;
+ 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;
+ /* 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]);
+ /* 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, 1);
+ 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);
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+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) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, ENOBUFS);
+ *error = ENOBUFS;
+ } 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) {
+ 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) {
+ sctp_m_freem(head);
+ *new_tail = NULL;
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, EFAULT);
+ *error = EFAULT;
+ 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) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, ENOBUFS);
+ return (ENOBUFS);
+ }
+
+ 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_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 = NULL;
+ int resv_in_first;
+
+ *error = 0;
+ /* 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) ||
+ (asoc->state & SCTP_STATE_SHUTDOWN_PENDING)) {
+ /* got data while shutting down */
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ECONNRESET);
+ *error = ECONNRESET;
+ goto out_now;
+ }
+ 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
+)
+{
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct proc *p = current_proc();
+#endif
+ int error, use_sndinfo = 0;
+ struct sctp_sndrcvinfo sndrcvninfo;
+ struct sockaddr *addr_to_use;
+#if defined(INET) && defined(INET6)
+ struct sockaddr_in sin;
+#endif
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_LOCK(so, 1);
+#endif
+ if (control) {
+ /* process cmsg snd/rcv info (maybe a assoc-id) */
+ if (sctp_find_cmsg(SCTP_SNDRCV, (void *)&sndrcvninfo, control,
+ sizeof(sndrcvninfo))) {
+ /* got one */
+ use_sndinfo = 1;
+ }
+ }
+ addr_to_use = addr;
+#if defined(INET) && defined(INET6)
+ if ((addr) && (addr->sa_family == AF_INET6)) {
+ struct sockaddr_in6 *sin6;
+
+ 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;
+ }
+ }
+#endif
+ error = sctp_lower_sosend(so, addr_to_use, uio, top,
+ control,
+ flags,
+ use_sndinfo ? &sndrcvninfo: NULL
+#if !defined(__Userspace__)
+ , 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 *i_pak,
+ 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
+ )
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ ssize_t sndlen = 0, max_len, local_add_more;
+ int error, len;
+ struct mbuf *top = NULL;
+ int queue_only = 0, queue_only_for_init = 0;
+ int free_cnt_applied = 0;
+ int un_sent;
+ int now_filled = 0;
+ unsigned int inqueue_bytes = 0;
+ struct sctp_block_entry be;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb = NULL;
+ struct timeval now;
+ struct sctp_nets *net;
+ struct sctp_association *asoc;
+ struct sctp_inpcb *t_inp;
+ int user_marks_eor;
+ int create_lock_applied = 0;
+ int nagle_applies = 0;
+ int some_on_control = 0;
+ int got_all_of_the_send = 0;
+ int hold_tcblock = 0;
+ int non_blocking = 0;
+ ssize_t local_soresv = 0;
+ uint16_t port;
+ uint16_t sinfo_flags;
+ sctp_assoc_t sinfo_assoc_id;
+
+ error = 0;
+ net = NULL;
+ stcb = NULL;
+ asoc = NULL;
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sctp_lock_assert(so);
+#endif
+ t_inp = inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ error = EINVAL;
+ if (i_pak) {
+ SCTP_RELEASE_PKT(i_pak);
+ }
+ return (error);
+ }
+ if ((uio == NULL) && (i_pak == NULL)) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ return (EINVAL);
+ }
+ user_marks_eor = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXPLICIT_EOR);
+ atomic_add_int(&inp->total_sends, 1);
+ if (uio) {
+#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
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ return (EINVAL);
+ }
+#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 {
+ top = SCTP_HEADER_TO_CHAIN(i_pak);
+ sndlen = SCTP_HEADER_LEN(i_pak);
+ }
+ SCTPDBG(SCTP_DEBUG_OUTPUT1, "Send called addr:%p send length %zd\n",
+ (void *)addr,
+ sndlen);
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+ SCTP_IS_LISTENING(inp)) {
+ /* The listener can NOT send */
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, ENOTCONN);
+ error = ENOTCONN;
+ goto out_unlocked;
+ }
+ /**
+ * Pre-screen address, if one is given the sin-len
+ * must be set correctly!
+ */
+ if (addr) {
+ 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)) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ 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)) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ 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)) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ error = EINVAL;
+ goto out_unlocked;
+ }
+#endif
+ port = raddr->sconn.sconn_port;
+ break;
+#endif
+ default:
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EAFNOSUPPORT);
+ error = EAFNOSUPPORT;
+ goto out_unlocked;
+ }
+ } else
+ port = 0;
+
+ if (srcv) {
+ sinfo_flags = srcv->sinfo_flags;
+ sinfo_assoc_id = srcv->sinfo_assoc_id;
+ if (INVALID_SINFO_FLAG(sinfo_flags) ||
+ PR_SCTP_INVALID_POLICY(sinfo_flags)) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ if (srcv->sinfo_flags)
+ SCTP_STAT_INCR(sctps_sends_with_flags);
+ } else {
+ 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_SENDALL) {
+ /* its a sendall */
+ error = sctp_sendall(inp, uio, top, srcv);
+ top = NULL;
+ goto out_unlocked;
+ }
+ if ((sinfo_flags & SCTP_ADDR_OVER) && (addr == NULL)) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ /* now we must find the assoc */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) ||
+ (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);
+ hold_tcblock = 1;
+ }
+ SCTP_INP_RUNLOCK(inp);
+ } else if (sinfo_assoc_id) {
+ stcb = sctp_findassociation_ep_asocid(inp, sinfo_assoc_id, 1);
+ if (stcb != NULL) {
+ hold_tcblock = 1;
+ }
+ } else if (addr) {
+ /*-
+ * Since we did not use findep we must
+ * increment it, and if we don't find a tcb
+ * decrement it.
+ */
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_INCR_REF(inp);
+ SCTP_INP_WUNLOCK(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 {
+ hold_tcblock = 1;
+ }
+ }
+ if ((stcb == NULL) && (addr)) {
+ /* Possible implicit send? */
+ SCTP_ASOC_CREATE_LOCK(inp);
+ create_lock_applied = 1;
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE)) {
+ /* Should I really unlock ? */
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ error = EINVAL;
+ goto out_unlocked;
+
+ }
+ if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) &&
+ (addr->sa_family == AF_INET6)) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ 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 {
+ hold_tcblock = 1;
+ }
+ if (error) {
+ goto out_unlocked;
+ }
+ if (t_inp != inp) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, ENOTCONN);
+ error = ENOTCONN;
+ goto out_unlocked;
+ }
+ }
+ if (stcb == NULL) {
+ if (addr == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, ENOENT);
+ 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-existant assoc,
+ * or EOF a non-existant assoc with no data
+ */
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, ENOENT);
+ error = ENOENT;
+ goto out_unlocked;
+ }
+ /* get an asoc/stcb struct */
+ vrf_id = inp->def_vrf_id;
+#ifdef INVARIANTS
+ if (create_lock_applied == 0) {
+ panic("Error, should hold create lock and I don't?");
+ }
+#endif
+ stcb = sctp_aloc_assoc(inp, addr, &error, 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 */
+ goto out_unlocked;
+ }
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) {
+ stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED;
+ /* Set the connected flag so we can queue data */
+ soisconnecting(so);
+ }
+ hold_tcblock = 1;
+ if (create_lock_applied) {
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ create_lock_applied = 0;
+ } else {
+ SCTP_PRINTF("Huh-3? create lock should have been on??\n");
+ }
+ /* Turn on queue only flag to prevent data from being sent */
+ queue_only = 1;
+ asoc = &stcb->asoc;
+ SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_WAIT);
+ (void)SCTP_GETTIME_TIMEVAL(&asoc->time_entered);
+
+ if (control) {
+ if (sctp_process_cmsgs_for_init(stcb, control, &error)) {
+ sctp_free_assoc(inp, stcb, SCTP_PCBFREE_FORCE,
+ SCTP_FROM_SCTP_OUTPUT + SCTP_LOC_6);
+ hold_tcblock = 0;
+ stcb = NULL;
+ 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.
+ */
+ }
+ } else
+ asoc = &stcb->asoc;
+ if (srcv == NULL) {
+ srcv = (struct sctp_sndrcvinfo *)&asoc->def_send;
+ sinfo_flags = srcv->sinfo_flags;
+#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) {
+ if (addr)
+ net = sctp_findnet(stcb, addr);
+ else
+ net = NULL;
+ if ((net == NULL) ||
+ ((port != 0) && (port != stcb->rport))) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ } else {
+ if (stcb->asoc.alternate) {
+ net = stcb->asoc.alternate;
+ } else {
+ net = stcb->asoc.primary_destination;
+ }
+ }
+ atomic_add_int(&stcb->total_sends, 1);
+ /* Keep the stcb from being freed under our feet */
+ atomic_add_int(&asoc->refcnt, 1);
+ free_cnt_applied = 1;
+
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NO_FRAGMENT)) {
+ if (sndlen > (ssize_t)asoc->smallest_mtu) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EMSGSIZE);
+ error = EMSGSIZE;
+ goto out_unlocked;
+ }
+ }
+#if defined(__Userspace__)
+ if (inp->recv_callback) {
+ non_blocking = 1;
+ }
+#endif
+ if (SCTP_SO_IS_NBIO(so)
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ || (flags & (MSG_NBIO | MSG_DONTWAIT)) != 0
+#endif
+ ) {
+ non_blocking = 1;
+ }
+ /* would we block? */
+ if (non_blocking) {
+ ssize_t amount;
+
+ if (hold_tcblock == 0) {
+ SCTP_TCB_LOCK(stcb);
+ hold_tcblock = 1;
+ }
+ inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->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 + stcb->asoc.sb_send_resv)) ||
+ (stcb->asoc.chunks_on_out_queue >= SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue))) {
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EWOULDBLOCK);
+ if (sndlen > (ssize_t)SCTP_SB_LIMIT_SND(so))
+ error = EMSGSIZE;
+ else
+ error = EWOULDBLOCK;
+ goto out_unlocked;
+ }
+ stcb->asoc.sb_send_resv += (uint32_t)sndlen;
+ SCTP_TCB_UNLOCK(stcb);
+ hold_tcblock = 0;
+ } else {
+ atomic_add_int(&stcb->asoc.sb_send_resv, sndlen);
+ }
+ local_soresv = sndlen;
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ECONNRESET);
+ error = ECONNRESET;
+ goto out_unlocked;
+ }
+ if (create_lock_applied) {
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ create_lock_applied = 0;
+ }
+ /* Is the stream no. valid? */
+ if (srcv->sinfo_stream >= asoc->streamoutcnt) {
+ /* Invalid stream number */
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ error = EINVAL;
+ goto out_unlocked;
+ }
+ if ((asoc->strmout[srcv->sinfo_stream].state != SCTP_STREAM_OPEN) &&
+ (asoc->strmout[srcv->sinfo_stream].state != SCTP_STREAM_OPENING)) {
+ /*
+ * Can't queue any data while stream reset is underway.
+ */
+ if (asoc->strmout[srcv->sinfo_stream].state > SCTP_STREAM_OPEN) {
+ error = EAGAIN;
+ } else {
+ error = EINVAL;
+ }
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, error);
+ goto out_unlocked;
+ }
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
+ queue_only = 1;
+ }
+ /* we are now done with all control */
+ if (control) {
+ sctp_m_freem(control);
+ control = NULL;
+ }
+ 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 (sinfo_flags & SCTP_ABORT) {
+ ;
+ } else {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ECONNRESET);
+ error = ECONNRESET;
+ goto out_unlocked;
+ }
+ }
+ /* Ok, we will attempt a msgsnd :> */
+#if !(defined(_WIN32) || defined(__Userspace__))
+ if (p) {
+#if defined(__FreeBSD__)
+ p->td_ru.ru_msgsnd++;
+#else
+ p->p_stats->p_ru.ru_msgsnd++;
+#endif
+ }
+#endif
+ /* Are we aborting? */
+ if (sinfo_flags & SCTP_ABORT) {
+ struct mbuf *mm;
+ 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 */
+ /* how big is the user initiated abort? */
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ error = EINVAL;
+ goto out;
+ }
+ if (hold_tcblock) {
+ SCTP_TCB_UNLOCK(stcb);
+ hold_tcblock = 0;
+ }
+ if (top) {
+ struct mbuf *cntm = NULL;
+
+ mm = sctp_get_mbuf_for_msg(sizeof(struct sctp_paramhdr), 0, M_WAITOK, 1, MT_DATA);
+ if (sndlen != 0) {
+ for (cntm = top; cntm; cntm = SCTP_BUF_NEXT(cntm)) {
+ tot_out += SCTP_BUF_LEN(cntm);
+ }
+ }
+ } else {
+ /* Must fit in a MTU */
+ tot_out = sndlen;
+ tot_demand = (tot_out + sizeof(struct sctp_paramhdr));
+ if (tot_demand > SCTP_DEFAULT_ADD_MORE) {
+ /* To big */
+ SCTP_LTRACE_ERR_RET(NULL, stcb, net, SCTP_FROM_SCTP_OUTPUT, EMSGSIZE);
+ error = EMSGSIZE;
+ goto out;
+ }
+ mm = sctp_get_mbuf_for_msg((unsigned int)tot_demand, 0, M_WAITOK, 1, MT_DATA);
+ }
+ if (mm == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, net, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
+ error = ENOMEM;
+ goto out;
+ }
+ 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;
+ }
+ if (mm) {
+ struct sctp_paramhdr *ph;
+
+ /* now move forward the data pointer */
+ 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) {
+#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
+ if (error) {
+ /*-
+ * 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;
+ }
+ } else {
+ if (sndlen != 0) {
+ SCTP_BUF_NEXT(mm) = top;
+ }
+ }
+ }
+ if (hold_tcblock == 0) {
+ SCTP_TCB_LOCK(stcb);
+ }
+ atomic_add_int(&stcb->asoc.refcnt, -1);
+ free_cnt_applied = 0;
+ /* 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, SCTP_SO_LOCKED);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ /* now relock the stcb so everything is sane */
+ hold_tcblock = 0;
+ 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;
+ }
+ /* Calculate the maximum we can send */
+ inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->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 (hold_tcblock) {
+ SCTP_TCB_UNLOCK(stcb);
+ hold_tcblock = 0;
+ }
+ if (asoc->strmout == NULL) {
+ /* huh? software error */
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EFAULT);
+ error = EFAULT;
+ goto out_unlocked;
+ }
+
+ /* 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 */
+ SCTP_LTRACE_ERR_RET(NULL, stcb, net, SCTP_FROM_SCTP_OUTPUT, EMSGSIZE);
+ error = EMSGSIZE;
+ goto out_unlocked;
+ }
+ if ((uio == NULL) && user_marks_eor) {
+ /*-
+ * We do not support eeor mode for
+ * sending with mbuf chains (like sendfile).
+ */
+ SCTP_LTRACE_ERR_RET(NULL, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ error = EINVAL;
+ goto out_unlocked;
+ }
+
+ if (user_marks_eor) {
+ 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;
+ }
+ len = 0;
+ 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) ||
+ ((stcb->asoc.chunks_on_out_queue+stcb->asoc.stream_queue_cnt) >= SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue))) {
+ /* No room right now ! */
+ SOCKBUF_LOCK(&so->so_snd);
+ inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->asoc.chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
+ while ((SCTP_SB_LIMIT_SND(so) < (inqueue_bytes + local_add_more)) ||
+ ((stcb->asoc.stream_queue_cnt + stcb->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,
+ stcb->asoc.stream_queue_cnt,
+ stcb->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
+ error = sbwait(&so->so_snd);
+ stcb->block_entry = NULL;
+ if (error || so->so_error || be.error) {
+ if (error == 0) {
+ if (so->so_error)
+ error = so->so_error;
+ if (be.error) {
+ error = be.error;
+ }
+ }
+ SOCKBUF_UNLOCK(&so->so_snd);
+ goto out_unlocked;
+ }
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_BLK_LOGGING_ENABLE) {
+ sctp_log_block(SCTP_BLOCK_LOG_OUTOF_BLK,
+ asoc, stcb->asoc.total_output_queue_size);
+ }
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ SOCKBUF_UNLOCK(&so->so_snd);
+ goto out_unlocked;
+ }
+ inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->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;
+ }
+ SOCKBUF_UNLOCK(&so->so_snd);
+ }
+
+skip_preblock:
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ goto out_unlocked;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ error = sblock(&so->so_snd, SBLOCKWAIT(flags));
+#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 (sndlen == 0) {
+ if (sinfo_flags & SCTP_EOF) {
+ got_all_of_the_send = 1;
+ goto dataless_eof;
+ } else {
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ error = EINVAL;
+ goto out;
+ }
+ }
+ if (top == NULL) {
+ struct sctp_stream_queue_pending *sp;
+ struct sctp_stream_out *strm;
+ uint32_t sndout;
+
+ SCTP_TCB_SEND_LOCK(stcb);
+ if ((asoc->stream_locked) &&
+ (asoc->stream_locked_on != srcv->sinfo_stream)) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
+ error = EINVAL;
+ goto out;
+ }
+ SCTP_TCB_SEND_UNLOCK(stcb);
+
+ strm = &stcb->asoc.strmout[srcv->sinfo_stream];
+ if (strm->last_msg_incomplete == 0) {
+ do_a_copy_in:
+ sp = sctp_copy_it_in(stcb, asoc, srcv, uio, net, max_len, user_marks_eor, &error);
+ if (error) {
+ goto out;
+ }
+ SCTP_TCB_SEND_LOCK(stcb);
+ 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 (stcb->asoc.idata_supported == 0) {
+ asoc->stream_locked = 1;
+ asoc->stream_locked_on = srcv->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);
+ }
+ TAILQ_INSERT_TAIL(&strm->outqueue, sp, next);
+ stcb->asoc.ss_functions.sctp_ss_add_to_stream(stcb, asoc, strm, sp, 1);
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ } else {
+ SCTP_TCB_SEND_LOCK(stcb);
+ sp = TAILQ_LAST(&strm->outqueue, sctp_streamhead);
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ 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 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 = stcb->asoc.total_output_queue_size - (stcb->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 && (SCTP_SB_LIMIT_SND(so) < SCTP_BASE_SYSCTL(sctp_add_more_threshold))) ||
+#if defined(__APPLE__) && !defined(__Userspace__)
+#if defined(APPLE_LEOPARD)
+ (uio->uio_resid && (uio->uio_resid <= max_len))) {
+#else
+ (uio_resid(uio) && (uio_resid(uio) <= max_len))) {
+#endif
+#else
+ (uio->uio_resid && (uio->uio_resid <= max_len))) {
+#endif
+ sndout = 0;
+ new_tail = NULL;
+ if (hold_tcblock) {
+ SCTP_TCB_UNLOCK(stcb);
+ hold_tcblock = 0;
+ }
+#if defined(__APPLE__) && !defined(__Userspace__)
+ SCTP_SOCKET_UNLOCK(so, 0);
+#endif
+#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
+ if ((mm == NULL) || error) {
+ if (mm) {
+ sctp_m_freem(mm);
+ }
+ goto out;
+ }
+ /* Update the mbuf and count */
+ SCTP_TCB_SEND_LOCK(stcb);
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ /* we need to get out.
+ * Peer probably aborted.
+ */
+ sctp_m_freem(mm);
+ if (stcb->asoc.state & SCTP_PCB_FLAGS_WAS_ABORTED) {
+ SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ECONNRESET);
+ error = ECONNRESET;
+ }
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ goto out;
+ }
+ if (sp->tail_mbuf) {
+ /* tack it to the end */
+ SCTP_BUF_NEXT(sp->tail_mbuf) = mm;
+ sp->tail_mbuf = new_tail;
+ } else {
+ /* A stolen mbuf */
+ sp->data = mm;
+ sp->tail_mbuf = new_tail;
+ }
+ sctp_snd_sb_alloc(stcb, sndout);
+ atomic_add_int(&sp->length, sndout);
+ len += 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;
+ }
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+#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 */
+ if (hold_tcblock == 0) {
+ SCTP_TCB_LOCK(stcb);
+ hold_tcblock = 1;
+ }
+ sctp_prune_prsctp(stcb, asoc, srcv, (int)sndlen);
+ inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->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;
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ hold_tcblock = 0;
+ }
+ /* wait for space now */
+ if (non_blocking) {
+ /* Non-blocking io in place out */
+ goto skip_out_eof;
+ }
+ /* What about the INIT, send it maybe */
+ if (queue_only_for_init) {
+ if (hold_tcblock == 0) {
+ SCTP_TCB_LOCK(stcb);
+ hold_tcblock = 1;
+ }
+ 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 = stcb->asoc.total_output_queue_size - stcb->asoc.total_flight;
+ if ((sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY)) &&
+ (stcb->asoc.total_flight > 0) &&
+ (stcb->asoc.stream_queue_cnt < SCTP_MAX_DATA_BUNDLING) &&
+ (un_sent < (int)(stcb->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, stcb->asoc.total_output_queue_size,
+ stcb->asoc.total_flight,
+ stcb->asoc.chunks_on_out_queue, stcb->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
+ if (hold_tcblock == 0) {
+ if (SCTP_TCB_TRYLOCK(stcb)) {
+ hold_tcblock = 1;
+ sctp_chunk_output(inp,
+ stcb,
+ SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_LOCKED);
+ }
+ } else {
+ sctp_chunk_output(inp,
+ stcb,
+ SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_LOCKED);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ }
+ if (hold_tcblock == 1) {
+ SCTP_TCB_UNLOCK(stcb);
+ hold_tcblock = 0;
+ }
+ SOCKBUF_LOCK(&so->so_snd);
+ /*-
+ * 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 = stcb->asoc.total_output_queue_size - (stcb->asoc.chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
+ 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
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sbunlock(&so->so_snd, 1);
+#endif
+ error = sbwait(&so->so_snd);
+ stcb->block_entry = NULL;
+
+ if (error || so->so_error || be.error) {
+ if (error == 0) {
+ if (so->so_error)
+ error = so->so_error;
+ if (be.error) {
+ error = be.error;
+ }
+ }
+ SOCKBUF_UNLOCK(&so->so_snd);
+ goto out_unlocked;
+ }
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+ error = sblock(&so->so_snd, SBLOCKWAIT(flags));
+#endif
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_BLK_LOGGING_ENABLE) {
+ sctp_log_block(SCTP_BLOCK_LOG_OUTOF_BLK,
+ asoc, stcb->asoc.total_output_queue_size);
+ }
+ }
+ SOCKBUF_UNLOCK(&so->so_snd);
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ goto out_unlocked;
+ }
+ }
+ SCTP_TCB_SEND_LOCK(stcb);
+ if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ goto out_unlocked;
+ }
+ if (sp) {
+ if (sp->msg_is_complete == 0) {
+ strm->last_msg_incomplete = 1;
+ if (stcb->asoc.idata_supported == 0) {
+ asoc->stream_locked = 1;
+ asoc->stream_locked_on = srcv->sinfo_stream;
+ }
+ } else {
+ sp->sender_all_done = 1;
+ strm->last_msg_incomplete = 0;
+ asoc->stream_locked = 0;
+ }
+ } else {
+ SCTP_PRINTF("Huh no sp TSNH?\n");
+ strm->last_msg_incomplete = 0;
+ asoc->stream_locked = 0;
+ }
+ SCTP_TCB_SEND_UNLOCK(stcb);
+#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 = 1;
+ }
+ } else {
+ /* We send in a 0, since we do NOT have any locks */
+ error = sctp_msg_append(stcb, net, top, srcv, 0);
+ top = NULL;
+ if (sinfo_flags & SCTP_EOF) {
+ got_all_of_the_send = 1;
+ }
+ }
+ if (error) {
+ goto out;
+ }
+dataless_eof:
+ /* EOF thing ? */
+ if ((sinfo_flags & SCTP_EOF) &&
+ (got_all_of_the_send == 1)) {
+ SCTP_STAT_INCR(sctps_sends_with_eof);
+ error = 0;
+ if (hold_tcblock == 0) {
+ SCTP_TCB_LOCK(stcb);
+ hold_tcblock = 1;
+ }
+ 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 (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);
+ }
+ } 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 (hold_tcblock == 0) {
+ SCTP_TCB_LOCK(stcb);
+ hold_tcblock = 1;
+ }
+ 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_add_int(&stcb->asoc.refcnt, -1);
+ free_cnt_applied = 0;
+ }
+ 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, SCTP_SO_LOCKED);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ /* now relock the stcb so everything is sane */
+ hold_tcblock = 0;
+ stcb = NULL;
+ goto out;
+ }
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb,
+ NULL);
+ sctp_feature_off(inp, SCTP_PCB_FLAGS_NODELAY);
+ }
+ }
+ }
+skip_out_eof:
+ if (!TAILQ_EMPTY(&stcb->asoc.control_send_queue)) {
+ some_on_control = 1;
+ }
+ if (queue_only_for_init) {
+ if (hold_tcblock == 0) {
+ SCTP_TCB_LOCK(stcb);
+ hold_tcblock = 1;
+ }
+ 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) &&
+ (stcb->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 = stcb->asoc.total_output_queue_size - stcb->asoc.total_flight;
+ if ((sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY)) &&
+ (stcb->asoc.total_flight > 0) &&
+ (stcb->asoc.stream_queue_cnt < SCTP_MAX_DATA_BUNDLING) &&
+ (un_sent < (int)(stcb->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, stcb->asoc.total_output_queue_size,
+ stcb->asoc.total_flight,
+ stcb->asoc.chunks_on_out_queue, stcb->asoc.total_flight_count);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ if ((queue_only == 0) && (nagle_applies == 0) && (stcb->asoc.peers_rwnd && un_sent)) {
+ /* we can attempt to send too. */
+ if (hold_tcblock == 0) {
+ /* If there is activity recv'ing sacks no need to send */
+ if (SCTP_TCB_TRYLOCK(stcb)) {
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_LOCKED);
+ hold_tcblock = 1;
+ }
+ } else {
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_LOCKED);
+ }
+ } else if ((queue_only == 0) &&
+ (stcb->asoc.peers_rwnd == 0) &&
+ (stcb->asoc.total_flight == 0)) {
+ /* We get to have a probe outstanding */
+ if (hold_tcblock == 0) {
+ hold_tcblock = 1;
+ SCTP_TCB_LOCK(stcb);
+ }
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_LOCKED);
+ } else if (some_on_control) {
+ int num_out, reason, frag_point;
+
+ /* Here we do control only */
+ if (hold_tcblock == 0) {
+ hold_tcblock = 1;
+ SCTP_TCB_LOCK(stcb);
+ }
+ frag_point = sctp_get_frag_point(stcb, &stcb->asoc);
+ (void)sctp_med_chunk_output(inp, stcb, &stcb->asoc, &num_out,
+ &reason, 1, 1, &now, &now_filled, frag_point, 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, stcb->asoc.peers_rwnd, un_sent,
+ stcb->asoc.total_flight, stcb->asoc.chunks_on_out_queue,
+ stcb->asoc.total_output_queue_size, error);
+
+out:
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sbunlock(&so->so_snd, 1);
+#endif
+out_unlocked:
+
+ if (local_soresv && stcb) {
+ atomic_subtract_int(&stcb->asoc.sb_send_resv, sndlen);
+ }
+ if (create_lock_applied) {
+ SCTP_ASOC_CREATE_UNLOCK(inp);
+ }
+ if ((stcb) && hold_tcblock) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ if (stcb && free_cnt_applied) {
+ atomic_add_int(&stcb->asoc.refcnt, -1);
+ }
+#ifdef INVARIANTS
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (stcb) {
+ if (mtx_owned(&stcb->tcb_mtx)) {
+ panic("Leaving with tcb mtx owned?");
+ }
+ if (mtx_owned(&stcb->tcb_send_mtx)) {
+ panic("Leaving with tcb send mtx owned?");
+ }
+ }
+#endif
+#endif
+ if (top) {
+ sctp_m_freem(top);
+ }
+ if (control) {
+ sctp_m_freem(control);
+ }
+ 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 100755
index 0000000000..4849c2b492
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_output.h
@@ -0,0 +1,248 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_output.h 362054 2020-06-11 13:34:09Z tuexen $");
+#endif
+
+#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 *);
+
+int sctp_get_frag_point(struct sctp_tcb *, struct sctp_association *);
+
+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 100755
index 0000000000..94aec1847a
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_pcb.c
@@ -0,0 +1,8096 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_pcb.c 362497 2020-06-22 14:01:31Z markj $");
+#endif
+
+#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 :-)
+ */
+ 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();
+ 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 = NULL;
+ struct sctp_ifa *sctp_ifap = NULL;
+ 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_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();
+ return (NULL);
+ }
+ }
+ }
+ if (sctp_ifnp == NULL) {
+ /* build one and add it, can't hold lock
+ * until after malloc done though.
+ */
+ SCTP_IPI_ADDR_WUNLOCK();
+ SCTP_MALLOC(sctp_ifnp, struct sctp_ifn *,
+ sizeof(struct sctp_ifn), SCTP_M_IFN);
+ if (sctp_ifnp == NULL) {
+#ifdef INVARIANTS
+ panic("No memory for IFN");
+#endif
+ return (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, addr->sa_family);
+ 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);
+ SCTP_IPI_ADDR_WLOCK();
+ 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();
+ 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_IPI_ADDR_WUNLOCK();
+ SCTP_MALLOC(sctp_ifap, struct sctp_ifa *, sizeof(struct sctp_ifa), SCTP_M_IFA);
+ if (sctp_ifap == NULL) {
+#ifdef INVARIANTS
+ panic("No memory for IFA");
+#endif
+ return (NULL);
+ }
+ 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;
+ }
+ SCTP_IPI_ADDR_WLOCK();
+ 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 (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 == NULL) {
+ SCTP_PRINTF("TSNH ep_associd\n");
+ return (NULL);
+ }
+ 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;
+
+ 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_RUNLOCK(inp);
+ SCTP_INP_INFO_WLOCK();
+ 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);
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_RLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ 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 = SCTP_DEFAULT_MAXSEGMENT;
+ 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;
+
+#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__)
+ INP_LOCK_INIT(&inp->ip_inp.inp, "inp", "sctpinp");
+#endif
+ SCTP_INP_READ_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 = 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);
+
+ 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
+ * since we really don't care if they are running
+ * or not just blast in the new_inp into all of
+ * them.
+ */
+
+ stcb->asoc.dack_timer.ep = (void *)new_inp;
+ stcb->asoc.asconf_timer.ep = (void *)new_inp;
+ stcb->asoc.strreset_timer.ep = (void *)new_inp;
+ stcb->asoc.shut_guard_timer.ep = (void *)new_inp;
+ stcb->asoc.autoclose_timer.ep = (void *)new_inp;
+ stcb->asoc.delete_prim_timer.ep = (void *)new_inp;
+ /* now what about the nets? */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ net->pmtu_timer.ep = (void *)new_inp;
+ net->hb_timer.ep = (void *)new_inp;
+ net->rxt_timer.ep = (void *)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
+
+
+/* sctp_ifap is used to bypass normal local address validation checks */
+int
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+sctp_inpcb_bind(struct socket *so, struct sockaddr *addr,
+ struct sctp_ifa *sctp_ifap, struct thread *p)
+#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
+{
+ /* bind a ep to a socket address */
+ struct sctppcbhead *head;
+ struct sctp_inpcb *inp, *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;
+
+ lport = 0;
+ bindall = 1;
+ inp = (struct sctp_inpcb *)so->so_pcb;
+#if (defined(__FreeBSD__) || defined(__APPLE__)) && !defined(__Userspace__)
+ ip_inp = (struct inpcb *)so->so_pcb;
+#endif
+#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) {
+ /* already did a bind, subsequent binds NOT allowed ! */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ return (EINVAL);
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#ifdef INVARIANTS
+ if (p == NULL)
+ panic("null proc/thread");
+#endif
+#endif
+ if (addr != NULL) {
+ switch (addr->sa_family) {
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ /* IPV6_V6ONLY socket? */
+ if (SCTP_IPV6_V6ONLY(inp)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ return (EINVAL);
+ }
+#ifdef HAVE_SA_LEN
+ if (addr->sa_len != sizeof(*sin)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ return (EINVAL);
+ }
+#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 (p && (error = prison_local_ip4(p->td_ucred, &sin->sin_addr)) != 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ return (error);
+ }
+#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)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ return (EINVAL);
+ }
+#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 (p && (error = prison_local_ip6(p->td_ucred, &sin6->sin6_addr,
+ (SCTP_IPV6_V6ONLY(inp) != 0))) != 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ return (error);
+ }
+#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) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ return (EINVAL);
+ }
+#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
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ return (EINVAL);
+ }
+#elif defined(__FreeBSD__) && !defined(__Userspace__)
+ error = scope6_check_id(sin6, MODULE_GLOBAL(ip6_use_defzone));
+ if (error != 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ return (error);
+ }
+#else
+ if (in6_embedscope(&sin6->sin6_addr, sin6) != 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ return (EINVAL);
+ }
+#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)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ return (EINVAL);
+ }
+#endif
+ sconn = (struct sockaddr_conn *)addr;
+ lport = sconn->sconn_port;
+ if (sconn->sconn_addr != NULL) {
+ bindall = 0;
+ }
+ break;
+ }
+#endif
+ default:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EAFNOSUPPORT);
+ return (EAFNOSUPPORT);
+ }
+ }
+ SCTP_INP_INFO_WLOCK();
+ SCTP_INP_WLOCK(inp);
+ /* Setup a vrf_id to be the default for the non-bind-all case. */
+ vrf_id = inp->def_vrf_id;
+
+ /* increase our count due to the unlock we do */
+ SCTP_INP_INCR_REF(inp);
+ 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 ((p != NULL) && ((error =
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ priv_check(p, PRIV_NETINET_RESERVEDPORT)
+#elif defined(__APPLE__) && !defined(__Userspace__)
+ suser(p->p_ucred, &p->p_acflag)
+#elif defined(__Userspace__) /* must be true to use raw socket */
+ 1
+#else
+ suser(p, 0)
+#endif
+ ) != 0)) {
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ return (error);
+ }
+ }
+#endif
+ 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_DECR_REF(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EADDRINUSE);
+ return (EADDRINUSE);
+ }
+#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_DECR_REF(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EADDRINUSE);
+ return (EADDRINUSE);
+ }
+ }
+ continue_anyway:
+ SCTP_INP_WLOCK(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 {
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EADDRINUSE);
+ return (EADDRINUSE);
+ }
+ }
+ }
+ } else {
+ uint16_t first, last, candidate;
+ uint16_t count;
+ int done;
+
+#if defined(_WIN32) && !defined(__Userspace__)
+ first = 1;
+ last = 0xffff;
+#else
+#if defined(__Userspace__)
+ /* TODO ensure uid is 0, etc... */
+#elif (defined(__FreeBSD__) || defined(__APPLE__)) && !defined(__Userspace__)
+ 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 (p && (error =
+#if defined(__FreeBSD__)
+ priv_check(p, PRIV_NETINET_RESERVEDPORT)
+#elif defined(__APPLE__)
+ suser(p->p_ucred, &p->p_acflag)
+#else
+ suser(p, 0)
+#endif
+ )) {
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error);
+ return (error);
+ }
+ first = MODULE_GLOBAL(ipport_lowfirstauto);
+ last = MODULE_GLOBAL(ipport_lowlastauto);
+ } else {
+#endif
+ first = MODULE_GLOBAL(ipport_firstauto);
+ last = MODULE_GLOBAL(ipport_lastauto);
+#if (defined(__FreeBSD__) || defined(__APPLE__)) && !defined(__Userspace__)
+ }
+#endif
+#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);
+
+ done = 0;
+ while (!done) {
+#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) {
+ done = 1;
+ }
+#else
+ if (sctp_isport_inuse(inp, htons(candidate), inp->def_vrf_id) == NULL) {
+ done = 1;
+ }
+#endif
+ if (!done) {
+ if (--count == 0) {
+ SCTP_INP_DECR_REF(inp);
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EADDRINUSE);
+ return (EADDRINUSE);
+ }
+ if (candidate == last)
+ candidate = first;
+ else
+ candidate = candidate + 1;
+ }
+ }
+ lport = htons(candidate);
+ }
+ SCTP_INP_DECR_REF(inp);
+ 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.
+ */
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ return (EINVAL);
+ }
+ /* 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) {
+ /* Can't find an interface with that address */
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EADDRNOTAVAIL);
+ return (EADDRNOTAVAIL);
+ }
+#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. */
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ return (EINVAL);
+ }
+ }
+#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) {
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ return (error);
+ }
+ 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 */
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_UNBOUND;
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ return (0);
+}
+
+
+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 *asoc, *nasoc;
+ 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();
+ so = inp->sctp_socket;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
+ /* been here before.. eeks.. get out of here */
+ SCTP_PRINTF("This conflict in free SHOULD not be happening! from %d, imm %d\n", from, immediate);
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 1);
+#endif
+ return;
+ }
+ SCTP_ASOC_CREATE_LOCK(inp);
+ SCTP_INP_INFO_WLOCK();
+
+ SCTP_INP_WLOCK(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(asoc, &inp->sctp_asoc_list, sctp_tcblist, nasoc) {
+ SCTP_TCB_LOCK(asoc);
+ if (asoc->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ /* Skip guys being freed */
+ cnt_in_sd++;
+ if (asoc->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(asoc, SCTP_STATE_IN_ACCEPT_QUEUE);
+ sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, asoc, NULL);
+ }
+ SCTP_TCB_UNLOCK(asoc);
+ continue;
+ }
+ if (((SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED)) &&
+ (asoc->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, asoc, SCTP_PCBFREE_NOFORCE,
+ SCTP_FROM_SCTP_PCB + SCTP_LOC_2) == 0) {
+ cnt_in_sd++;
+ }
+ continue;
+ }
+ /* Disconnect the socket please */
+ asoc->sctp_socket = NULL;
+ SCTP_ADD_SUBSTATE(asoc, SCTP_STATE_CLOSED_SOCKET);
+ if ((asoc->asoc.size_on_reasm_queue > 0) ||
+ (asoc->asoc.control_pdapi) ||
+ (asoc->asoc.size_on_all_streams > 0) ||
+ (so && (so->so_rcv.sb_cc > 0))) {
+ /* Left with Data unread */
+ struct mbuf *op_err;
+
+ op_err = sctp_generate_cause(SCTP_CAUSE_USER_INITIATED_ABT, "");
+ asoc->sctp_ep->last_abort_code = SCTP_FROM_SCTP_PCB + SCTP_LOC_3;
+ sctp_send_abort_tcb(asoc, op_err, SCTP_SO_LOCKED);
+ SCTP_STAT_INCR_COUNTER32(sctps_aborted);
+ if ((SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ if (sctp_free_assoc(inp, asoc,
+ SCTP_PCBFREE_NOFORCE, SCTP_FROM_SCTP_PCB + SCTP_LOC_4) == 0) {
+ cnt_in_sd++;
+ }
+ continue;
+ } else if (TAILQ_EMPTY(&asoc->asoc.send_queue) &&
+ TAILQ_EMPTY(&asoc->asoc.sent_queue) &&
+ (asoc->asoc.stream_queue_cnt == 0)) {
+ if ((*asoc->asoc.ss_functions.sctp_ss_is_user_msgs_incomplete)(asoc, &asoc->asoc)) {
+ goto abort_anyway;
+ }
+ if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) &&
+ (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
+ struct sctp_nets *netp;
+
+ /*
+ * there is nothing queued to send,
+ * so I send shutdown
+ */
+ if ((SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ SCTP_SET_STATE(asoc, SCTP_STATE_SHUTDOWN_SENT);
+ sctp_stop_timers_for_shutdown(asoc);
+ if (asoc->asoc.alternate) {
+ netp = asoc->asoc.alternate;
+ } else {
+ netp = asoc->asoc.primary_destination;
+ }
+ sctp_send_shutdown(asoc, netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, asoc->sctp_ep, asoc,
+ netp);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, asoc->sctp_ep, asoc, NULL);
+ sctp_chunk_output(inp, asoc, SCTP_OUTPUT_FROM_SHUT_TMR, SCTP_SO_LOCKED);
+ }
+ } else {
+ /* mark into shutdown pending */
+ SCTP_ADD_SUBSTATE(asoc, SCTP_STATE_SHUTDOWN_PENDING);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, asoc->sctp_ep, asoc, NULL);
+ if ((*asoc->asoc.ss_functions.sctp_ss_is_user_msgs_incomplete)(asoc, &asoc->asoc)) {
+ SCTP_ADD_SUBSTATE(asoc, SCTP_STATE_PARTIAL_MSG_LEFT);
+ }
+ if (TAILQ_EMPTY(&asoc->asoc.send_queue) &&
+ TAILQ_EMPTY(&asoc->asoc.sent_queue) &&
+ (asoc->asoc.state & SCTP_STATE_PARTIAL_MSG_LEFT)) {
+ struct mbuf *op_err;
+ abort_anyway:
+ op_err = sctp_generate_cause(SCTP_CAUSE_USER_INITIATED_ABT, "");
+ asoc->sctp_ep->last_abort_code = SCTP_FROM_SCTP_PCB + SCTP_LOC_5;
+ sctp_send_abort_tcb(asoc, op_err, SCTP_SO_LOCKED);
+ SCTP_STAT_INCR_COUNTER32(sctps_aborted);
+ if ((SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ if (sctp_free_assoc(inp, asoc,
+ SCTP_PCBFREE_NOFORCE,
+ SCTP_FROM_SCTP_PCB + SCTP_LOC_6) == 0) {
+ cnt_in_sd++;
+ }
+ continue;
+ } else {
+ sctp_chunk_output(inp, asoc, SCTP_OUTPUT_FROM_CLOSING, SCTP_SO_LOCKED);
+ }
+ }
+ cnt_in_sd++;
+ SCTP_TCB_UNLOCK(asoc);
+ }
+ /* 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) !=
+ SCTP_PCB_FLAGS_UNBOUND) {
+ /*
+ * 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(asoc, &inp->sctp_asoc_list, sctp_tcblist, nasoc) {
+ SCTP_TCB_LOCK(asoc);
+ if (asoc->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ if (asoc->asoc.state & SCTP_STATE_IN_ACCEPT_QUEUE) {
+ SCTP_CLEAR_SUBSTATE(asoc, SCTP_STATE_IN_ACCEPT_QUEUE);
+ sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, asoc, NULL);
+ }
+ cnt++;
+ SCTP_TCB_UNLOCK(asoc);
+ continue;
+ }
+ /* Free associations that are NOT killing us */
+ if ((SCTP_GET_STATE(asoc) != SCTP_STATE_COOKIE_WAIT) &&
+ ((asoc->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0)) {
+ struct mbuf *op_err;
+
+ op_err = sctp_generate_cause(SCTP_CAUSE_USER_INITIATED_ABT, "");
+ asoc->sctp_ep->last_abort_code = SCTP_FROM_SCTP_PCB + SCTP_LOC_7;
+ sctp_send_abort_tcb(asoc, op_err, SCTP_SO_LOCKED);
+ SCTP_STAT_INCR_COUNTER32(sctps_aborted);
+ } else if (asoc->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
+ cnt++;
+ SCTP_TCB_UNLOCK(asoc);
+ continue;
+ }
+ if ((SCTP_GET_STATE(asoc) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ SCTP_STAT_DECR_GAUGE32(sctps_currestab);
+ }
+ if (sctp_free_assoc(inp, asoc, 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++;
+
+ 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();
+ /* Now we release all locks. Since this INP
+ * cannot be found anymore except possibly by the
+ * kill timer that might be running. We call
+ * the drain function here. It should hit the case
+ * were it sees the ACTIVE flag cleared and exit
+ * out freeing us to proceed and destroy everything.
+ */
+ if (from != SCTP_CALLED_FROM_INPKILL_TIMER) {
+ (void)SCTP_OS_TIMER_STOP_DRAIN(&inp->sctp_ep.signature_change.timer);
+ } else {
+ /* Probably un-needed */
+ (void)SCTP_OS_TIMER_STOP(&inp->sctp_ep.signature_change.timer);
+ }
+
+#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)
+ so->so_rcv.sb_cc -= 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\n", (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(asoc, &inp->sctp_asoc_free_list, sctp_tcblist, nasoc) {
+ LIST_REMOVE(asoc, sctp_tcblist);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asoc), asoc);
+ 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_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) {
+ imtu = SCTP_GATHER_MTU_FROM_INTFC(net->ro._s_addr->ifn_p);
+ } 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);
+ }
+#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.
+ */
+struct sctp_tcb *
+sctp_aloc_assoc(struct sctp_inpcb *inp, struct sockaddr *firstaddr,
+ int *error, uint32_t override_tag, 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;
+
+ /*
+ * 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);
+ }
+ SCTP_INP_RLOCK(inp);
+ 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_INP_RUNLOCK(inp);
+ 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_INP_RUNLOCK(inp);
+ 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) != 0) ||
+ (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) != 0) &&
+ (SCTP_IPV6_V6ONLY(inp) != 0)))) {
+#else
+ (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) != 0) &&
+ (SCTP_IPV6_V6ONLY(inp) != 0))) {
+#endif
+ /* Invalid address */
+ SCTP_INP_RUNLOCK(inp);
+ 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_INP_RUNLOCK(inp);
+ 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_INP_RUNLOCK(inp);
+ 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_INP_RUNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ *error = EINVAL;
+ return (NULL);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ 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(inp->sctp_socket, 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);
+ SCTP_TCB_SEND_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, vrf_id, o_streams))) {
+ /* failed */
+ SCTP_TCB_LOCK_DESTROY(stcb);
+ SCTP_TCB_SEND_LOCK_DESTROY(stcb);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asoc), stcb);
+ SCTP_DECR_ASOC_COUNT();
+ *error = err;
+ return (NULL);
+ }
+ /* and the port */
+ SCTP_INP_INFO_WLOCK();
+ SCTP_INP_WLOCK(inp);
+ if (inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE | SCTP_PCB_FLAGS_SOCKET_ALLGONE)) {
+ /* inpcb freed while alloc going on */
+ SCTP_TCB_LOCK_DESTROY(stcb);
+ SCTP_TCB_SEND_LOCK_DESTROY(stcb);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asoc), stcb);
+ SCTP_INP_WUNLOCK(inp);
+ SCTP_INP_INFO_WUNLOCK();
+ SCTP_DECR_ASOC_COUNT();
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, EINVAL);
+ *error = EINVAL;
+ 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);
+ SCTP_INP_INFO_WUNLOCK();
+
+ if ((err = 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);
+ SCTP_TCB_SEND_LOCK_DESTROY(stcb);
+ 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);
+ }
+ SCTP_INP_WUNLOCK(inp);
+ SCTPDBG(SCTP_DEBUG_PCB1, "Association %p now allocated\n", (void *)stcb);
+ 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 == 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);
+}
+
+void
+sctp_delete_from_timewait(uint32_t tag, uint16_t lport, uint16_t rport)
+{
+ struct sctpvtaghead *chain;
+ struct sctp_tagblock *twait_block;
+ int found = 0;
+ int i;
+
+ 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].v_tag == tag) &&
+ (twait_block->vtag_block[i].lport == lport) &&
+ (twait_block->vtag_block[i].rport == rport)) {
+ twait_block->vtag_block[i].tv_sec_at_expire = 0;
+ twait_block->vtag_block[i].v_tag = 0;
+ twait_block->vtag_block[i].lport = 0;
+ twait_block->vtag_block[i].rport = 0;
+ found = 1;
+ break;
+ }
+ }
+ if (found)
+ break;
+ }
+}
+
+int
+sctp_is_in_timewait(uint32_t tag, uint16_t lport, uint16_t rport)
+{
+ struct sctpvtaghead *chain;
+ struct sctp_tagblock *twait_block;
+ int found = 0;
+ int i;
+
+ SCTP_INP_INFO_WLOCK();
+ 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].v_tag == tag) &&
+ (twait_block->vtag_block[i].lport == lport) &&
+ (twait_block->vtag_block[i].rport == rport)) {
+ found = 1;
+ break;
+ }
+ }
+ if (found)
+ break;
+ }
+ SCTP_INP_INFO_WUNLOCK();
+ return (found);
+}
+
+
+void
+sctp_add_vtag_to_timewait(uint32_t tag, uint32_t time, uint16_t lport, uint16_t rport)
+{
+ struct sctpvtaghead *chain;
+ struct sctp_tagblock *twait_block;
+ struct timeval now;
+ int set, i;
+
+ if (time == 0) {
+ /* Its disabled */
+ return;
+ }
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+ chain = &SCTP_BASE_INFO(vtag_timewait)[(tag % SCTP_STACK_VTAG_HASH_SIZE)];
+ set = 0;
+ 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) {
+ twait_block->vtag_block[i].tv_sec_at_expire =
+ now.tv_sec + time;
+ twait_block->vtag_block[i].v_tag = tag;
+ twait_block->vtag_block[i].lport = lport;
+ twait_block->vtag_block[i].rport = rport;
+ set = 1;
+ } else if ((twait_block->vtag_block[i].v_tag) &&
+ ((long)twait_block->vtag_block[i].tv_sec_at_expire < now.tv_sec)) {
+ /* Audit expires this guy */
+ twait_block->vtag_block[i].tv_sec_at_expire = 0;
+ twait_block->vtag_block[i].v_tag = 0;
+ twait_block->vtag_block[i].lport = 0;
+ twait_block->vtag_block[i].rport = 0;
+ if (set == 0) {
+ /* Reuse it for my new tag */
+ twait_block->vtag_block[i].tv_sec_at_expire = now.tv_sec + time;
+ twait_block->vtag_block[i].v_tag = tag;
+ twait_block->vtag_block[i].lport = lport;
+ twait_block->vtag_block[i].rport = rport;
+ set = 1;
+ }
+ }
+ }
+ 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);
+ twait_block->vtag_block[0].tv_sec_at_expire = now.tv_sec + time;
+ twait_block->vtag_block[0].v_tag = tag;
+ twait_block->vtag_block[0].lport = lport;
+ twait_block->vtag_block[0].rport = 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
+
+#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)) {
+ /*
+ * Need to add a PD-API aborted indication.
+ * Setting the control_pdapi assures that it will
+ * be added right after this msg.
+ */
+ uint32_t strseq;
+ stcb->asoc.control_pdapi = sq;
+ strseq = (sq->sinfo_stream << 16) | (sq->mid & 0x0000ffff);
+ sctp_ulp_notify(SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION,
+ stcb,
+ SCTP_PARTIAL_DELIVERY_ABORTED,
+ (void *)&strseq,
+ SCTP_SO_LOCKED);
+ stcb->asoc.control_pdapi = NULL;
+ }
+ }
+ /* 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);
+ }
+ SCTP_TCB_UNLOCK(stcb);
+ 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);
+ }
+
+#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_add_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);
+ }
+ /* 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, SCTP_BASE_SYSCTL(sctp_vtag_time_wait),
+ 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 */
+ SCTP_TCB_SEND_LOCK(stcb);
+ 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, 1);
+ 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);
+ }
+ }
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ /*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);
+ SCTP_TCB_SEND_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,
+ RFPROC,
+ 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));
+#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));
+#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).
+ */
+ 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);
+ }
+ /* 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_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_add_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_add_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,
+ 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_add_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,
+ 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_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);
+ sctp_free_remote_addr(net);
+ if (net == stcb->asoc.primary_destination) {
+ stcb->asoc.primary_destination = NULL;
+ sctp_select_primary_destination(stcb);
+ }
+ }
+ }
+ 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) && (stcb->asoc.alternate)) {
+ 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);
+ }
+}
+
+int
+sctp_is_vtag_good(uint32_t tag, uint16_t lport, uint16_t rport, struct timeval *now)
+{
+ /*
+ * This function serves two purposes. It will see if a TAG can be
+ * re-used and return 1 for yes it is ok and 0 for don't use that
+ * tag. A secondary function it will do is purge out old tags that
+ * can be removed.
+ */
+ struct sctpvtaghead *chain;
+ struct sctp_tagblock *twait_block;
+ struct sctpasochead *head;
+ struct sctp_tcb *stcb;
+ int i;
+
+ SCTP_INP_INFO_RLOCK();
+ 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;
+ }
+ /* Its a used tag set */
+ SCTP_INP_INFO_RUNLOCK();
+ return (0);
+ }
+ }
+ chain = &SCTP_BASE_INFO(vtag_timewait)[(tag % SCTP_STACK_VTAG_HASH_SIZE)];
+ /* Now what about timed wait ? */
+ LIST_FOREACH(twait_block, chain, sctp_nxt_tagblock) {
+ /*
+ * Block(s) are present, lets see if we have this tag in the
+ * list
+ */
+ for (i = 0; i < SCTP_NUMBER_IN_VTAG_BLOCK; i++) {
+ if (twait_block->vtag_block[i].v_tag == 0) {
+ /* not used */
+ continue;
+ } else if ((long)twait_block->vtag_block[i].tv_sec_at_expire <
+ now->tv_sec) {
+ /* Audit expires this guy */
+ twait_block->vtag_block[i].tv_sec_at_expire = 0;
+ twait_block->vtag_block[i].v_tag = 0;
+ twait_block->vtag_block[i].lport = 0;
+ twait_block->vtag_block[i].rport = 0;
+ } else if ((twait_block->vtag_block[i].v_tag == tag) &&
+ (twait_block->vtag_block[i].lport == lport) &&
+ (twait_block->vtag_block[i].rport == rport)) {
+ /* Bad tag, sorry :< */
+ SCTP_INP_INFO_RUNLOCK();
+ return (0);
+ }
+ }
+ }
+ SCTP_INP_INFO_RUNLOCK();
+ return (1);
+}
+
+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.
+ */
+}
+
+void
+sctp_drain()
+{
+ /*
+ * 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_ITERATOR_DECL(vnet_iter);
+#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
+#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
+ 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();
+#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);
+}
diff --git a/netwerk/sctp/src/netinet/sctp_pcb.h b/netwerk/sctp/src/netinet/sctp_pcb.h
new file mode 100755
index 0000000000..3b0ea3e1ff
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_pcb.h
@@ -0,0 +1,879 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_pcb.h 362106 2020-06-12 16:31:13Z tuexen $");
+#endif
+
+#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_mutex_t ipi_ep_mtx;
+ userland_mutex_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;
+#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
+ struct sctp_iterator *inp_starting_point_for_iterator;
+ 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;
+ 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);
+#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));
+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;
+ struct mtx tcb_send_mtx;
+#elif defined(SCTP_PROCESS_LEVEL_LOCKS)
+ userland_mutex_t tcb_mtx;
+ userland_mutex_t tcb_send_mtx;
+#elif defined(__APPLE__) && !defined(__Userspace__)
+ lck_mtx_t* tcb_mtx;
+ lck_mtx_t* tcb_send_mtx;
+#elif defined(_WIN32) && !defined(__Userspace__)
+ struct spinlock tcb_lock;
+ struct spinlock tcb_send_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 *);
+#elif defined(_WIN32) && !defined(__Userspace__)
+int sctp_inpcb_bind(struct socket *, 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 *);
+#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, 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, 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, uint16_t, uint16_t, struct proc *,
+ int);
+#endif
+
+int sctp_free_assoc(struct sctp_inpcb *, struct sctp_tcb *, int, int);
+
+
+void sctp_delete_from_timewait(uint32_t, uint16_t, uint16_t);
+
+int sctp_is_in_timewait(uint32_t tag, uint16_t lport, uint16_t rport);
+
+void
+sctp_add_vtag_to_timewait(uint32_t tag, uint32_t time, uint16_t lport, uint16_t rport);
+
+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 *);
+
+int sctp_is_vtag_good(uint32_t, uint16_t lport, uint16_t rport, struct timeval *);
+
+/* void sctp_drain(void); */
+
+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);
+
+/*-
+ * 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 100755
index 0000000000..8581f515f4
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_peeloff.c
@@ -0,0 +1,303 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_peeloff.c 362054 2020-06-11 13:34:09Z tuexen $");
+#endif
+
+#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;
+ n_inp->inp_starting_point_for_iterator = NULL;
+ /* 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 100755
index 0000000000..7e1c5eceeb
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_peeloff.h
@@ -0,0 +1,70 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_peeloff.h 309607 2016-12-06 10:21:25Z tuexen $");
+#endif
+
+#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 100755
index 0000000000..b8eefb3550
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_process_lock.h
@@ -0,0 +1,669 @@
+/*-
+ * 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_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_DESTROY()
+#define SCTP_IPI_COUNT_INIT()
+#define SCTP_IPI_COUNT_DESTROY()
+#endif
+
+#define SCTP_TCB_SEND_LOCK_INIT(_tcb)
+#define SCTP_TCB_SEND_LOCK_DESTROY(_tcb)
+#define SCTP_TCB_SEND_LOCK(_tcb)
+#define SCTP_TCB_SEND_UNLOCK(_tcb)
+
+/* 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_INIT(_inp)
+#define SCTP_INP_READ_DESTROY(_inp)
+#define SCTP_INP_READ_LOCK(_inp)
+#define SCTP_INP_READ_UNLOCK(_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()
+
+#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_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_INIT(_inp) \
+ InitializeCriticalSection(&(_inp)->inp_rdata_mtx)
+#define SCTP_INP_READ_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_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_TCB_SEND_LOCK_INIT(_tcb) \
+ InitializeCriticalSection(&(_tcb)->tcb_send_mtx)
+#define SCTP_TCB_SEND_LOCK_DESTROY(_tcb) \
+ DeleteCriticalSection(&(_tcb)->tcb_send_mtx)
+#define SCTP_TCB_SEND_LOCK(_tcb) \
+ EnterCriticalSection(&(_tcb)->tcb_send_mtx)
+#define SCTP_TCB_SEND_UNLOCK(_tcb) \
+ LeaveCriticalSection(&(_tcb)->tcb_send_mtx)
+
+#define SCTP_INP_INCR_REF(_inp) atomic_add_int(&((_inp)->refcount), 1)
+#define SCTP_INP_DECR_REF(_inp) atomic_add_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: wq_addr_mtx already locked", __func__))
+#define SCTP_WQ_ADDR_UNLOCK() \
+ KASSERT(pthread_mutex_unlock(&SCTP_BASE_INFO(wq_addr_mtx)) == 0, ("%s: wq_addr_mtx not locked", __func__))
+#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: wq_addr_mtx not locked", __func__))
+
+#define SCTP_INP_INFO_LOCK_INIT() \
+ (void)pthread_mutex_init(&SCTP_BASE_INFO(ipi_ep_mtx), &SCTP_BASE_VAR(mtx_attr))
+#define SCTP_INP_INFO_LOCK_DESTROY() \
+ (void)pthread_mutex_destroy(&SCTP_BASE_INFO(ipi_ep_mtx))
+#ifdef INVARIANTS
+#define SCTP_INP_INFO_RLOCK() \
+ KASSERT(pthread_mutex_lock(&SCTP_BASE_INFO(ipi_ep_mtx)) == 0, ("%s: ipi_ep_mtx already locked", __func__))
+#define SCTP_INP_INFO_WLOCK() \
+ KASSERT(pthread_mutex_lock(&SCTP_BASE_INFO(ipi_ep_mtx)) == 0, ("%s: ipi_ep_mtx already locked", __func__))
+#define SCTP_INP_INFO_RUNLOCK() \
+ KASSERT(pthread_mutex_unlock(&SCTP_BASE_INFO(ipi_ep_mtx)) == 0, ("%s: ipi_ep_mtx not locked", __func__))
+#define SCTP_INP_INFO_WUNLOCK() \
+ KASSERT(pthread_mutex_unlock(&SCTP_BASE_INFO(ipi_ep_mtx)) == 0, ("%s: ipi_ep_mtx not locked", __func__))
+#else
+#define SCTP_INP_INFO_RLOCK() \
+ (void)pthread_mutex_lock(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_WLOCK() \
+ (void)pthread_mutex_lock(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_RUNLOCK() \
+ (void)pthread_mutex_unlock(&SCTP_BASE_INFO(ipi_ep_mtx))
+#define SCTP_INP_INFO_WUNLOCK() \
+ (void)pthread_mutex_unlock(&SCTP_BASE_INFO(ipi_ep_mtx))
+#endif
+#define SCTP_INP_INFO_TRYLOCK() \
+ (!(pthread_mutex_trylock(&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: ipi_pktlog_mtx already locked", __func__))
+#define SCTP_IP_PKTLOG_UNLOCK() \
+ KASSERT(pthread_mutex_unlock(&SCTP_BASE_INFO(ipi_pktlog_mtx)) == 0, ("%s: ipi_pktlog_mtx not locked", __func__))
+#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_INIT(_inp) \
+ (void)pthread_mutex_init(&(_inp)->inp_rdata_mtx, &SCTP_BASE_VAR(mtx_attr))
+#define SCTP_INP_READ_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: inp_rdata_mtx already locked", __func__))
+#define SCTP_INP_READ_UNLOCK(_inp) \
+ KASSERT(pthread_mutex_unlock(&(_inp)->inp_rdata_mtx) == 0, ("%s: inp_rdata_mtx not locked", __func__))
+#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_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: inp_mtx already locked", __func__)) \
+} 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: inp_mtx already locked", __func__))
+} while (0)
+#else
+#define SCTP_INP_RLOCK(_inp) \
+ KASSERT(pthread_mutex_lock(&(_inp)->inp_mtx) == 0, ("%s: inp_mtx already locked", __func__))
+#define SCTP_INP_WLOCK(_inp) \
+ KASSERT(pthread_mutex_lock(&(_inp)->inp_mtx) == 0, ("%s: inp_mtx already locked", __func__))
+#endif
+#define SCTP_INP_RUNLOCK(_inp) \
+ KASSERT(pthread_mutex_unlock(&(_inp)->inp_mtx) == 0, ("%s: inp_mtx not locked", __func__))
+#define SCTP_INP_WUNLOCK(_inp) \
+ KASSERT(pthread_mutex_unlock(&(_inp)->inp_mtx) == 0, ("%s: inp_mtx not locked", __func__))
+#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: inp_mtx not locked", __func__))
+#define SCTP_INP_WLOCK_ASSERT(_inp) \
+ KASSERT(pthread_mutex_trylock(&(_inp)->inp_mtx) == EBUSY, ("%s: inp_mtx not locked", __func__))
+#define SCTP_INP_INCR_REF(_inp) atomic_add_int(&((_inp)->refcount), 1)
+#define SCTP_INP_DECR_REF(_inp) atomic_add_int(&((_inp)->refcount), -1)
+
+#define SCTP_TCB_SEND_LOCK_INIT(_tcb) \
+ (void)pthread_mutex_init(&(_tcb)->tcb_send_mtx, &SCTP_BASE_VAR(mtx_attr))
+#define SCTP_TCB_SEND_LOCK_DESTROY(_tcb) \
+ (void)pthread_mutex_destroy(&(_tcb)->tcb_send_mtx)
+#ifdef INVARIANTS
+#define SCTP_TCB_SEND_LOCK(_tcb) \
+ KASSERT(pthread_mutex_lock(&(_tcb)->tcb_send_mtx) == 0, ("%s: tcb_send_mtx already locked", __func__))
+#define SCTP_TCB_SEND_UNLOCK(_tcb) \
+ KASSERT(pthread_mutex_unlock(&(_tcb)->tcb_send_mtx) == 0, ("%s: tcb_send_mtx not locked", __func__))
+#else
+#define SCTP_TCB_SEND_LOCK(_tcb) \
+ (void)pthread_mutex_lock(&(_tcb)->tcb_send_mtx)
+#define SCTP_TCB_SEND_UNLOCK(_tcb) \
+ (void)pthread_mutex_unlock(&(_tcb)->tcb_send_mtx)
+#endif
+
+#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: inp_create_mtx already locked", __func__)) \
+} while (0)
+#else
+#define SCTP_ASOC_CREATE_LOCK(_inp) \
+ KASSERT(pthread_mutex_lock(&(_inp)->inp_create_mtx) == 0, ("%s: inp_create_mtx already locked", __func__))
+#endif
+#define SCTP_ASOC_CREATE_UNLOCK(_inp) \
+ KASSERT(pthread_mutex_unlock(&(_inp)->inp_create_mtx) == 0, ("%s: inp_create_mtx not locked", __func__))
+#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: tcb_mtx already locked", __func__)) \
+} while (0)
+#else
+#define SCTP_TCB_LOCK(_tcb) \
+ KASSERT(pthread_mutex_lock(&(_tcb)->tcb_mtx) == 0, ("%s: tcb_mtx already locked", __func__))
+#endif
+#define SCTP_TCB_UNLOCK(_tcb) \
+ KASSERT(pthread_mutex_unlock(&(_tcb)->tcb_mtx) == 0, ("%s: tcb_mtx not locked", __func__))
+#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: tcb_mtx not locked", __func__))
+#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: socket buffer not locked", __func__))
+#ifdef INVARIANTS
+#define SOCKBUF_LOCK(_so_buf) \
+ KASSERT(pthread_mutex_lock(SOCKBUF_MTX(_so_buf)) == 0, ("%s: sockbuf_mtx already locked", __func__))
+#define SOCKBUF_UNLOCK(_so_buf) \
+ KASSERT(pthread_mutex_unlock(SOCKBUF_MTX(_so_buf)) == 0, ("%s: sockbuf_mtx not locked", __func__))
+#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 */
+#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))
+
+
+/* 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_mutex_init(&SCTP_BASE_INFO(ipi_addr_mtx), &SCTP_BASE_VAR(mtx_attr))
+#define SCTP_IPI_ADDR_DESTROY() \
+ (void)pthread_mutex_destroy(&SCTP_BASE_INFO(ipi_addr_mtx))
+#ifdef INVARIANTS
+#define SCTP_IPI_ADDR_RLOCK() \
+ KASSERT(pthread_mutex_lock(&SCTP_BASE_INFO(ipi_addr_mtx)) == 0, ("%s: ipi_addr_mtx already locked", __func__))
+#define SCTP_IPI_ADDR_RUNLOCK() \
+ KASSERT(pthread_mutex_unlock(&SCTP_BASE_INFO(ipi_addr_mtx)) == 0, ("%s: ipi_addr_mtx not locked", __func__))
+#define SCTP_IPI_ADDR_WLOCK() \
+ KASSERT(pthread_mutex_lock(&SCTP_BASE_INFO(ipi_addr_mtx)) == 0, ("%s: ipi_addr_mtx already locked", __func__))
+#define SCTP_IPI_ADDR_WUNLOCK() \
+ KASSERT(pthread_mutex_unlock(&SCTP_BASE_INFO(ipi_addr_mtx)) == 0, ("%s: ipi_addr_mtx not locked", __func__))
+#else
+#define SCTP_IPI_ADDR_RLOCK() \
+ (void)pthread_mutex_lock(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_RUNLOCK() \
+ (void)pthread_mutex_unlock(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_WLOCK() \
+ (void)pthread_mutex_lock(&SCTP_BASE_INFO(ipi_addr_mtx))
+#define SCTP_IPI_ADDR_WUNLOCK() \
+ (void)pthread_mutex_unlock(&SCTP_BASE_INFO(ipi_addr_mtx))
+#endif
+
+/* 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: it_mtx already locked", __func__))
+#define SCTP_ITERATOR_UNLOCK() \
+ KASSERT(pthread_mutex_unlock(&sctp_it_ctl.it_mtx) == 0, ("%s: it_mtx not locked", __func__))
+#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: ipi_iterator_wq_mtx already locked", __func__))
+#define SCTP_IPI_ITERATOR_WQ_UNLOCK() \
+ KASSERT(pthread_mutex_unlock(&sctp_it_ctl.ipi_iterator_wq_mtx) == 0, ("%s: ipi_iterator_wq_mtx not locked", __func__))
+#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 100755
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 100755
index 0000000000..d535ee4639
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_sha1.h
@@ -0,0 +1,90 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD$");
+#endif
+
+
+#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 100755
index 0000000000..fad42467c2
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_ss_functions.c
@@ -0,0 +1,1130 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_ss_functions.c 362173 2020-06-14 09:50:00Z tuexen $");
+#endif
+
+#include <netinet/sctp_pcb.h>
+#if defined(__Userspace__)
+#include <netinet/sctp_os_userspace.h>
+#endif
+
+/*
+ * Default simple round-robin algorithm.
+ * Just interates 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 *, int);
+
+static void
+sctp_ss_default_remove(struct sctp_tcb *, struct sctp_association *,
+ struct sctp_stream_out *,
+ struct sctp_stream_queue_pending *, int);
+
+static void
+sctp_ss_default_init(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ int holds_lock)
+{
+ uint16_t i;
+
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(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 < stcb->asoc.streamoutcnt; i++) {
+ stcb->asoc.ss_functions.sctp_ss_add_to_stream(stcb, &stcb->asoc,
+ &stcb->asoc.strmout[i],
+ NULL, 1);
+ }
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ return;
+}
+
+static void
+sctp_ss_default_clear(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ int clear_values SCTP_UNUSED, int holds_lock)
+{
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ while (!TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
+ struct sctp_stream_out *strq;
+
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.rr.next_spoke);
+ strq->ss_params.rr.next_spoke.tqe_next = NULL;
+ strq->ss_params.rr.next_spoke.tqe_prev = NULL;
+ }
+ asoc->ss_data.last_out_stream = NULL;
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ return;
+}
+
+static void
+sctp_ss_default_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq)
+{
+ 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.rr.next_spoke.tqe_next = NULL;
+ strq->ss_params.rr.next_spoke.tqe_prev = NULL;
+ 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, int holds_lock)
+{
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ /* Add to wheel if not already on it and stream queue not empty */
+ if (!TAILQ_EMPTY(&strq->outqueue) &&
+ (strq->ss_params.rr.next_spoke.tqe_next == NULL) &&
+ (strq->ss_params.rr.next_spoke.tqe_prev == NULL)) {
+ TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel,
+ strq, ss_params.rr.next_spoke);
+ }
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ return;
+}
+
+static int
+sctp_ss_default_is_empty(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc)
+{
+ if (TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
+ return (1);
+ } else {
+ return (0);
+ }
+}
+
+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, int holds_lock)
+{
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ /* Remove from wheel if stream queue is empty and actually is on the wheel */
+ if (TAILQ_EMPTY(&strq->outqueue) &&
+ (strq->ss_params.rr.next_spoke.tqe_next != NULL ||
+ strq->ss_params.rr.next_spoke.tqe_prev != NULL)) {
+ 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.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;
+ }
+ }
+ TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.rr.next_spoke);
+ strq->ss_params.rr.next_spoke.tqe_next = NULL;
+ strq->ss_params.rr.next_spoke.tqe_prev = NULL;
+ }
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ 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;
+
+ if (asoc->ss_data.locked_on_sending) {
+ return (asoc->ss_data.locked_on_sending);
+ }
+ strqt = asoc->ss_data.last_out_stream;
+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.rr.next_spoke);
+ if (strq == NULL) {
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ }
+ }
+
+ /* 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;
+
+ asoc->ss_data.last_out_stream = strq;
+ if (stcb->asoc.idata_supported == 0) {
+ sp = TAILQ_FIRST(&strq->outqueue);
+ if ((sp != NULL) && (sp->some_taken == 1)) {
+ stcb->asoc.ss_data.locked_on_sending = strq;
+ } else {
+ stcb->asoc.ss_data.locked_on_sending = NULL;
+ }
+ } else {
+ stcb->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)
+{
+ /* 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)
+{
+ /* 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)
+{
+ /* Nothing to be done here */
+ return (-1);
+}
+
+static int
+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;
+
+ if (asoc->stream_queue_cnt != 1) {
+ return (0);
+ }
+ strq = asoc->ss_data.locked_on_sending;
+ if (strq == NULL) {
+ return (0);
+ }
+ sp = TAILQ_FIRST(&strq->outqueue);
+ if (sp == NULL) {
+ return (0);
+ }
+ return (!sp->msg_is_complete);
+}
+
+/*
+ * Real round-robin algorithm.
+ * Always interates 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, int holds_lock)
+{
+ struct sctp_stream_out *strqt;
+
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ if (!TAILQ_EMPTY(&strq->outqueue) &&
+ (strq->ss_params.rr.next_spoke.tqe_next == NULL) &&
+ (strq->ss_params.rr.next_spoke.tqe_prev == NULL)) {
+ if (TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
+ TAILQ_INSERT_HEAD(&asoc->ss_data.out.wheel, strq, ss_params.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.rr.next_spoke);
+ }
+ if (strqt != NULL) {
+ TAILQ_INSERT_BEFORE(strqt, strq, ss_params.rr.next_spoke);
+ } else {
+ TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel, strq, ss_params.rr.next_spoke);
+ }
+ }
+ }
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ return;
+}
+
+/*
+ * Real round-robin per packet algorithm.
+ * Always interates 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)
+{
+ 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;
+
+ strqt = asoc->ss_data.last_out_stream;
+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.rr.next_spoke);
+ if (strq == NULL) {
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ }
+ }
+
+ /* 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,
+ int clear_values, int holds_lock)
+{
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ while (!TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
+ struct sctp_stream_out *strq;
+
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ if (clear_values) {
+ strq->ss_params.prio.priority = 0;
+ }
+ TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.prio.next_spoke);
+ strq->ss_params.prio.next_spoke.tqe_next = NULL;
+ strq->ss_params.prio.next_spoke.tqe_prev = NULL;
+
+ }
+ asoc->ss_data.last_out_stream = NULL;
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ return;
+}
+
+static void
+sctp_ss_prio_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq)
+{
+ 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.prio.next_spoke.tqe_next = NULL;
+ strq->ss_params.prio.next_spoke.tqe_prev = NULL;
+ if (with_strq != NULL) {
+ strq->ss_params.prio.priority = with_strq->ss_params.prio.priority;
+ } else {
+ strq->ss_params.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,
+ int holds_lock)
+{
+ struct sctp_stream_out *strqt;
+
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ /* Add to wheel if not already on it and stream queue not empty */
+ if (!TAILQ_EMPTY(&strq->outqueue) &&
+ (strq->ss_params.prio.next_spoke.tqe_next == NULL) &&
+ (strq->ss_params.prio.next_spoke.tqe_prev == NULL)) {
+ if (TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
+ TAILQ_INSERT_HEAD(&asoc->ss_data.out.wheel, strq, ss_params.prio.next_spoke);
+ } else {
+ strqt = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ while (strqt != NULL && strqt->ss_params.prio.priority < strq->ss_params.prio.priority) {
+ strqt = TAILQ_NEXT(strqt, ss_params.prio.next_spoke);
+ }
+ if (strqt != NULL) {
+ TAILQ_INSERT_BEFORE(strqt, strq, ss_params.prio.next_spoke);
+ } else {
+ TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel, strq, ss_params.prio.next_spoke);
+ }
+ }
+ }
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ 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,
+ int holds_lock)
+{
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ /* Remove from wheel if stream queue is empty and actually is on the wheel */
+ if (TAILQ_EMPTY(&strq->outqueue) &&
+ (strq->ss_params.prio.next_spoke.tqe_next != NULL ||
+ strq->ss_params.prio.next_spoke.tqe_prev != NULL)) {
+ 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.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;
+ }
+ }
+ TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.prio.next_spoke);
+ strq->ss_params.prio.next_spoke.tqe_next = NULL;
+ strq->ss_params.prio.next_spoke.tqe_prev = NULL;
+ }
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ 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;
+
+ if (asoc->ss_data.locked_on_sending) {
+ return (asoc->ss_data.locked_on_sending);
+ }
+ strqt = asoc->ss_data.last_out_stream;
+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.prio.next_spoke);
+ if (strqn != NULL &&
+ strqn->ss_params.prio.priority == strqt->ss_params.prio.priority) {
+ strq = strqn;
+ } else {
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ }
+ }
+
+ /* 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)
+{
+ if (strq == NULL) {
+ return (-1);
+ }
+ *value = strq->ss_params.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)
+{
+ if (strq == NULL) {
+ return (-1);
+ }
+ strq->ss_params.prio.priority = value;
+ sctp_ss_prio_remove(stcb, asoc, strq, NULL, 1);
+ sctp_ss_prio_add(stcb, asoc, strq, NULL, 1);
+ return (1);
+}
+
+/*
+ * Fair bandwidth algorithm.
+ * Maintains an equal troughput per stream.
+ */
+static void
+sctp_ss_fb_clear(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ int clear_values, int holds_lock)
+{
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ while (!TAILQ_EMPTY(&asoc->ss_data.out.wheel)) {
+ struct sctp_stream_out *strq;
+
+ strq = TAILQ_FIRST(&asoc->ss_data.out.wheel);
+ if (clear_values) {
+ strq->ss_params.fb.rounds = -1;
+ }
+ TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.fb.next_spoke);
+ strq->ss_params.fb.next_spoke.tqe_next = NULL;
+ strq->ss_params.fb.next_spoke.tqe_prev = NULL;
+ }
+ asoc->ss_data.last_out_stream = NULL;
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ return;
+}
+
+static void
+sctp_ss_fb_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq)
+{
+ 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.fb.next_spoke.tqe_next = NULL;
+ strq->ss_params.fb.next_spoke.tqe_prev = NULL;
+ if (with_strq != NULL) {
+ strq->ss_params.fb.rounds = with_strq->ss_params.fb.rounds;
+ } else {
+ strq->ss_params.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,
+ int holds_lock)
+{
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ if (!TAILQ_EMPTY(&strq->outqueue) &&
+ (strq->ss_params.fb.next_spoke.tqe_next == NULL) &&
+ (strq->ss_params.fb.next_spoke.tqe_prev == NULL)) {
+ if (strq->ss_params.fb.rounds < 0)
+ strq->ss_params.fb.rounds = TAILQ_FIRST(&strq->outqueue)->length;
+ TAILQ_INSERT_TAIL(&asoc->ss_data.out.wheel, strq, ss_params.fb.next_spoke);
+ }
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ 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,
+ int holds_lock)
+{
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ /* Remove from wheel if stream queue is empty and actually is on the wheel */
+ if (TAILQ_EMPTY(&strq->outqueue) &&
+ (strq->ss_params.fb.next_spoke.tqe_next != NULL ||
+ strq->ss_params.fb.next_spoke.tqe_prev != NULL)) {
+ 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.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;
+ }
+ }
+ TAILQ_REMOVE(&asoc->ss_data.out.wheel, strq, ss_params.fb.next_spoke);
+ strq->ss_params.fb.next_spoke.tqe_next = NULL;
+ strq->ss_params.fb.next_spoke.tqe_prev = NULL;
+ }
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ 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;
+
+ if (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.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.fb.rounds >= 0) && (strq == NULL ||
+ strqt->ss_params.fb.rounds < strq->ss_params.fb.rounds)) {
+ strq = strqt;
+ }
+ }
+ if (strqt != NULL) {
+ strqt = TAILQ_NEXT(strqt, ss_params.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;
+
+ if (stcb->asoc.idata_supported == 0) {
+ sp = TAILQ_FIRST(&strq->outqueue);
+ if ((sp != NULL) && (sp->some_taken == 1)) {
+ stcb->asoc.ss_data.locked_on_sending = strq;
+ } else {
+ stcb->asoc.ss_data.locked_on_sending = NULL;
+ }
+ } else {
+ stcb->asoc.ss_data.locked_on_sending = NULL;
+ }
+ subtract = strq->ss_params.fb.rounds;
+ TAILQ_FOREACH(strqt, &asoc->ss_data.out.wheel, ss_params.fb.next_spoke) {
+ strqt->ss_params.fb.rounds -= subtract;
+ if (strqt->ss_params.fb.rounds < 0)
+ strqt->ss_params.fb.rounds = 0;
+ }
+ if (TAILQ_FIRST(&strq->outqueue)) {
+ strq->ss_params.fb.rounds = TAILQ_FIRST(&strq->outqueue)->length;
+ } else {
+ strq->ss_params.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, int holds_lock);
+
+static void
+sctp_ss_fcfs_init(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ int holds_lock)
+{
+ uint32_t x, n = 0, add_more = 1;
+ struct sctp_stream_queue_pending *sp;
+ uint16_t i;
+
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(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 < stcb->asoc.streamoutcnt; i++) {
+ sp = TAILQ_FIRST(&stcb->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, &stcb->asoc, &stcb->asoc.strmout[i], sp, 1);
+ add_more = 1;
+ }
+ }
+ n++;
+ }
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ return;
+}
+
+static void
+sctp_ss_fcfs_clear(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ int clear_values, int holds_lock)
+{
+ struct sctp_stream_queue_pending *sp;
+
+ if (clear_values) {
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ while (!TAILQ_EMPTY(&asoc->ss_data.out.list)) {
+ sp = TAILQ_FIRST(&asoc->ss_data.out.list);
+ TAILQ_REMOVE(&asoc->ss_data.out.list, sp, ss_next);
+ sp->ss_next.tqe_next = NULL;
+ sp->ss_next.tqe_prev = NULL;
+ }
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ }
+ return;
+}
+
+static void
+sctp_ss_fcfs_init_stream(struct sctp_tcb *stcb, struct sctp_stream_out *strq, struct sctp_stream_out *with_strq)
+{
+ 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.fb.next_spoke.tqe_next = NULL;
+ strq->ss_params.fb.next_spoke.tqe_prev = NULL;
+ 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,
+ int holds_lock)
+{
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ if (sp && (sp->ss_next.tqe_next == NULL) &&
+ (sp->ss_next.tqe_prev == NULL)) {
+ TAILQ_INSERT_TAIL(&asoc->ss_data.out.list, sp, ss_next);
+ }
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ return;
+}
+
+static int
+sctp_ss_fcfs_is_empty(struct sctp_tcb *stcb SCTP_UNUSED, struct sctp_association *asoc)
+{
+ if (TAILQ_EMPTY(&asoc->ss_data.out.list)) {
+ return (1);
+ } else {
+ return (0);
+ }
+}
+
+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,
+ int holds_lock)
+{
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ if (sp &&
+ ((sp->ss_next.tqe_next != NULL) ||
+ (sp->ss_next.tqe_prev != NULL))) {
+ TAILQ_REMOVE(&asoc->ss_data.out.list, sp, ss_next);
+ sp->ss_next.tqe_next = NULL;
+ sp->ss_next.tqe_prev = NULL;
+ }
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ 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;
+
+ 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);
+}
+
+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_ROUND_ROBIN */
+{
+#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_ROUND_ROBIN_PACKET */
+{
+#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_PRIORITY */
+{
+#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_FAIR_BANDWITH */
+{
+#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_FIRST_COME */
+{
+#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_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_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_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
+}
+};
diff --git a/netwerk/sctp/src/netinet/sctp_structs.h b/netwerk/sctp/src/netinet/sctp_structs.h
new file mode 100755
index 0000000000..3943b75669
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_structs.h
@@ -0,0 +1,1296 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_structs.h 362106 2020-06-12 16:31:13Z tuexen $");
+#endif
+
+#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
+
+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_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.
+ */
+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;
+};
+
+/*
+ * 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);
+
+
+/* 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 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;
+};
+
+/*
+ * This union holds all parameters per stream
+ * necessary for different stream schedulers.
+ */
+union scheduling_parameters {
+ struct ss_rr rr;
+ struct ss_prio prio;
+ struct ss_fb fb;
+};
+
+/* 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
+
+#define SCTP_MAX_STREAMS_AT_ONCE_RESET 200
+
+/* This struct is used to track the traffic on outbound streams */
+struct sctp_stream_out {
+ struct sctp_streamhead outqueue;
+ union 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;
+};
+
+/* 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;
+};
+
+/* 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;
+};
+
+/*
+ * 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,
+ int holds_lock);
+ void (*sctp_ss_clear)(struct sctp_tcb *stcb, struct sctp_association *asoc,
+ int clear_values, int holds_lock);
+ 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, int holds_lock);
+ int (*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, int holds_lock);
+ 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);
+ int (*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;
+ /*
+ * For the pd-api we should re-write this a bit more efficient. We
+ * could have multiple sctp_queued_to_read's that we are building at
+ * once. Now we only do this when we get ready to deliver to the
+ * socket buffer. Note that we depend on the fact that the struct is
+ * "stuck" on the read queue until we finish all the pd-api.
+ */
+ struct sctp_queued_to_read *control_pdapi;
+
+ 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;
+
+ /* 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 100755
index 0000000000..91068118e1
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_sysctl.c
@@ -0,0 +1,1625 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_sysctl.c 361934 2020-06-08 20:23:20Z tuexen $");
+#endif
+
+#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()
+{
+ 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;
+#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 mulitple 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, ipv4_local_scope, local_scope, site_scope;
+ int ipv4_addr_legal, ipv6_addr_legal;
+#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) {
+ /* use association specific values */
+ loopback_scope = stcb->asoc.scope.loopback_scope;
+ ipv4_local_scope = stcb->asoc.scope.ipv4_local_scope;
+ local_scope = stcb->asoc.scope.local_scope;
+ site_scope = stcb->asoc.scope.site_scope;
+ ipv4_addr_legal = stcb->asoc.scope.ipv4_addr_legal;
+ ipv6_addr_legal = stcb->asoc.scope.ipv6_addr_legal;
+#if defined(__Userspace__)
+ conn_addr_legal = stcb->asoc.scope.conn_addr_legal;
+#endif
+ } else {
+ /* Use generic values for endpoints. */
+ loopback_scope = 1;
+ ipv4_local_scope = 1;
+ local_scope = 1;
+ site_scope = 1;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ ipv6_addr_legal = 1;
+ if (SCTP_IPV6_V6ONLY(inp)) {
+ ipv4_addr_legal = 0;
+ } else {
+ ipv4_addr_legal = 1;
+ }
+#if defined(__Userspace__)
+ conn_addr_legal = 0;
+#endif
+ } else {
+ ipv6_addr_legal = 0;
+#if defined(__Userspace__)
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_CONN) {
+ conn_addr_legal = 1;
+ ipv4_addr_legal = 0;
+ } else {
+ conn_addr_legal = 0;
+ ipv4_addr_legal = 1;
+ }
+#else
+ ipv4_addr_legal = 1;
+#endif
+ }
+ }
+
+ /* neither Mac OS X nor FreeBSD support mulitple routing functions */
+ if ((vrf = sctp_find_vrf(inp->def_vrf_id)) == NULL) {
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_INP_INFO_RUNLOCK();
+ return (-1);
+ }
+ 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) {
+ /*
+ * 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) {
+ 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 && 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) {
+ 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) {
+ 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;
+ 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)
+#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);
+#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);
+
+#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 100755
index 0000000000..eb888338ca
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_sysctl.h
@@ -0,0 +1,632 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_sysctl.h 361895 2020-06-07 14:39:20Z tuexen $");
+#endif
+
+#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;
+ uint32_t sctp_diag_info_code;
+#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;
+#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 Muliple-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 sec */
+#define SCTPCTL_VALID_COOKIE_LIFE_DESC "Default cookie lifetime in seconds"
+#define SCTPCTL_VALID_COOKIE_LIFE_MIN 0
+#define SCTPCTL_VALID_COOKIE_LIFE_MAX 0xFFFFFFFF
+#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
+
+#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
+#endif
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+#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
+#endif
+
+#if defined(__APPLE__) && !defined(__Userspace__)
+#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 100755
index 0000000000..ad062b35ee
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_timer.c
@@ -0,0 +1,1633 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_timer.c 362054 2020-06-11 13:34:09Z tuexen $");
+#endif
+
+#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)
+{
+ if (net) {
+ 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)) {
+ 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 (stcb == NULL)
+ return (0);
+
+ if (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, SCTP_SO_NOT_LOCKED);
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * sctp_find_alternate_net() returns a non-NULL pointer as long
+ * the argument net is non-NULL.
+ */
+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;
+ int once;
+ /* 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 others but net */
+ 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 desination 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);
+ }
+ }
+ mnet = net;
+ once = 0;
+
+ if (mnet == NULL) {
+ mnet = TAILQ_FIRST(&stcb->asoc.nets);
+ if (mnet == NULL) {
+ return (NULL);
+ }
+ }
+ for (;;) {
+ alt = TAILQ_NEXT(mnet, sctp_next);
+ if (alt == NULL) {
+ once++;
+ if (once > 1) {
+ break;
+ }
+ alt = TAILQ_FIRST(&stcb->asoc.nets);
+ if (alt == NULL) {
+ return (NULL);
+ }
+ }
+#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))) {
+ /* Found a reachable address */
+ break;
+ }
+ mnet = alt;
+ }
+
+ if (alt == NULL) {
+ /* Case where NO insv network exists (dormant state) */
+ /* we rotate destinations */
+ once = 0;
+ mnet = net;
+ for (;;) {
+ if (mnet == NULL) {
+ return (TAILQ_FIRST(&stcb->asoc.nets));
+ }
+ alt = TAILQ_NEXT(mnet, sctp_next);
+ if (alt == NULL) {
+ once++;
+ if (once > 1) {
+ break;
+ }
+ alt = TAILQ_FIRST(&stcb->asoc.nets);
+ if (alt == NULL) {
+ break;
+ }
+ }
+ if ((!(alt->dest_state & SCTP_ADDR_UNCONFIRMED)) &&
+ (alt != net)) {
+ /* Found an alternate address */
+ break;
+ }
+ mnet = alt;
+ }
+ }
+ if (alt == NULL) {
+ return (net);
+ }
+ 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;
+ int recovery_cnt = 0;
+
+
+ /* 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, now.tv_sec, now.tv_usec, SCTP_FR_T3_MARK_TIME);
+ sctp_log_fr(0, 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);
+ recovery_cnt++;
+#ifdef INVARIANTS
+ panic("last acked >= chk on sent-Q");
+#else
+ 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,
+ 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,
+ 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;
+ unsigned int ms_goneby;
+
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+ if (net->last_sent_time.tv_sec) {
+ ms_goneby = (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)) ||
+ (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 selecton and
+ * a route allocation.
+ */
+ if (net->ro._s_addr) {
+ 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) {
+ 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) {
+ 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, 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)) {
+ /*
+ * 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)) {
+ /*
+ * 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;
+ /*
+ * This function is ONLY called when the send/sent queues are empty.
+ */
+ if ((stcb == NULL) || (inp == NULL))
+ return;
+
+ 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, 0);
+ 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)
+{
+ uint8_t net_was_pf;
+
+ if (net->dest_state & SCTP_ADDR_PF) {
+ net_was_pf = 1;
+ } else {
+ net_was_pf = 0;
+ }
+ if (net->hb_responded == 0) {
+ if (net->ro._s_addr) {
+ /* 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) {
+ 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) &&
+ !((net_was_pf == 0) && (net->dest_state & SCTP_ADDR_PF))) {
+ /* when move to PF during threshold mangement, 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_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 100755
index 0000000000..ee1c493081
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_timer.h
@@ -0,0 +1,104 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_timer.h 359195 2020-03-21 16:12:19Z tuexen $");
+#endif
+
+#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 100755
index 0000000000..6298f9334a
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_uio.h
@@ -0,0 +1,1361 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_uio.h 362473 2020-06-21 23:12:56Z tuexen $");
+#endif
+
+#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;
+};
+
+/* 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_reserved[31]; /* 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 *i_pak,
+ 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 100755
index 0000000000..8e2ebf4623
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_userspace.c
@@ -0,0 +1,406 @@
+/*-
+ * 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, int af)
+{
+ struct ifreq ifr;
+ int fd;
+
+ 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 ((fd = socket(af, SOCK_DGRAM, 0)) < 0)
+ return (0);
+ if (ioctl(fd, SIOCGIFMTU, &ifr) < 0) {
+ close(fd);
+ return (0);
+ }
+ close(fd);
+ return ifr.ifr_mtu;
+ } else {
+ return (0);
+ }
+}
+#endif
+
+#if defined(__native_client__)
+int
+sctp_userspace_get_mtu_from_ifn(uint32_t if_index, int af)
+{
+ return 1280;
+}
+#endif
+
+#if defined(__APPLE__) || defined(__DragonFly__) || defined(__linux__) || defined(__native_client__) || defined(__NetBSD__) || defined(_WIN32) || defined(__Fuchsia__)
+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, int af)
+{
+ PIP_ADAPTER_ADDRESSES pAdapterAddrs, pAdapt;
+ DWORD AdapterAddrsSize, Err;
+ int ret;
+
+ ret = 0;
+ 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);
+ ret = -1;
+ goto cleanup;
+ }
+ }
+ if ((pAdapterAddrs = (PIP_ADAPTER_ADDRESSES) GlobalAlloc(GPTR, AdapterAddrsSize)) == NULL) {
+ SCTPDBG(SCTP_DEBUG_USR, "Memory allocation error!\n");
+ ret = -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);
+ ret = -1;
+ goto cleanup;
+ }
+ for (pAdapt = pAdapterAddrs; pAdapt; pAdapt = pAdapt->Next) {
+ if (pAdapt->IfIndex == if_index) {
+ ret = pAdapt->Mtu;
+ break;
+ }
+ }
+cleanup:
+ if (pAdapterAddrs != NULL) {
+ GlobalFree(pAdapterAddrs);
+ }
+ return (ret);
+}
+
+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 100755
index 0000000000..78286988f8
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_usrreq.c
@@ -0,0 +1,9074 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_usrreq.c 362563 2020-06-23 23:05:05Z tuexen $");
+#endif
+
+#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[];
+
+void
+#if defined(__Userspace__)
+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))
+sctp_init(struct protosw *pp SCTP_UNUSED, struct domain *dp SCTP_UNUSED)
+#else
+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) * SCTP_DEFAULT_MAXSEGMENT));
+#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__)
+#ifdef VIMAGE
+static void
+sctp_finish(void *unused __unused)
+{
+ 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
+}
+#endif
+
+void
+sctp_pathmtu_adjustment(struct sctp_tcb *stcb, uint16_t nxtsz)
+{
+ struct sctp_tmit_chunk *chk;
+ uint16_t overhead;
+
+ /* Adjust that too */
+ stcb->asoc.smallest_mtu = nxtsz;
+ /* now off to subtract IP_DF flag if needed */
+ overhead = IP_HDR_SIZE + sizeof(struct sctphdr);
+ if (sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.peer_auth_chunks)) {
+ overhead += sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id);
+ }
+ TAILQ_FOREACH(chk, &stcb->asoc.send_queue, sctp_next) {
+ if ((chk->send_size + overhead) > nxtsz) {
+ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+ }
+ }
+ TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) {
+ if ((chk->send_size + overhead) > nxtsz) {
+ /*
+ * For this guy we also mark for immediate resend
+ * since we sent to big of chunk
+ */
+ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+ if (chk->sent < SCTP_DATAGRAM_RESEND) {
+ sctp_flight_size_decrease(chk);
+ sctp_total_flight_decrease(stcb, chk);
+ chk->sent = SCTP_DATAGRAM_RESEND;
+ sctp_ucount_incr(stcb->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, 1, 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);
+ }
+ /* 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__)
+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
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct ip *outer_ip;
+#endif
+ struct ip *inner_ip;
+ struct sctphdr *sh;
+ struct icmp *icmp;
+ struct sctp_inpcb *inp;
+ struct sctp_tcb *stcb;
+ struct sctp_nets *net;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct sctp_init_chunk *ch;
+#endif
+ struct sockaddr_in src, dst;
+
+ if (sa->sa_family != AF_INET ||
+ ((struct sockaddr_in *)sa)->sin_addr.s_addr == INADDR_ANY) {
+ return;
+ }
+ 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)));
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ outer_ip = (struct ip *)((caddr_t)icmp - sizeof(struct ip));
+#endif
+ 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 {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ 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;
+ }
+#else
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+#endif
+ }
+ sctp_notify(inp, stcb, net,
+ icmp->icmp_type,
+ icmp->icmp_code,
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ ntohs(inner_ip->ip_len),
+#else
+ inner_ip->ip_len,
+#endif
+ (uint32_t)ntohs(icmp->icmp_nextmtu));
+#if defined(__Userspace__)
+ if (!(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) &&
+ (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
+
+#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
+
+
+#ifdef INET
+#if defined(_WIN32) || defined(__Userspace__)
+int
+#elif defined(__FreeBSD__)
+static void
+#else
+static int
+#endif
+sctp_abort(struct socket *so)
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ struct sctp_inpcb *inp;
+ uint32_t flags;
+
+ 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
+ }
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ 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)))) {
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 16);
+#endif
+ sctp_inpcb_free(inp, SCTP_FREE_SHOULD_USE_ABORT,
+ SCTP_CALLED_AFTER_CMPSET_OFCLOSE);
+ SOCK_LOCK(so);
+ 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__)
+ so->so_usecount--;
+#else
+ /* Now null out the reference, we are completely detached. */
+ so->so_pcb = NULL;
+#endif
+ SOCK_UNLOCK(so);
+ } else {
+ flags = inp->sctp_flags;
+ if ((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) {
+ goto sctp_must_try_again;
+ }
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+ return;
+#else
+ return (0);
+#endif
+}
+
+#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;
+ uint32_t flags;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL)
+ return;
+
+ /* Inform all the lower layer assoc that we
+ * are done.
+ */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ 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 defined(__Userspace__)
+ if (((so->so_options & SCTP_SO_LINGER) && (so->so_linger == 0)) ||
+ (so->so_rcv.sb_cc > 0)) {
+#else
+ if (((so->so_options & SO_LINGER) && (so->so_linger == 0)) ||
+ (so->so_rcv.sb_cc > 0)) {
+#endif
+#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, 14);
+#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.
+ */
+ SOCK_LOCK(so);
+ 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 null out the reference, we are completely detached. */
+ so->so_pcb = NULL;
+#endif
+ SOCK_UNLOCK(so);
+ } else {
+ flags = inp->sctp_flags;
+ if ((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) {
+ goto sctp_must_try_again;
+ }
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ return;
+}
+
+#else
+
+
+int
+sctp_detach(struct socket *so)
+{
+ struct sctp_inpcb *inp;
+ uint32_t flags;
+
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp == NULL) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ return;
+#else
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+#endif
+ }
+ 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 defined(__Userspace__)
+ if (((so->so_options & SCTP_SO_LINGER) && (so->so_linger == 0)) ||
+ (so->so_rcv.sb_cc > 0)) {
+#else
+ if (((so->so_options & SO_LINGER) && (so->so_linger == 0)) ||
+ (so->so_rcv.sb_cc > 0)) {
+#endif
+#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;
+ }
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ return;
+#else
+ return (0);
+#endif
+}
+#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;
+ } else if (addr == NULL) {
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EDESTADDRREQ);
+ error = EDESTADDRREQ;
+ sctp_m_freem(m);
+ if (control) {
+ sctp_m_freem(control);
+ control = NULL;
+ }
+ return (error);
+ }
+#ifdef INET6
+ if (addr->sa_family != AF_INET) {
+ /* must be a v4 address! */
+ SCTP_LTRACE_ERR_RET_PKT(m, inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EDESTADDRREQ);
+ sctp_m_freem(m);
+ if (control) {
+ sctp_m_freem(control);
+ control = NULL;
+ }
+ error = EDESTADDRREQ;
+ return (error);
+ }
+#endif /* INET6 */
+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)
+{
+ 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, ENOTCONN);
+ return (ENOTCONN);
+ }
+ SCTP_INP_RLOCK(inp);
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
+ if (LIST_EMPTY(&inp->sctp_asoc_list)) {
+ /* No connection */
+ SCTP_INP_RUNLOCK(inp);
+ return (0);
+ } else {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ struct sctp_association *asoc;
+ struct sctp_tcb *stcb;
+
+ 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, EINVAL);
+ return (EINVAL);
+ }
+ SCTP_TCB_LOCK(stcb);
+ asoc = &stcb->asoc;
+ if (stcb->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)) ||
+ (so->so_rcv.sb_cc > 0)) {
+#else
+ if (((so->so_options & SO_LINGER) &&
+ (so->so_linger == 0)) ||
+ (so->so_rcv.sb_cc > 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_T3, 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);
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, NULL);
+ 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);
+ }
+ /* not reached */
+ } else {
+ /* UDP model does not support this */
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EOPNOTSUPP);
+ return (EOPNOTSUPP);
+ }
+}
+
+#if defined(__FreeBSD__) || defined(_WIN32) || defined(__Userspace__)
+int
+sctp_flush(struct socket *so, int how)
+{
+ /*
+ * We will just clear out the values and let
+ * subsequent close clear out the data, if any.
+ * Note if the user did a shutdown(SHUT_RD) they
+ * will not be able to read the data, the socket
+ * will block that from happening.
+ */
+ 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 the 1 to many model this does nothing */
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) {
+ SCTP_INP_RUNLOCK(inp);
+ return (0);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ if ((how == PRU_FLUSH_RD) || (how == PRU_FLUSH_RDWR)) {
+ /* First make sure the sb will be happy, we don't
+ * use these except maybe the count
+ */
+ SCTP_INP_WLOCK(inp);
+ SCTP_INP_READ_LOCK(inp);
+ inp->sctp_flags |= SCTP_PCB_FLAGS_SOCKET_CANT_READ;
+ SCTP_INP_READ_UNLOCK(inp);
+ SCTP_INP_WUNLOCK(inp);
+ so->so_rcv.sb_cc = 0;
+ so->so_rcv.sb_mbcnt = 0;
+ so->so_rcv.sb_mb = NULL;
+ }
+ if ((how == PRU_FLUSH_WR) || (how == PRU_FLUSH_RDWR)) {
+ /* First make sure the sb will be happy, we don't
+ * use these except maybe the count
+ */
+ so->so_snd.sb_cc = 0;
+ so->so_snd.sb_mbcnt = 0;
+ so->so_snd.sb_mb = NULL;
+
+ }
+ 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);
+ } 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, SCTP_SO_LOCKED);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+#endif
+ return (0);
+ }
+ }
+ sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, NULL);
+ /* 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);
+}
+
+
+
+/*
+ * NOTE: assumes addr lock is held
+ */
+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;
+
+ 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 unspecifed
+ * 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 unspecifed
+ * 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 = 0;
+#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 */
+ 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);
+}
+
+/*
+ * NOTE: assumes addr lock is held
+ */
+static int
+sctp_count_max_addresses_vrf(struct sctp_inpcb *inp, uint32_t vrf_id)
+{
+ int cnt = 0;
+ struct sctp_vrf *vrf = NULL;
+
+ /*
+ * In both sub-set bound an bound_all cases we return 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.
+ */
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL) {
+ return (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))
+ cnt += sizeof(struct sockaddr_in6);
+ else
+ cnt += sizeof(struct sockaddr_in);
+#else
+ cnt += sizeof(struct sockaddr_in);
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ cnt += sizeof(struct sockaddr_in6);
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ cnt += 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))
+ cnt += sizeof(struct sockaddr_in6);
+ else
+ cnt += sizeof(struct sockaddr_in);
+#else
+ cnt += sizeof(struct sockaddr_in);
+#endif
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ cnt += sizeof(struct sockaddr_in6);
+ break;
+#endif
+#if defined(__Userspace__)
+ case AF_CONN:
+ cnt += sizeof(struct sockaddr_conn);
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ }
+ return (cnt);
+}
+
+static int
+sctp_count_max_addresses(struct sctp_inpcb *inp)
+{
+ int cnt = 0;
+#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.
+ */
+ /* count addresses for all VRFs on the endpoint */
+ for (id = 0; id < inp->num_vrfs; id++) {
+ cnt += sctp_count_max_addresses_vrf(inp, inp->m_vrf_ids[id]);
+ }
+#else
+ /* count addresses for the endpoint's default VRF */
+ cnt = sctp_count_max_addresses_vrf(inp, inp->def_vrf_id);
+#endif
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (cnt);
+}
+
+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) ==
+ 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(inp, sa, &error, 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;
+ }
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) {
+ stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED;
+ /* Set the connected flag so we can queue data */
+ soisconnecting(so);
+ }
+ 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_PLUGGABLE_SS:
+ {
+ 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_SS_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;
+ int ovh;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (stcb) {
+ av->assoc_value = sctp_get_frag_point(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))) {
+ SCTP_INP_RLOCK(inp);
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ ovh = SCTP_MED_OVERHEAD;
+ } else {
+ ovh = SCTP_MED_V4_OVERHEAD;
+ }
+ if (inp->sctp_frag_point >= SCTP_DEFAULT_MAXSEGMENT)
+ av->assoc_value = 0;
+ else
+ av->assoc_value = inp->sctp_frag_point - ovh;
+ 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 = sctp_count_max_addresses(inp);
+ SCTP_INP_RUNLOCK(inp);
+ *optsize = sizeof(uint32_t);
+ break;
+ }
+ case SCTP_GET_REMOTE_ADDR_SIZE:
+ {
+ uint32_t *value;
+ size_t size;
+ struct sctp_nets *net;
+
+ 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) {
+ 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 {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOTCONN);
+ error = ENOTCONN;
+ }
+ 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) {
+ 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 {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOENT);
+ error = ENOENT;
+ }
+ 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);
+
+ limit = *optsize - offsetof(struct sctp_getaddresses, addr);
+ actual = sctp_fill_up_addresses(inp, stcb, limit, &saddr->addr[0].sa);
+ if (stcb) {
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ *optsize = offsetof(struct sctp_getaddresses, addr) + actual;
+ 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, &stcb->asoc);
+ 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 = 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;
+ uint32_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 ((size_t)(*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:
+ {
+ int onoff;
+
+ if (*optsize < sizeof(int)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ } else {
+ SCTP_INP_RLOCK(inp);
+ onoff = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVRCVINFO);
+ SCTP_INP_RUNLOCK(inp);
+ }
+ if (error == 0) {
+ /* return the option value */
+ *(int *)optval = onoff;
+ *optsize = sizeof(int);
+ }
+ break;
+ }
+ case SCTP_RECVNXTINFO:
+ {
+ int onoff;
+
+ if (*optsize < sizeof(int)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ error = EINVAL;
+ } else {
+ SCTP_INP_RLOCK(inp);
+ onoff = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_RECVNXTINFO);
+ SCTP_INP_RUNLOCK(inp);
+ }
+ if (error == 0) {
+ /* return the option value */
+ *(int *)optval = onoff;
+ *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_PLUGGABLE_SS:
+ {
+ 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_ROUND_ROBIN) &&
+ (av->assoc_value != SCTP_SS_ROUND_ROBIN_PACKET) &&
+ (av->assoc_value != SCTP_SS_PRIORITY) &&
+ (av->assoc_value != SCTP_SS_FAIR_BANDWITH) &&
+ (av->assoc_value != SCTP_SS_FIRST_COME)) {
+ 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) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ stcb->asoc.ss_functions.sctp_ss_clear(stcb, &stcb->asoc, 1, 1);
+ 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, 1);
+ SCTP_TCB_SEND_UNLOCK(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) &&
+ ((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);
+ SCTP_TCB_SEND_LOCK(stcb);
+ stcb->asoc.ss_functions.sctp_ss_clear(stcb, &stcb->asoc, 1, 1);
+ 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, 1);
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ SCTP_TCB_UNLOCK(stcb);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ }
+ }
+ break;
+ }
+ case SCTP_SS_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;
+ int ovh;
+
+ SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
+ SCTP_FIND_STCB(inp, stcb, av->assoc_id);
+
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ ovh = SCTP_MED_OVERHEAD;
+ } else {
+ ovh = SCTP_MED_V4_OVERHEAD;
+ }
+ if (stcb) {
+ if (av->assoc_value) {
+ stcb->asoc.sctp_frag_point = (av->assoc_value + ovh);
+ } else {
+ stcb->asoc.sctp_frag_point = SCTP_DEFAULT_MAXSEGMENT;
+ }
+ 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);
+ /* FIXME MT: I think this is not in tune with the API ID */
+ if (av->assoc_value) {
+ inp->sctp_frag_point = (av->assoc_value + ovh);
+ } else {
+ inp->sctp_frag_point = SCTP_DEFAULT_MAXSEGMENT;
+ }
+ 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);
+ }
+ SCTP_INP_WUNLOCK(inp);
+
+ SCTP_INP_RLOCK(inp);
+ 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) ||
+ (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
+ stcb = LIST_FIRST(&inp->sctp_asoc_list);
+ if (stcb) {
+ 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_RUNLOCK(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 < 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) &&
+ !(net->dest_state & SCTP_ADDR_NOHB)) {
+ 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;
+ 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);
+ }
+ }
+ 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) {
+ 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)) {
+ net->dest_state |= SCTP_ADDR_NOHB;
+ if (!(net->dest_state & SCTP_ADDR_UNCONFIRMED)) {
+ 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;
+ 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);
+ }
+ }
+ 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) {
+ 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) {
+ /* boundary check the cookie life */
+ if (sasoc->sasoc_cookie_life < 1000)
+ sasoc->sasoc_cookie_life = 1000;
+ if (sasoc->sasoc_cookie_life > SCTP_MAX_COOKIE_LIFE) {
+ sasoc->sasoc_cookie_life = SCTP_MAX_COOKIE_LIFE;
+ }
+ }
+ if (stcb) {
+ if (sasoc->sasoc_asocmaxrxt)
+ stcb->asoc.max_send_times = sasoc->sasoc_asocmaxrxt;
+ if (sasoc->sasoc_cookie_life) {
+ 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)
+ inp->sctp_ep.max_send_times = sasoc->sasoc_asocmaxrxt;
+ if (sasoc->sasoc_cookie_life) {
+ 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)) {
+ /* 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)) &&
+ (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;
+ }
+ 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__)
+ 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) ==
+ 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(inp, addr, &error, 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;
+ }
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) {
+ stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED;
+ /* Set the connected flag so we can queue data */
+ soisconnecting(so);
+ }
+ 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) == 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(inp, addr, &error, 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;
+ }
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) {
+ stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED;
+ /* Set the connected flag so we can queue data */
+ soisconnecting(so);
+ }
+ 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_RLOCK(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 defined(__FreeBSD__) || defined(__Userspace__)
+ SOCK_LOCK(so);
+ error = solisten_proto_check(so);
+ SOCK_UNLOCK(so);
+ if (error) {
+ SCTP_INP_RUNLOCK(inp);
+ return (error);
+ }
+#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)) {
+ SCTP_INP_RUNLOCK(inp);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EADDRINUSE);
+ return (EADDRINUSE);
+ }
+ }
+
+ 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_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EADDRINUSE);
+ return (EADDRINUSE);
+ }
+ SCTP_INP_RUNLOCK(inp);
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) {
+ /* We must do a bind. */
+ if ((error = sctp_inpcb_bind(so, NULL, NULL, p))) {
+ /* bind error, probably perm */
+ return (error);
+ }
+ }
+ SCTP_INP_WLOCK(inp);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) == 0) {
+ SOCK_LOCK(so);
+ solisten_proto(so, backlog);
+ SOCK_UNLOCK(so);
+ }
+#elif defined(_WIN32) || defined(__Userspace__)
+ SOCK_LOCK(so);
+ 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);
+#endif
+#if defined(__FreeBSD__) || defined(_WIN32) || defined(__Userspace__)
+ if (backlog > 0) {
+ inp->sctp_flags |= SCTP_PCB_FLAGS_ACCEPTING;
+ } else {
+ inp->sctp_flags &= ~SCTP_PCB_FLAGS_ACCEPTING;
+ }
+#else
+ inp->sctp_flags |= SCTP_PCB_FLAGS_ACCEPTING;
+#endif
+ SCTP_INP_WUNLOCK(inp);
+ 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__)
+struct pr_usrreqs sctp_usrreqs = {
+#if defined(__FreeBSD__)
+ .pru_abort = sctp_abort,
+ .pru_accept = sctp_accept,
+ .pru_attach = sctp_attach,
+ .pru_bind = sctp_bind,
+ .pru_connect = sctp_connect,
+ .pru_control = in_control,
+ .pru_close = sctp_close,
+ .pru_detach = sctp_close,
+ .pru_sopoll = sopoll_generic,
+ .pru_flush = sctp_flush,
+ .pru_disconnect = sctp_disconnect,
+ .pru_listen = sctp_listen,
+ .pru_peeraddr = sctp_peeraddr,
+ .pru_send = sctp_sendm,
+ .pru_shutdown = sctp_shutdown,
+ .pru_sockaddr = sctp_ingetaddr,
+ .pru_sosend = sctp_sosend,
+ .pru_soreceive = sctp_soreceive
+#elif 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
+};
+#elif !defined(__Userspace__)
+int
+sctp_usrreq(so, req, m, nam, control)
+ struct socket *so;
+ int req;
+ struct mbuf *m, *nam, *control;
+{
+ struct proc *p = curproc;
+ int error;
+ int family;
+ struct sctp_inpcb *inp = (struct sctp_inpcb *)so->so_pcb;
+
+ error = 0;
+ family = so->so_proto->pr_domain->dom_family;
+ if (req == PRU_CONTROL) {
+ switch (family) {
+ case PF_INET:
+ error = in_control(so, (long)m, (caddr_t)nam,
+ (struct ifnet *)control);
+ break;
+#ifdef INET6
+ case PF_INET6:
+ error = in6_control(so, (long)m, (caddr_t)nam,
+ (struct ifnet *)control, p);
+ break;
+#endif
+ default:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EAFNOSUPPORT);
+ error = EAFNOSUPPORT;
+ }
+ return (error);
+ }
+ switch (req) {
+ case PRU_ATTACH:
+ error = sctp_attach(so, family, p);
+ break;
+ case PRU_DETACH:
+ error = sctp_detach(so);
+ break;
+ case PRU_BIND:
+ if (nam == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ error = sctp_bind(so, nam, p);
+ break;
+ case PRU_LISTEN:
+ error = sctp_listen(so, p);
+ break;
+ case PRU_CONNECT:
+ if (nam == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ error = sctp_connect(so, nam, p);
+ break;
+ case PRU_DISCONNECT:
+ error = sctp_disconnect(so);
+ break;
+ case PRU_ACCEPT:
+ if (nam == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ error = sctp_accept(so, nam);
+ break;
+ case PRU_SHUTDOWN:
+ error = sctp_shutdown(so);
+ break;
+
+ case PRU_RCVD:
+ /*
+ * For Open and Net BSD, this is real ugly. The mbuf *nam
+ * that is passed (by soreceive()) is the int flags c ast as
+ * a (mbuf *) yuck!
+ */
+ break;
+
+ case PRU_SEND:
+ /* Flags are ignored */
+ {
+ struct sockaddr *addr;
+
+ if (nam == NULL)
+ addr = NULL;
+ else
+ addr = mtod(nam, struct sockaddr *);
+
+ error = sctp_sendm(so, 0, m, addr, control, p);
+ }
+ break;
+ case PRU_ABORT:
+ error = sctp_abort(so);
+ break;
+
+ case PRU_SENSE:
+ error = 0;
+ break;
+ case PRU_RCVOOB:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EAFNOSUPPORT);
+ error = EAFNOSUPPORT;
+ break;
+ case PRU_SENDOOB:
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EAFNOSUPPORT);
+ error = EAFNOSUPPORT;
+ break;
+ case PRU_PEERADDR:
+ error = sctp_peeraddr(so, nam);
+ break;
+ case PRU_SOCKADDR:
+ error = sctp_ingetaddr(so, nam);
+ break;
+ case PRU_SLOWTIMO:
+ error = 0;
+ break;
+ default:
+ break;
+ }
+ return (error);
+}
+
+#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))
+{
+ 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 100755
index 0000000000..2264d5693d
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_var.h
@@ -0,0 +1,450 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctp_var.h 360292 2020-04-25 09:06:11Z melifaro $");
+#endif
+
+#ifndef _NETINET_SCTP_VAR_H_
+#define _NETINET_SCTP_VAR_H_
+
+#include <netinet/sctp_uio.h>
+
+#if defined(_KERNEL) || defined(__Userspace__)
+
+#if !defined(__Userspace__)
+extern struct pr_usrreqs sctp_usrreqs;
+#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) > (sb)->sb_cc) ? (sctp_maxspace(sb) - (sb)->sb_cc) : 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)) { \
+ (void)SCTP_OS_TIMER_STOP(&(__net)->rxt_timer.timer); \
+ 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_SAVE_ATOMIC_DECREMENT(&(sb)->sb_cc, 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) { \
+ atomic_add_int(&(sb)->sb_cc,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)) { \
+ (void)SCTP_OS_TIMER_STOP(&(__net)->rxt_timer.timer); \
+ 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_SAVE_ATOMIC_DECREMENT(&(sb)->sb_cc, 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) { \
+ atomic_add_int(&(sb)->sb_cc, 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
+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);
+#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 *, uint16_t);
+#else
+#if defined(__Userspace__)
+void sctp_pathmtu_adjustment(struct sctp_tcb *, uint16_t);
+#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
+void sctp_drain(void);
+#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
+void sctp_init(void);
+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 100755
index 0000000000..e40e04e591
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctputil.c
@@ -0,0 +1,8656 @@
+/*-
+ * 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.
+ */
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: head/sys/netinet/sctputil.c 362462 2020-06-21 09:56:09Z tuexen $");
+#endif
+
+#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 = sb->sb_cc;
+ 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(&(inp->sctp_socket->so_rcv.sb_mtx));
+ sctp_clog.x.lock.sockrcvbuf_lock = mtx_owned(&(inp->sctp_socket->so_rcv.sb_mtx));
+ sctp_clog.x.lock.socksndbuf_lock = mtx_owned(&(inp->sctp_socket->so_snd.sb_mtx));
+ } 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 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->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;
+ asoc->asconf_seq_out = asoc->str_reset_seq_out = asoc->init_seq_number = asoc->sending_seq =
+ sctp_select_initial_TSN(&inp->sctp_ep);
+ asoc->asconf_seq_out_acked = asoc->asconf_seq_out - 1;
+ /* we are optimisitic here */
+ asoc->peer_supports_nat = 0;
+ asoc->sent_queue_retran_cnt = 0;
+
+ /* for CMT */
+ asoc->last_net_cmt_send_started = NULL;
+
+ /* This will need to be adjusted */
+ asoc->last_acked_seq = asoc->init_seq_number - 1;
+ asoc->advanced_peer_ack_point = asoc->last_acked_seq;
+ asoc->asconf_seq_in = asoc->last_acked_seq;
+
+ /* here we are different, we hold the next one we expect */
+ asoc->str_reset_seq_in = asoc->last_acked_seq + 1;
+
+ 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 = inp->sctp_frag_point;
+ 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);
+ }
+ 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.
+ */
+ asoc->strmout[i].next_mid_ordered = 0;
+ asoc->strmout[i].next_mid_unordered = 0;
+ TAILQ_INIT(&asoc->strmout[i].outqueue);
+ 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].sid = i;
+ asoc->strmout[i].last_msg_incomplete = 0;
+ asoc->strmout[i].state = SCTP_STREAM_OPENING;
+ asoc->ss_functions.sctp_ss_init_stream(stcb, &asoc->strmout[i], NULL);
+ }
+ asoc->ss_functions.sctp_ss_init(stcb, asoc, 0);
+
+ /* 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) {
+ 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_add_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_add_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));
+
+ /* run function on this one */
+ (*it->function_assoc)(it->inp, it->stcb, it->pointer, it->val);
+
+ /*
+ * 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_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 did_output;
+ int type;
+ int i, secret;
+
+ 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);
+#endif
+ did_output = 1;
+
+#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));
+ if (inp) {
+ SCTP_INP_INCR_REF(inp);
+ }
+ tmr->stopped_from = 0xa001;
+ if (stcb) {
+ atomic_add_int(&stcb->asoc.refcnt, 1);
+ if (stcb->asoc.state == 0) {
+ atomic_add_int(&stcb->asoc.refcnt, -1);
+ if (inp) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d handler exiting due to CLOSED association.\n",
+ type);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ CURVNET_RESTORE();
+#endif
+ return;
+ }
+ }
+ tmr->stopped_from = 0xa002;
+ SCTPDBG(SCTP_DEBUG_TIMER2, "Timer type %d goes off.\n", type);
+ if (!SCTP_OS_TIMER_ACTIVE(&tmr->timer)) {
+ if (inp) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ if (stcb) {
+ atomic_add_int(&stcb->asoc.refcnt, -1);
+ }
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d handler exiting due to not being active.\n",
+ type);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ CURVNET_RESTORE();
+#endif
+ return;
+ }
+
+ tmr->stopped_from = 0xa003;
+ if (stcb) {
+ SCTP_TCB_LOCK(stcb);
+ atomic_add_int(&stcb->asoc.refcnt, -1);
+ if ((type != SCTP_TIMER_TYPE_ASOCKILL) &&
+ ((stcb->asoc.state == 0) ||
+ (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED))) {
+ SCTP_TCB_UNLOCK(stcb);
+ if (inp) {
+ SCTP_INP_DECR_REF(inp);
+ }
+ SCTPDBG(SCTP_DEBUG_TIMER2,
+ "Timer type %d handler exiting due to CLOSED association.\n",
+ type);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ CURVNET_RESTORE();
+#endif
+ return;
+ }
+ } else if (inp != NULL) {
+ SCTP_INP_WLOCK(inp);
+ } else {
+ SCTP_WQ_ADDR_LOCK();
+ }
+
+ /* Record in stopped_from which timeout occurred. */
+ tmr->stopped_from = type;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ /* mark as being serviced now */
+ if (SCTP_OS_TIMER_PENDING(&tmr->timer)) {
+ /*
+ * Callout has been rescheduled.
+ */
+ goto get_out;
+ }
+ if (!SCTP_OS_TIMER_ACTIVE(&tmr->timer)) {
+ /*
+ * Not active, so no action.
+ */
+ goto get_out;
+ }
+ SCTP_OS_TIMER_DEACTIVATE(&tmr->timer);
+
+#if defined(__Userspace__)
+ if ((stcb != NULL) &&
+ !(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) &&
+ (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);
+ 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;
+ }
+ /* We do output but not here */
+ did_output = 0;
+ 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);
+ 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);
+ 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)) {
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net);
+ sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_HB_TMR, SCTP_SO_NOT_LOCKED);
+ }
+ 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);
+ 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 = 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 = 0;
+ 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 = 0;
+ 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);
+ 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);
+ 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, 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 = 0;
+ 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);
+ 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_no_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_INP_DECR_REF(inp);
+ 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_no_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();
+ 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);
+ break;
+ default:
+#ifdef INVARIANTS
+ panic("Unknown timer type %d", type);
+#else
+ goto get_out;
+#endif
+ }
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_audit_log(0xF1, (uint8_t) type);
+ if (inp)
+ sctp_auditing(5, inp, stcb, net);
+#endif
+ if ((did_output) && stcb) {
+ /*
+ * 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);
+ }
+get_out:
+ if (stcb) {
+ 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
+ if (inp) {
+ SCTP_INP_DECR_REF(inp);
+ }
+
+out_no_decr:
+ 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;
+ to_ticks = 0;
+ 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,
+ * ususually 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)) {
+ 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 (jitter >= (to_ticks >> 1)) {
+ to_ticks = to_ticks + (jitter - (to_ticks >> 1));
+ } else {
+ to_ticks = to_ticks - jitter;
+ }
+ if (!(net->dest_state & SCTP_ADDR_UNCONFIRMED) &&
+ !(net->dest_state & SCTP_ADDR_PF)) {
+ to_ticks += net->heart_beat_delay;
+ }
+ /*
+ * 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 ususually 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, ususually
+ * 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_chage
+ * 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);
+ } 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_chage
+ * 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);
+ tmr->ep = NULL;
+ tmr->tcb = NULL;
+ tmr->net = NULL;
+ } 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)
+{
+ uint32_t tlen = 0;
+ struct mbuf *at;
+
+ at = m;
+ while (at) {
+ tlen += SCTP_BUF_LEN(at);
+ at = SCTP_BUF_NEXT(at);
+ }
+ return (tlen);
+}
+
+void
+sctp_mtu_size_reset(struct sctp_inpcb *inp,
+ struct sctp_association *asoc, uint32_t mtu)
+{
+ /*
+ * Reset the P-MTU size on this association, this involves changing
+ * the asoc MTU, going through ANY chunk+overhead larger than mtu to
+ * allow the DF flag to be cleared.
+ */
+ struct sctp_tmit_chunk *chk;
+ unsigned int eff_mtu, ovh;
+
+ asoc->smallest_mtu = mtu;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ ovh = SCTP_MIN_OVERHEAD;
+ } else {
+ ovh = SCTP_MIN_V4_OVERHEAD;
+ }
+ eff_mtu = mtu - ovh;
+ TAILQ_FOREACH(chk, &asoc->send_queue, sctp_next) {
+ if (chk->send_size > eff_mtu) {
+ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+ }
+ }
+ TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) {
+ if (chk->send_size > eff_mtu) {
+ chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+ }
+ }
+}
+
+
+/*
+ * 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 measurment */
+ 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, uint8_t from_peer, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_assoc_change *sac;
+ struct sctp_queued_to_read *control;
+ unsigned int notif_len;
+ uint16_t abort_len;
+ unsigned int i;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+
+ if (stcb == NULL) {
+ return;
+ }
+ if (sctp_stcb_is_feature_on(stcb->sctp_ep, 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;
+ /* XXX verify these stream counts */
+ 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(stcb->sctp_ep, stcb,
+ control,
+ &stcb->sctp_socket->so_rcv, 1, SCTP_READ_LOCK_NOT_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 (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (stcb->sctp_ep->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 ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
+ 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(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) {
+ SCTP_SOCKET_UNLOCK(so, 1);
+ return;
+ }
+ }
+#endif
+ if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
+ (stcb->sctp_ep->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;
+
+ if ((stcb == NULL) ||
+ 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_NOT_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;
+
+ if ((stcb == NULL) ||
+ (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_NOT_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;
+
+ if ((stcb == NULL) ||
+ (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_NOT_HELD, so_locked);
+}
+
+
+
+static void
+sctp_notify_adaptation_layer(struct sctp_tcb *stcb)
+{
+ struct mbuf *m_notify;
+ struct sctp_adaptation_event *sai;
+ struct sctp_queued_to_read *control;
+
+ if ((stcb == NULL) ||
+ 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_NOT_HELD, SCTP_SO_NOT_LOCKED);
+}
+
+/* This always must be called with the read-queue LOCKED in the INP */
+static void
+sctp_notify_partial_delivery_indication(struct sctp_tcb *stcb, uint32_t error,
+ uint32_t val, int so_locked)
+{
+ struct mbuf *m_notify;
+ struct sctp_pdapi_event *pdapi;
+ struct sctp_queued_to_read *control;
+ struct sockbuf *sb;
+
+ if ((stcb == NULL) ||
+ sctp_stcb_is_feature_off(stcb->sctp_ep, stcb, SCTP_PCB_FLAGS_PDAPIEVNT)) {
+ /* event not enabled */
+ return;
+ }
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_CANT_READ) {
+ 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 = (val >> 16);
+ pdapi->pdapi_seq = (val & 0x0000ffff);
+ 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;
+ if (stcb->asoc.control_pdapi)
+ TAILQ_INSERT_AFTER(&stcb->sctp_ep->read_queue, stcb->asoc.control_pdapi, control, next);
+ else {
+ /* we really should not see this case */
+ TAILQ_INSERT_TAIL(&stcb->sctp_ep->read_queue, 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)
+{
+ struct mbuf *m_notify;
+ struct sctp_shutdown_event *sse;
+ struct sctp_queued_to_read *control;
+
+ /*
+ * 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_NOT_HELD, SCTP_SO_NOT_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;
+
+ if ((stcb == NULL) ||
+ 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_NOT_HELD, so_locked);
+}
+
+
+void
+sctp_notify_stream_reset_add(struct sctp_tcb *stcb, uint16_t numberin, uint16_t numberout, int flag)
+{
+ struct mbuf *m_notify;
+ struct sctp_queued_to_read *control;
+ struct sctp_stream_change_event *stradd;
+
+ if ((stcb == NULL) ||
+ (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 = numberin;
+ stradd->strchange_outstrms = numberout;
+ 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_NOT_HELD, SCTP_SO_NOT_LOCKED);
+}
+
+void
+sctp_notify_stream_reset_tsn(struct sctp_tcb *stcb, uint32_t sending_tsn, uint32_t recv_tsn, int flag)
+{
+ struct mbuf *m_notify;
+ struct sctp_queued_to_read *control;
+ struct sctp_assoc_reset_event *strasoc;
+
+ if ((stcb == NULL) ||
+ (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 = sending_tsn;
+ strasoc->assocreset_remote_tsn = recv_tsn;
+ 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_NOT_HELD, SCTP_SO_NOT_LOCKED);
+}
+
+
+
+static void
+sctp_notify_stream_reset(struct sctp_tcb *stcb,
+ int number_entries, uint16_t * list, int flag)
+{
+ struct mbuf *m_notify;
+ struct sctp_queued_to_read *control;
+ struct sctp_stream_reset_event *strreset;
+ int len;
+
+ if ((stcb == NULL) ||
+ (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_NOT_HELD, SCTP_SO_NOT_LOCKED);
+}
+
+
+static void
+sctp_notify_remote_error(struct sctp_tcb *stcb, uint16_t error, struct sctp_error_chunk *chunk)
+{
+ struct mbuf *m_notify;
+ struct sctp_remote_error *sre;
+ struct sctp_queued_to_read *control;
+ unsigned int notif_len;
+ uint16_t chunk_len;
+
+ if ((stcb == NULL) ||
+ 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_NOT_HELD, SCTP_SO_NOT_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)
+{
+ if ((stcb == NULL) ||
+ (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)) {
+ /* If the socket is gone we are out of here */
+ return;
+ }
+#if (defined(__FreeBSD__) || defined(_WIN32)) && !defined(__Userspace__)
+ if (stcb->sctp_socket->so_rcv.sb_state & SBS_CANTRCVMORE) {
+#else
+ if (stcb->sctp_socket->so_state & SS_CANTRCVMORE) {
+#endif
+ 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 ((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;
+ }
+ }
+ switch (notification) {
+ case SCTP_NOTIFY_ASSOC_UP:
+ if (stcb->asoc.assoc_up_sent == 0) {
+ sctp_notify_assoc_change(SCTP_COMM_UP, stcb, error, NULL, 0, so_locked);
+ stcb->asoc.assoc_up_sent = 1;
+ }
+ if (stcb->asoc.adaptation_needed && (stcb->asoc.adaptation_sent == 0)) {
+ sctp_notify_adaptation_layer(stcb);
+ }
+ if (stcb->asoc.auth_supported == 0) {
+ sctp_ulp_notify(SCTP_NOTIFY_NO_PEER_AUTH, stcb, 0,
+ NULL, so_locked);
+ }
+ break;
+ case SCTP_NOTIFY_ASSOC_DOWN:
+ sctp_notify_assoc_change(SCTP_SHUTDOWN_COMP, stcb, error, NULL, 0, so_locked);
+#if defined(__Userspace__)
+ if (stcb->sctp_ep->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);
+ stcb->sctp_ep->recv_callback(stcb->sctp_socket, addr, NULL, 0, rcv, 0, stcb->sctp_ep->ulp_info);
+ SCTP_TCB_LOCK(stcb);
+ atomic_subtract_int(&stcb->asoc.refcnt, 1);
+ }
+ }
+#endif
+ break;
+ case SCTP_NOTIFY_INTERFACE_DOWN:
+ {
+ struct sctp_nets *net;
+
+ net = (struct sctp_nets *)data;
+ sctp_notify_peer_addr_change(stcb, SCTP_ADDR_UNREACHABLE,
+ (struct sockaddr *)&net->ro._l_addr, error, so_locked);
+ break;
+ }
+ case SCTP_NOTIFY_INTERFACE_UP:
+ {
+ struct sctp_nets *net;
+
+ net = (struct sctp_nets *)data;
+ sctp_notify_peer_addr_change(stcb, SCTP_ADDR_AVAILABLE,
+ (struct sockaddr *)&net->ro._l_addr, error, so_locked);
+ break;
+ }
+ case SCTP_NOTIFY_INTERFACE_CONFIRMED:
+ {
+ struct sctp_nets *net;
+
+ net = (struct sctp_nets *)data;
+ sctp_notify_peer_addr_change(stcb, SCTP_ADDR_CONFIRMED,
+ (struct sockaddr *)&net->ro._l_addr, 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:
+ {
+ uint32_t val;
+ val = *((uint32_t *)data);
+
+ sctp_notify_partial_delivery_indication(stcb, error, val, 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, 0, so_locked);
+ } else {
+ sctp_notify_assoc_change(SCTP_COMM_LOST, stcb, error, data, 0, 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, 1, so_locked);
+ } else {
+ sctp_notify_assoc_change(SCTP_COMM_LOST, stcb, error, data, 1, so_locked);
+ }
+ break;
+ case SCTP_NOTIFY_ASSOC_RESTART:
+ sctp_notify_assoc_change(SCTP_RESTART, stcb, error, NULL, 0, so_locked);
+ if (stcb->asoc.auth_supported == 0) {
+ sctp_ulp_notify(SCTP_NOTIFY_NO_PEER_AUTH, stcb, 0,
+ NULL, so_locked);
+ }
+ break;
+ case SCTP_NOTIFY_STR_RESET_SEND:
+ sctp_notify_stream_reset(stcb, error, ((uint16_t *) data), SCTP_STREAM_RESET_OUTGOING_SSN);
+ break;
+ case SCTP_NOTIFY_STR_RESET_RECV:
+ sctp_notify_stream_reset(stcb, error, ((uint16_t *) data), SCTP_STREAM_RESET_INCOMING);
+ 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));
+ 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));
+ 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));
+ 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));
+ 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);
+ break;
+ case SCTP_NOTIFY_AUTH_NEW_KEY:
+ sctp_notify_authentication(stcb, SCTP_AUTH_NEW_KEY, error,
+ (uint16_t)(uintptr_t)data,
+ so_locked);
+ break;
+ case SCTP_NOTIFY_AUTH_FREE_KEY:
+ sctp_notify_authentication(stcb, SCTP_AUTH_FREE_KEY, error,
+ (uint16_t)(uintptr_t)data,
+ so_locked);
+ break;
+ case SCTP_NOTIFY_NO_PEER_AUTH:
+ sctp_notify_authentication(stcb, SCTP_AUTH_NO_AUTH, error,
+ (uint16_t)(uintptr_t)data,
+ 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);
+ break;
+ default:
+ SCTPDBG(SCTP_DEBUG_UTIL1, "%s: unknown notification %xh (%u)\n",
+ __func__, notification, notification);
+ break;
+ } /* end switch */
+}
+
+void
+sctp_report_all_outbound(struct sctp_tcb *stcb, uint16_t error, int holds_lock, 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 */
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_LOCK(stcb);
+ }
+ /* 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, 1);
+ 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*/
+ }
+ }
+
+ if (holds_lock == 0) {
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+}
+
+void
+sctp_abort_notification(struct sctp_tcb *stcb, uint8_t from_peer, 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
+ 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))) {
+ stcb->sctp_ep->sctp_flags |= 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;
+ }
+ /* Tell them we lost the asoc */
+ sctp_report_all_outbound(stcb, error, 0, so_locked);
+ if (from_peer) {
+ sctp_ulp_notify(SCTP_NOTIFY_ASSOC_REM_ABORTED, 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)
+{
+ uint32_t vtag;
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+
+ vtag = 0;
+ if (stcb != NULL) {
+ vtag = stcb->asoc.peer_vtag;
+ vrf_id = stcb->asoc.vrf_id;
+ }
+ 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, 0, 0, NULL, SCTP_SO_NOT_LOCKED);
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_WAS_ABORTED);
+ /* 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,
+ int so_locked)
+{
+#if defined(__APPLE__) && !defined(__Userspace__)
+ struct socket *so;
+#endif
+
+#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;
+ } else {
+ SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_WAS_ABORTED);
+ }
+ /* 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, 0, 0, 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 * vtagfill)
+{
+ 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) {
+ /* need to update the Vtag */
+ init_chk = (struct sctp_init_chunk *)sctp_m_getptr(m,
+ offset, sizeof(*init_chk), (uint8_t *) & chunk_buf);
+ if (init_chk != NULL) {
+ *vtagfill = 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__)
+ error = sblock(&old_so->so_rcv, waitflags);
+ if (error) {
+ /* Gak, can't get sblock, 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 sb-lock on the old socket */
+#if defined(__APPLE__) && !defined(__Userspace__)
+ sbunlock(&old_so->so_rcv, 1);
+#endif
+
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ sbunlock(&old_so->so_rcv);
+#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)) {
+#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 == 0)
+ 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 == 0)
+ SCTP_INP_READ_UNLOCK(inp);
+ return;
+ }
+ if (!(control->spec_flags & M_NOTIFICATION)) {
+ 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) {
+ 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 (inp_read_lock_held == 0)
+ SCTP_INP_READ_UNLOCK(inp);
+#if defined(__Userspace__)
+ sctp_invoke_recv_callback(inp, stcb, control, inp_read_lock_held);
+#endif
+ if (inp && inp->sctp_socket) {
+ sctp_wakeup_the_read_socket(inp, stcb, so_locked);
+ }
+}
+
+/*************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);
+}
+
+#ifdef SCTP_MBCNT_LOGGING
+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;
+ }
+ asoc->chunks_on_out_queue -= chk_cnt;
+ 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);
+ }
+ if (asoc->total_output_queue_size >= tp1->book_size) {
+ atomic_add_int(&asoc->total_output_queue_size, -tp1->book_size);
+ } else {
+ asoc->total_output_queue_size = 0;
+ }
+
+ if (stcb->sctp_socket && (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) ||
+ ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE)))) {
+ if (stcb->sctp_socket->so_snd.sb_cc >= tp1->book_size) {
+ stcb->sctp_socket->so_snd.sb_cc -= tp1->book_size;
+ } else {
+ stcb->sctp_socket->so_snd.sb_cc = 0;
+
+ }
+ }
+}
+
+#endif
+
+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
+ sid = tp1->rec.data.sid;
+ mid = tp1->rec.data.mid;
+ if (sent || !(tp1->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG)) {
+ 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.
+ */
+ SCTP_TCB_SEND_LOCK(stcb);
+ 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;
+ }
+ }
+ SCTP_TCB_SEND_UNLOCK(stcb);
+ }
+ 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);
+ }
+ return (laddr->ifa);
+}
+
+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();
+
+ 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_add_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, so->so_rcv.sb_cc, uio->uio_resid);
+#else
+ sctp_misc_ints(SCTP_SORECV_ENTER,
+ rwnd_req, in_eeor_mode, so->so_rcv.sb_cc, uio_resid(uio));
+#endif
+#else
+ sctp_misc_ints(SCTP_SORECV_ENTER,
+ rwnd_req, in_eeor_mode, so->so_rcv.sb_cc, (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, so->so_rcv.sb_cc, uio->uio_resid);
+#else
+ sctp_misc_ints(SCTP_SORECV_ENTERPL,
+ rwnd_req, block_allowed, so->so_rcv.sb_cc, uio_resid(uio));
+#endif
+#else
+ sctp_misc_ints(SCTP_SORECV_ENTERPL,
+ rwnd_req, block_allowed, so->so_rcv.sb_cc, (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 = sblock(&so->so_rcv, (block_allowed ? SBL_WAIT : 0));
+#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) && (so->so_rcv.sb_cc == 0)) {
+#else
+ if ((so->so_state & SS_CANTRCVMORE) && (so->so_rcv.sb_cc == 0)) {
+#endif
+ if (so->so_error) {
+ error = so->so_error;
+ if ((in_flags & MSG_PEEK) == 0)
+ so->so_error = 0;
+ goto out;
+ } else {
+ if (so->so_rcv.sb_cc == 0) {
+ /* indicate EOF */
+ error = 0;
+ goto out;
+ }
+ }
+ }
+ if (so->so_rcv.sb_cc <= held_length) {
+ if (so->so_error) {
+ error = so->so_error;
+ if ((in_flags & MSG_PEEK) == 0) {
+ so->so_error = 0;
+ }
+ goto out;
+ }
+ if ((so->so_rcv.sb_cc == 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) {
+ error = sbwait(&so->so_rcv);
+ 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) && (so->so_rcv.sb_cc != 0)) {
+#ifdef INVARIANTS
+ panic("Huh, its non zero and nothing on control?");
+#endif
+ so->so_rcv.sb_cc = 0;
+ }
+ 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 hiddend 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 notificaiton 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 = so->so_rcv.sb_cc;
+ control->held_length = so->so_rcv.sb_cc;
+ 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, 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);
+ }
+ atomic_subtract_int(&so->so_rcv.sb_cc, cp_len);
+ if ((control->do_not_ref_stcb == 0) &&
+ stcb) {
+ atomic_subtract_int(&stcb->asoc.sb_cc, 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, 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 .. invarients 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 hiddend 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
+ * sbunlock() 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 (so->so_rcv.sb_cc <= control->held_length) {
+ error = sbwait(&so->so_rcv);
+ if (error) {
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ goto release;
+#else
+ goto release_unlocked;
+#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 (so->so_rcv.sb_cc > held_length) {
+ control->held_length = so->so_rcv.sb_cc;
+ 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__)
+ sbunlock(&so->so_rcv);
+ 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) {
+ sbunlock(&so->so_rcv);
+ }
+#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_add_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,
+ so->so_rcv.sb_cc);
+ } 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,
+ so->so_rcv.sb_cc);
+ }
+ }
+ 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, 0);
+ 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;
+
+ sin6 = (struct sockaddr_in6 *)sa;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ /* Must be non-mapped for connectx */
+ return (EINVAL);
+ }
+ incr = (unsigned int)sizeof(struct sockaddr_in6);
+#ifdef HAVE_SA_LEN
+ if (sa->sa_len != incr) {
+ return (EINVAL);
+ }
+#endif
+ (*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 void
+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;
+ out:
+ m_freem(m);
+}
+
+#ifdef INET
+static void
+sctp_recv_icmp_tunneled_packet(int cmd, struct sockaddr *sa, void *vip, void *ctx SCTP_UNUSED)
+{
+ struct ip *outer_ip, *inner_ip;
+ struct sctphdr *sh;
+ struct icmp *icmp;
+ 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 = (struct ip *)vip;
+ icmp = (struct icmp *)((caddr_t)inner_ip -
+ (sizeof(struct icmp) - sizeof(struct 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) &&
+ (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(int cmd, struct sockaddr *sa, void *d, void *ctx SCTP_UNUSED)
+{
+ struct ip6ctlparam *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;
+
+ ip6cp = (struct ip6ctlparam *)d;
+ /*
+ * 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) &&
+ (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 writting!
+ */
+#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 writting!
+ */
+ 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 100755
index 0000000000..0c5a40d87b
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctputil.h
@@ -0,0 +1,414 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet/sctputil.h 362448 2020-06-20 20:20:16Z tuexen $");
+#endif
+
+#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, uint16_t);
+
+void sctp_fill_random_store(struct sctp_pcb *);
+
+void
+sctp_notify_stream_reset_add(struct sctp_tcb *stcb, uint16_t numberin,
+ uint16_t numberout, int flag);
+void
+sctp_notify_stream_reset_tsn(struct sctp_tcb *stcb, uint32_t sending_tsn, uint32_t recv_tsn, int flag);
+
+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_mtu_size_reset(struct sctp_inpcb *, struct sctp_association *, uint32_t);
+
+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);
+
+int sctp_expand_mapping_array(struct sctp_association *, uint32_t);
+
+void sctp_abort_notification(struct sctp_tcb *, uint8_t, 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 *, 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);
+
+#ifdef SCTP_MBCNT_LOGGING
+void
+sctp_free_bufspace(struct sctp_tcb *, struct sctp_association *,
+ struct sctp_tmit_chunk *, int);
+
+#else
+#define sctp_free_bufspace(stcb, asoc, tp1, chk_cnt) \
+do { \
+ if (tp1->data != NULL) { \
+ atomic_subtract_int(&((asoc)->chunks_on_out_queue), chk_cnt); \
+ 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 && ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || \
+ (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL))) { \
+ if (stcb->sctp_socket->so_snd.sb_cc >= tp1->book_size) { \
+ atomic_subtract_int(&((stcb)->sctp_socket->so_snd.sb_cc), tp1->book_size); \
+ } else { \
+ stcb->sctp_socket->so_snd.sb_cc = 0; \
+ } \
+ } \
+ } \
+} while (0)
+
+#endif
+
+#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))) { \
+ if (stcb->sctp_socket->so_snd.sb_cc >= sp->length) { \
+ atomic_subtract_int(&stcb->sctp_socket->so_snd.sb_cc,sp->length); \
+ } else { \
+ stcb->sctp_socket->so_snd.sb_cc = 0; \
+ } \
+ } \
+ } \
+} 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))) { \
+ atomic_add_int(&stcb->sctp_socket->so_snd.sb_cc,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..1fc81daa87
--- /dev/null
+++ b/netwerk/sctp/src/netinet6/sctp6_usrreq.c
@@ -0,0 +1,1809 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet6/sctp6_usrreq.c 361895 2020-06-07 14:39:20Z tuexen $");
+#endif
+
+#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;
+ }
+ ecn_bits = ((ntohl(ip6->ip6_flow) >> 20) & 0x000000ff);
+#if defined(__FreeBSD__)
+ 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) &&
+ (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, 1, 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);
+ }
+ /* 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;
+ }
+}
+
+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;
+#if defined(__FreeBSD__)
+ 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__)
+ 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 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__)
+ 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;
+ }
+#else
+ SCTP_TCB_UNLOCK(stcb);
+ return;
+#endif
+ }
+ 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) &&
+ (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
+
+/*
+ * 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,
+ 0, 0,
+ sctp6_getcred, "S,ucred", "Get the ucred of a SCTP6 connection");
+
+#endif
+
+/* This is the same as the sctp_abort() could be made common */
+#if defined(__Userspace__)
+int
+#elif defined(__FreeBSD__) || defined(_WIN32)
+static void
+#else
+static int
+#endif
+sctp6_abort(struct socket *so)
+{
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ struct epoch_tracker et;
+#endif
+ 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_SCTP6_USRREQ, EINVAL);
+#if (defined(__FreeBSD__) || defined(_WIN32)) && !defined(__Userspace__)
+ return;
+#else
+ return (EINVAL);
+#endif
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_ENTER(et);
+#endif
+ 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)))) {
+#ifdef SCTP_LOG_CLOSING
+ sctp_log_closing(inp, NULL, 16);
+#endif
+ sctp_inpcb_free(inp, SCTP_FREE_SHOULD_USE_ABORT,
+ SCTP_CALLED_AFTER_CMPSET_OFCLOSE);
+ SOCK_LOCK(so);
+ 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__)
+ so->so_usecount--;
+#else
+ /* Now null out the reference, we are completely detached. */
+ so->so_pcb = NULL;
+#endif
+ SOCK_UNLOCK(so);
+ } else {
+ flags = inp->sctp_flags;
+ if ((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) {
+ goto sctp_must_try_again;
+ }
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ NET_EPOCH_EXIT(et);
+ return;
+#else
+ return (0);
+#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
+
+#if !defined(__Userspace__)
+static
+#endif
+int
+sctp6_disconnect(struct socket *so)
+{
+ return (sctp_disconnect(so));
+}
+
+
+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);
+ }
+#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) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ }
+ if (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 (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(inp, addr, &error, 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);
+ }
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) {
+ stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED;
+ /* Set the connected flag so we can queue data */
+ soisconnecting(so);
+ }
+ 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__)
+struct pr_usrreqs sctp6_usrreqs = {
+#if defined(__FreeBSD__)
+ .pru_abort = sctp6_abort,
+ .pru_accept = sctp_accept,
+ .pru_attach = sctp6_attach,
+ .pru_bind = sctp6_bind,
+ .pru_connect = sctp6_connect,
+ .pru_control = in6_control,
+ .pru_close = sctp6_close,
+ .pru_detach = sctp6_close,
+ .pru_sopoll = sopoll_generic,
+ .pru_flush = sctp_flush,
+ .pru_disconnect = sctp6_disconnect,
+ .pru_listen = sctp_listen,
+ .pru_peeraddr = sctp6_getpeeraddr,
+ .pru_send = sctp6_send,
+ .pru_shutdown = sctp_shutdown,
+ .pru_sockaddr = sctp6_in6getaddr,
+ .pru_sosend = sctp_sosend,
+ .pru_soreceive = sctp_soreceive
+#elif defined(__APPLE__) && !defined(__Userspace__)
+ .pru_abort = sctp6_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 = sctp6_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__)
+ sctp6_abort,
+ sctp_accept,
+ sctp6_attach,
+ sctp6_bind,
+ sctp6_connect,
+ pru_connect2_notsupp,
+ NULL,
+ NULL,
+ sctp6_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
+};
+
+#elif !defined(__Userspace__)
+int
+sctp6_usrreq(so, req, m, nam, control, p)
+ struct socket *so;
+ int req;
+ struct mbuf *m, *nam, *control;
+ struct proc *p;
+{
+ int error;
+ int family;
+
+ family = so->so_proto->pr_domain->dom_family;
+
+ if (req == PRU_CONTROL) {
+ switch (family) {
+ case PF_INET:
+ error = in_control(so, (long)m, (caddr_t)nam,
+ (struct ifnet *)control );
+ break;
+#ifdef INET6
+ case PF_INET6:
+ error = in6_control(so, (long)m, (caddr_t)nam,
+ (struct ifnet *)control, p);
+ break;
+#endif
+ default:
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EAFNOSUPPORT);
+ error = EAFNOSUPPORT;
+ }
+ return (error);
+ }
+ switch (req) {
+ case PRU_ATTACH:
+ error = sctp6_attach(so, family, p);
+ break;
+ case PRU_DETACH:
+ error = sctp6_detach(so);
+ break;
+ case PRU_BIND:
+ if (nam == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ error = sctp6_bind(so, nam, p);
+ break;
+ case PRU_LISTEN:
+ error = sctp_listen(so, p);
+ break;
+ case PRU_CONNECT:
+ if (nam == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ error = sctp6_connect(so, nam, p);
+ break;
+ case PRU_DISCONNECT:
+ error = sctp6_disconnect(so);
+ break;
+ case PRU_ACCEPT:
+ if (nam == NULL) {
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+ return (EINVAL);
+ }
+ error = sctp_accept(so, nam);
+ break;
+ case PRU_SHUTDOWN:
+ error = sctp_shutdown(so);
+ break;
+
+ case PRU_RCVD:
+ /*
+ * For OpenBSD and NetBSD, this is real ugly. The (mbuf *)
+ * nam that is passed (by soreceive()) is the int flags cast
+ * as a (mbuf *) yuck!
+ */
+ error = sctp_usr_recvd(so, (int)((long)nam));
+ break;
+
+ case PRU_SEND:
+ /* Flags are ignored */
+ error = sctp6_send(so, 0, m, nam, control, p);
+ break;
+ case PRU_ABORT:
+ error = sctp6_abort(so);
+ break;
+
+ case PRU_SENSE:
+ error = 0;
+ break;
+ case PRU_RCVOOB:
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EAFNOSUPPORT);
+ error = EAFNOSUPPORT;
+ break;
+ case PRU_SENDOOB:
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EAFNOSUPPORT);
+ error = EAFNOSUPPORT;
+ break;
+ case PRU_PEERADDR:
+ error = sctp6_getpeeraddr(so, nam);
+ break;
+ case PRU_SOCKADDR:
+ error = sctp6_in6getaddr(so, nam);
+ break;
+ case PRU_SLOWTIMO:
+ error = 0;
+ break;
+ default:
+ error = 0;
+ break;
+ }
+ return (error);
+}
+#endif
+#endif
diff --git a/netwerk/sctp/src/netinet6/sctp6_var.h b/netwerk/sctp/src/netinet6/sctp6_var.h
new file mode 100755
index 0000000000..56a6c3af3d
--- /dev/null
+++ b/netwerk/sctp/src/netinet6/sctp6_var.h
@@ -0,0 +1,81 @@
+/*-
+ * 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>
+__FBSDID("$FreeBSD: head/sys/netinet6/sctp6_var.h 317457 2017-04-26 19:26:40Z tuexen $");
+#endif
+
+#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);
+extern struct pr_usrreqs sctp6_usrreqs;
+#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);
+#else
+void sctp6_ctlinput(int, struct sockaddr *, void *);
+#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/user_atomic.h b/netwerk/sctp/src/user_atomic.h
new file mode 100755
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 100755
index 0000000000..dc9d98a392
--- /dev/null
+++ b/netwerk/sctp/src/user_environment.c
@@ -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.
+ */
+
+/* __Userspace__ */
+
+#include <stdlib.h>
+#if !defined(_WIN32)
+#include <stdint.h>
+#include <netinet/sctp_os_userspace.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
+#include <string.h>
+
+#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).
+ */
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+void
+init_random(void)
+{
+ return;
+}
+
+int
+read_random(void *buf, int count)
+{
+ memset(buf, 'A', count);
+ return (count);
+}
+#elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
+void
+init_random(void)
+{
+ return;
+}
+
+int
+read_random(void *buf, int count)
+{
+ if (count >= 0) {
+ arc4random_buf(buf, count);
+ }
+ return (count);
+}
+#else
+void
+init_random(void)
+{
+ struct timeval now;
+ unsigned int seed;
+
+ (void)SCTP_GETTIME_TIMEVAL(&now);
+ seed = 0;
+ seed |= (unsigned int)now.tv_sec;
+ seed |= (unsigned int)now.tv_usec;
+#if !defined(_WIN32) &&! defined(__native_client__)
+ seed |= getpid();
+#endif
+#if defined(_WIN32) || defined(__native_client__)
+ srand(seed);
+#else
+ srandom(seed);
+#endif
+ return;
+}
+
+int
+read_random(void *buf, int count)
+{
+ uint32_t randval;
+ int size, i;
+
+ /* Fill buf[] with random(9) output */
+ for (i = 0; i < count; i+= (int)sizeof(uint32_t)) {
+ randval = random();
+ size = MIN(count - i, (int)sizeof(uint32_t));
+ memcpy(&((char *)buf)[i], &randval, (size_t)size);
+ }
+
+ return (count);
+}
+#endif
diff --git a/netwerk/sctp/src/user_environment.h b/netwerk/sctp/src/user_environment.h
new file mode 100755
index 0000000000..0c2ec9db23
--- /dev/null
+++ b/netwerk/sctp/src/user_environment.h
@@ -0,0 +1,118 @@
+/*-
+ * 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_
+/* __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);
+int read_random(void *, int);
+
+/* 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>
+
+static inline void
+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 100755
index 0000000000..35d59c858f
--- /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.
+ * 4. 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 100755
index 0000000000..e1c8f24156
--- /dev/null
+++ b/netwerk/sctp/src/user_ip6_var.h
@@ -0,0 +1,126 @@
+/*-
+ * 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.
+ * 4. 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)
+#if !defined(__linux__)
+#define s6_addr8 __u6_addr.__u6_addr8
+#define s6_addr16 __u6_addr.__u6_addr16
+#define s6_addr32 __u6_addr.__u6_addr32
+#endif
+#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 100755
index 0000000000..049314e205
--- /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.
+ * 4. 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 100755
index 0000000000..1612452168
--- /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.
+ * 4. 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 100755
index 0000000000..9e566e3025
--- /dev/null
+++ b/netwerk/sctp/src/user_mbuf.c
@@ -0,0 +1,1566 @@
+/*-
+ * 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;
+ if (type == MT_NOINIT)
+ return (0);
+
+ 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,
+ NUULL,
+ 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;
+
+ /*
+ * The mbuf is initialized later.
+ *
+ */
+ if (type == MT_NOINIT)
+ return (0);
+
+ 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;
+}
+
+
+/*
+ * 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 100755
index 0000000000..c11b38c9cb
--- /dev/null
+++ b/netwerk/sctp/src/user_mbuf.h
@@ -0,0 +1,413 @@
+/*-
+ * 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);
+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"
+
+#define MT_NOINIT 255 /* Not a type but a flag to allocate
+ a non-initialized mbuf */
+
+/*
+ * 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[] */
+
+#define MT_NOINIT 255 /* Not a type but a flag to allocate
+ a non-initialized mbuf */
+
+/*
+ * __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)) ) \
+
+
+/*
+ * 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 100755
index 0000000000..cde1ccecf1
--- /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.
+ * 4. 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 100755
index 0000000000..3c1657b4a6
--- /dev/null
+++ b/netwerk/sctp/src/user_recv_thread.c
@@ -0,0 +1,1521 @@
+/*-
+ * 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(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__)
+#include <net/route.h>
+#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)
+ 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;
+ ncounter = n = recvmsg(SCTP_BASE_VAR(userspace_rawsctp), &msg, 0);
+ if (n < 0) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ } else {
+ break;
+ }
+ }
+#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);
+ }
+
+ 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 ip) + sizeof(struct sctphdr);
+
+ if (iphdr->ip_tos != 0) {
+ ecn = iphdr->ip_tos & 0x02;
+ }
+
+ 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)
+ unsigned int ncounter = 0;
+ 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;
+
+ ncounter = n = recvmsg(SCTP_BASE_VAR(userspace_rawsctp6), &msg, 0);
+ if (n < 0) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ } else {
+ break;
+ }
+ }
+#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;
+ }
+
+ sh = mtod(recvmbuf6[0], struct sctphdr *);
+ ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(struct sctphdr));
+ offset = sizeof(struct sctphdr);
+
+ 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)
+ 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;
+
+ ncounter = n = recvmsg(SCTP_BASE_VAR(userspace_udpsctp), &msg, 0);
+ if (n < 0) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ } else {
+ break;
+ }
+ }
+#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);*/
+ sh = mtod(udprecvmbuf[0], struct sctphdr *);
+ ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(struct sctphdr));
+ offset = sizeof(struct sctphdr);
+ 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;
+ 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;
+
+ ncounter = n = recvmsg(SCTP_BASE_VAR(userspace_udpsctp6), &msg, 0);
+ if (n < 0) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ } else {
+ break;
+ }
+ }
+#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;
+ }
+
+ sh = mtod(udprecvmbuf6[0], struct sctphdr *);
+ ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(struct sctphdr));
+ offset = sizeof(struct sctphdr);
+
+ 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) {
+ 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) {
+ 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 100755
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 100755
index 0000000000..82b07d769a
--- /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.
+ * 4. 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 100755
index 0000000000..b72b05ebc3
--- /dev/null
+++ b/netwerk/sctp/src/user_socket.c
@@ -0,0 +1,3590 @@
+/*-
+ * 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 irrelevent
+ * 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 shold 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)
+{
+#if defined(INET6)
+ struct sctp_inpcb *inp;
+#endif
+
+#if defined(INET6)
+ inp = (struct sctp_inpcb *)so->so_pcb;
+ if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ sctp6_abort(so);
+ } else {
+#if defined(INET)
+ sctp_abort(so);
+#endif
+ }
+#elif defined(INET)
+ sctp_abort(so);
+#endif
+ 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),
+ uint32_t sb_threshold,
+ void *ulp_info)
+{
+ struct socket *so;
+
+ 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) && (error = sodisconnect(so))) {
+ 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;
+ sctp_assoc_t asoc;
+ caddr_t lim;
+ socklen_t opt_len;
+ int cnt;
+
+ if (raddrs == NULL) {
+ errno = EFAULT;
+ return (-1);
+ }
+ asoc = id;
+ opt_len = (socklen_t)sizeof(sctp_assoc_t);
+ if (usrsctp_getsockopt(so, IPPROTO_SCTP, SCTP_GET_REMOTE_ADDR_SIZE, &asoc, &opt_len) != 0) {
+ return (-1);
+ }
+ /* size required is returned in 'asoc' */
+ opt_len = (socklen_t)((size_t)asoc + 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;
+ caddr_t lim;
+ struct sockaddr *sa;
+ size_t size_of_addresses;
+ socklen_t opt_len;
+ int cnt;
+
+ if (raddrs == NULL) {
+ errno = EFAULT;
+ return (-1);
+ }
+ size_of_addresses = 0;
+ opt_len = (socklen_t)sizeof(int);
+ if (usrsctp_getsockopt(so, IPPROTO_SCTP, SCTP_GET_LOCAL_ADDR_SIZE, &size_of_addresses, &opt_len) != 0) {
+ errno = ENOMEM;
+ return (-1);
+ }
+ if (size_of_addresses == 0) {
+ errno = ENOTCONN;
+ 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);
+ errno = ENOMEM;
+ 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_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 *stcb,
+ uint32_t vrf_id)
+{
+ struct mbuf *m;
+ struct mbuf *m_orig;
+ int iovcnt;
+ int len;
+ int send_count;
+ 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));
+ }
+
+ send_count = 0;
+ 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);
+ send_count += send_iovec[iovcnt].iov_len;
+#else
+ send_iovec[iovcnt].buf = (caddr_t)m->m_data;
+ send_iovec[iovcnt].len = SCTP_BUF_LEN(m);
+ send_count += send_iovec[iovcnt].len;
+#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 *stcb,
+ uint32_t vrf_id)
+{
+ struct mbuf *m;
+ struct mbuf *m_orig;
+ int iovcnt;
+ int len;
+ int send_count;
+ 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));
+ }
+
+ send_count = 0;
+ 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);
+ send_count += send_iovec[iovcnt].iov_len;
+#else
+ send_iovec[iovcnt].buf = (caddr_t)m->m_data;
+ send_iovec[iovcnt].len = SCTP_BUF_LEN(m);
+ send_count += send_iovec[iovcnt].len;
+#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;
+
+ 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);
+ if (SCTP_BUF_LEN(m) < (int)(sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr))) {
+ if ((m = m_pullup(m, sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr))) == NULL) {
+ SCTP_STAT_INCR(sctps_hdrops);
+ return;
+ }
+ }
+ sh = mtod(m, struct sctphdr *);;
+ ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(struct sctphdr));
+ src.sconn_port = sh->src_port;
+ dst.sconn_port = sh->dest_port;
+ sctp_common_input_processing(&m, 0, sizeof(struct sctphdr), (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 100755
index 0000000000..6b79b5908c
--- /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.
+ * 4. 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 100755
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..fea2aafd2d
--- /dev/null
+++ b/netwerk/sctp/src/usrsctp.h
@@ -0,0 +1,1321 @@
+/*-
+ * 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
+#define uint8_t unsigned __int8
+#define uint16_t unsigned __int16
+#define uint32_t unsigned __int32
+#define uint64_t unsigned __int64
+#define int16_t __int16
+#define int32_t __int32
+#endif
+
+#ifndef ssize_t
+#ifdef _WIN64
+#define ssize_t __int64
+#elif defined _WIN32
+#define ssize_t int
+#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_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;
+};
+
+
+/*
+ * 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),
+ 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)
+#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_reserved[31]; /* 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/update_sctp.sh b/netwerk/sctp/update_sctp.sh
new file mode 100755
index 0000000000..45f33dd732
--- /dev/null
+++ b/netwerk/sctp/update_sctp.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# assume $1 is the directory with a checkout of the libsctp source
+#
+# sctp usrlib source was available (via svn) at:
+# svn co https://sctp-refimpl.googlecode.com/svn/trunk sctpSVN
+# now available via git:
+# git clone https://github.com/sctplab/usrsctp.git
+#
+# also assumes we've made *NO* changes to the SCTP sources! If we do, we have to merge by
+# hand after this process, or use a more complex one.
+#
+# For example, one could update an sctp library import head, and merge back to default. Or keep a
+# separate repo with this in it, and pull from there to m-c and merge.
+if [ "$1" ] ; then
+ export date=`date`
+ export revision=`(cd $1; git rev-parse HEAD)`
+
+ cp $1/usrsctplib/*.c $1/usrsctplib/*.h netwerk/sctp/src
+ cp $1/usrsctplib/netinet/*.c $1/usrsctplib/netinet/*.h netwerk/sctp/src/netinet
+ cp $1/usrsctplib/netinet6/*.c $1/usrsctplib/netinet6/*.h netwerk/sctp/src/netinet6
+
+ hg addremove netwerk/sctp/src --include "**.c" --include "**.h" --similarity 90
+
+ echo "sctp updated to version $revision from git on $date" >> netwerk/sctp/sctp_update.log
+ echo "sctp updated to version $revision from git on $date"
+else
+ echo "usage: $0 path_to_sctp_directory"
+ echo "run from the root of your m-c clone"
+ echo "example: sh netwerk/sctp/update.sh ../../sctp"
+fi
diff --git a/netwerk/socket/moz.build b/netwerk/socket/moz.build
new file mode 100644
index 0000000000..e42e16a1e2
--- /dev/null
+++ b/netwerk/socket/moz.build
@@ -0,0 +1,49 @@
+# -*- 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",
+ "nsISOCKSSocketInfo.idl",
+ "nsISSLSocketControl.idl",
+ "nsITransportSecurityInfo.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..ddf9e8df49
--- /dev/null
+++ b/netwerk/socket/neqo/extra-bindgen-flags.in
@@ -0,0 +1 @@
+@BINDGEN_SYSTEM_FLAGS@
diff --git a/netwerk/socket/neqo_glue/Cargo.toml b/netwerk/socket/neqo_glue/Cargo.toml
new file mode 100644
index 0000000000..84ef23841c
--- /dev/null
+++ b/netwerk/socket/neqo_glue/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "neqo_glue"
+version = "0.1.0"
+authors = ["Dragana Damjanovic <dd.mozilla@gmail.com>"]
+edition = "2018"
+
+[lib]
+name = "neqo_glue"
+
+[dependencies]
+neqo-http3 = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
+neqo-transport = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
+neqo-common = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
+neqo-qpack = { tag = "v0.4.19", 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 = "0.4.0"
+
+[dependencies.neqo-crypto]
+tag = "v0.4.19"
+git = "https://github.com/mozilla/neqo"
+default-features = false
+features = ["gecko"]
diff --git a/netwerk/socket/neqo_glue/NeqoHttp3Conn.h b/netwerk/socket/neqo_glue/NeqoHttp3Conn.h
new file mode 100644
index 0000000000..c2ba79fac2
--- /dev/null
+++ b/netwerk/socket/neqo_glue/NeqoHttp3Conn.h
@@ -0,0 +1,107 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "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 nsACString& aLocalAddr,
+ const nsACString& aRemoteAddr, uint32_t aMaxTableSize,
+ uint16_t aMaxBlockedStreams, const nsACString& aQlogDir,
+ NeqoHttp3Conn** aConn) {
+ return neqo_http3conn_new(&aOrigin, &aAlpn, &aLocalAddr, &aRemoteAddr,
+ aMaxTableSize, aMaxBlockedStreams, &aQlogDir,
+ (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);
+ }
+
+ void ProcessInput(uint8_t* aPacket, uint32_t aLen) {
+ neqo_http3conn_process_input(this, aPacket, aLen);
+ }
+
+ uint64_t ProcessOutput() { return neqo_http3conn_process_output(this); }
+
+ bool HasDataToSend() { return neqo_http3conn_has_data_to_send(this); }
+
+ nsresult GetDataToSend(nsTArray<uint8_t>& aData) {
+ aData.TruncateLength(0);
+ return neqo_http3conn_get_data_to_send(this, &aData);
+ }
+
+ 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) {
+ return neqo_http3conn_fetch(this, &aMethod, &aScheme, &aHost, &aPath,
+ &aHeaders, aStreamId);
+ }
+
+ 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 ResetStream(uint64_t aStreamId, uint64_t aError) {
+ neqo_http3conn_reset_stream(this, aStreamId, aError);
+ }
+
+ void SetResumptionToken(nsTArray<uint8_t>& aToken) {
+ neqo_http3conn_set_resumption_token(this, &aToken);
+ }
+
+ bool IsZeroRtt() { return neqo_http3conn_is_zero_rtt(this); }
+
+ nsrefcnt AddRef() { return neqo_http3conn_addref(this); }
+ nsrefcnt Release() { return neqo_http3conn_release(this); }
+
+ void GetStats(Http3Stats* aStats) {
+ return neqo_http3conn_get_stats(this, aStats);
+ }
+
+ 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..406c8dbc66
--- /dev/null
+++ b/netwerk/socket/neqo_glue/cbindgen.toml
@@ -0,0 +1,26 @@
+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;
+} // 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"]
+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..9d04aba3f5
--- /dev/null
+++ b/netwerk/socket/neqo_glue/src/lib.rs
@@ -0,0 +1,816 @@
+/* This Source Code Form is subject to the terms of 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/. */
+
+use neqo_common::event::Provider;
+use neqo_common::{self as common, qlog::NeqoQlog, qwarn, Datagram, Role};
+use neqo_crypto::{init, PRErrorCode};
+use neqo_http3::Error as Http3Error;
+use neqo_http3::{Http3Client, Http3ClientEvent, Http3Parameters, Http3State};
+use neqo_qpack::QpackSettings;
+use neqo_transport::{
+ ConnectionParameters, Error as TransportError, FixedConnectionIdManager, Output, QuicVersion,
+};
+use nserror::*;
+use nsstring::*;
+use qlog::QlogStreamer;
+use std::cell::RefCell;
+use std::convert::TryFrom;
+use std::fs::OpenOptions;
+use std::net::SocketAddr;
+use std::path::PathBuf;
+use std::ptr;
+use std::rc::Rc;
+use std::slice;
+use std::str;
+use std::time::Instant;
+use thin_vec::ThinVec;
+use xpcom::{interfaces::nsrefcnt, AtomicRefcnt, RefCounted, RefPtr};
+
+#[repr(C)]
+pub struct NeqoHttp3Conn {
+ conn: Http3Client,
+ local_addr: SocketAddr,
+ remote_addr: SocketAddr,
+ refcnt: AtomicRefcnt,
+ packets_to_send: Vec<Datagram>,
+}
+
+impl NeqoHttp3Conn {
+ fn new(
+ origin: &nsACString,
+ alpn: &nsACString,
+ local_addr: &nsACString,
+ remote_addr: &nsACString,
+ max_table_size: u64,
+ max_blocked_streams: u16,
+ qlog_dir: &nsACString,
+ ) -> 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 = match str::from_utf8(local_addr) {
+ Ok(s) => match s.parse() {
+ Ok(addr) => addr,
+ Err(_) => return Err(NS_ERROR_INVALID_ARG),
+ },
+ Err(_) => return Err(NS_ERROR_INVALID_ARG),
+ };
+
+ let remote: SocketAddr = match str::from_utf8(remote_addr) {
+ Ok(s) => match s.parse() {
+ Ok(addr) => addr,
+ Err(_) => return Err(NS_ERROR_INVALID_ARG),
+ },
+ Err(_) => return Err(NS_ERROR_INVALID_ARG),
+ };
+
+ let http3_settings = Http3Parameters {
+ qpack_settings: QpackSettings {
+ max_table_size_encoder: max_table_size,
+ max_table_size_decoder: max_table_size,
+ max_blocked_streams,
+ },
+ max_concurrent_push_streams: 0,
+ };
+
+ let quic_version = match alpn_conv {
+ "h3-30" => QuicVersion::Draft30,
+ "h3-29" => QuicVersion::Draft29,
+ "h3-28" => QuicVersion::Draft28,
+ "h3-27" => QuicVersion::Draft27,
+ _ => return Err(NS_ERROR_INVALID_ARG),
+ };
+
+ let mut conn = match Http3Client::new(
+ origin_conv,
+ Rc::new(RefCell::new(FixedConnectionIdManager::new(3))),
+ local,
+ remote,
+ &ConnectionParameters::default().quic_version(quic_version),
+ &http3_settings,
+ ) {
+ 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));
+
+ // 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),
+ 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,
+ remote_addr: remote,
+ refcnt: unsafe { AtomicRefcnt::new() },
+ packets_to_send: Vec::new(),
+ }));
+ unsafe { Ok(RefPtr::from_raw(conn).unwrap()) }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn neqo_http3conn_addref(conn: &NeqoHttp3Conn) -> nsrefcnt {
+ conn.refcnt.inc()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn neqo_http3conn_release(conn: &NeqoHttp3Conn) -> nsrefcnt {
+ let rc = conn.refcnt.dec();
+ if rc == 0 {
+ Box::from_raw(conn as *const _ as *mut NeqoHttp3Conn);
+ }
+ rc
+}
+
+// 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: &nsACString,
+ remote_addr: &nsACString,
+ max_table_size: u64,
+ max_blocked_streams: u16,
+ qlog_dir: &nsACString,
+ result: &mut *const NeqoHttp3Conn,
+) -> nsresult {
+ *result = ptr::null_mut();
+
+ match NeqoHttp3Conn::new(
+ origin,
+ alpn,
+ local_addr,
+ remote_addr,
+ max_table_size,
+ max_blocked_streams,
+ qlog_dir,
+ ) {
+ Ok(http3_conn) => {
+ http3_conn.forget(result);
+ NS_OK
+ }
+ Err(e) => e,
+ }
+}
+
+/* Process a packet.
+ * packet holds packet data.
+ */
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_process_input(
+ conn: &mut NeqoHttp3Conn,
+ packet: *const u8,
+ len: u32,
+) {
+ let array = unsafe { slice::from_raw_parts(packet, len as usize) };
+ conn.conn.process_input(
+ Datagram::new(conn.remote_addr, conn.local_addr, array.to_vec()),
+ Instant::now(),
+ );
+}
+
+/* Process output and store data to be sent into conn.packets_to_send.
+ * neqo_http3conn_get_data_to_send will be called to pick up this data.
+ */
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_process_output(conn: &mut NeqoHttp3Conn) -> u64 {
+ loop {
+ let out = conn.conn.process_output(Instant::now());
+ match out {
+ Output::Datagram(dg) => {
+ conn.packets_to_send.push(dg);
+ }
+ Output::Callback(to) => {
+ let timeout = to.as_millis() as u64;
+ // Necko resolution is in milliseconds whereas neqo resolution
+ // is in nanoseconds. If we called process_output too soon due
+ // to this difference, we might do few unnecessary loops until
+ // we waste the remaining time. To avoid it, we return 1ms when
+ // the timeout is less than 1ms.
+ if timeout == 0 {
+ break 1;
+ }
+ break timeout;
+ }
+ Output::None => break std::u64::MAX,
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_has_data_to_send(conn: &mut NeqoHttp3Conn) -> bool {
+ !conn.packets_to_send.is_empty()
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_get_data_to_send(
+ conn: &mut NeqoHttp3Conn,
+ packet: &mut ThinVec<u8>,
+) -> nsresult {
+ match conn.packets_to_send.pop() {
+ None => NS_BASE_STREAM_WOULD_BLOCK,
+ Some(d) => {
+ packet.extend_from_slice(&d);
+ NS_OK
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_close(conn: &mut NeqoHttp3Conn, error: u64) {
+ conn.conn.close(Instant::now(), 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
+ }
+}
+
+#[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,
+) -> 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 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((name, value));
+ }
+ }
+ }
+
+ 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;
+ }
+ };
+ match conn.conn.fetch(
+ Instant::now(),
+ method_tmp,
+ scheme_tmp,
+ host_tmp,
+ path_tmp,
+ &hdrs,
+ ) {
+ Ok(id) => {
+ *stream_id = id;
+ NS_OK
+ }
+ Err(Http3Error::StreamLimitError) => NS_BASE_STREAM_WOULD_BLOCK,
+ Err(_) => NS_ERROR_UNEXPECTED,
+ }
+}
+
+#[no_mangle]
+pub 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 = unsafe { slice::from_raw_parts(buf, len as usize) };
+ match conn.conn.send_request_body(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,
+ }
+}
+
+// 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 {
+ QuicTransportError(u64),
+ Http3AppError(u64),
+}
+
+impl From<neqo_transport::CloseError> for CloseError {
+ fn from(error: neqo_transport::CloseError) -> CloseError {
+ match error {
+ neqo_transport::CloseError::Transport(c) => CloseError::QuicTransportError(c),
+ neqo_transport::CloseError::Application(c) => CloseError::Http3AppError(c),
+ }
+ }
+}
+
+// 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(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(stream_id) {
+ Ok(()) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[repr(C)]
+pub enum Http3Event {
+ /// A request stream has space for more data to be send.
+ DataWritable {
+ stream_id: u64,
+ },
+ /// A server has send STOP_SENDING frame.
+ StopSending {
+ stream_id: u64,
+ error: u64,
+ },
+ HeaderReady {
+ stream_id: u64,
+ fin: 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
+ },
+ NoEvent,
+}
+
+fn convert_h3_to_h1_headers(
+ headers: Vec<(String, String)>,
+ ret_headers: &mut ThinVec<u8>,
+) -> nsresult {
+ if headers.iter().filter(|(k, _)| k == ":status").count() != 1 {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ let (_, status_val) = headers
+ .iter()
+ .find(|(k, _)| k == ":status")
+ .expect("must be one");
+
+ 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 (key, value) in headers.iter().filter(|(k, _)| k != ":status") {
+ ret_headers.extend_from_slice(key.as_bytes());
+ ret_headers.extend_from_slice(b": ");
+ ret_headers.extend_from_slice(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 },
+ Http3ClientEvent::StopSending { stream_id, error } => {
+ Http3Event::StopSending { stream_id, error }
+ }
+ Http3ClientEvent::HeaderReady {
+ stream_id,
+ headers,
+ fin,
+ interim,
+ } => {
+ if interim {
+ // This are 1xx responses and they are ignored.
+ Http3Event::NoEvent
+ } else {
+ let res = convert_h3_to_h1_headers(headers, data);
+ if res != NS_OK {
+ return res;
+ }
+ Http3Event::HeaderReady { stream_id, fin }
+ }
+ }
+ Http3ClientEvent::DataReadable { stream_id } => Http3Event::DataReadable { stream_id },
+ Http3ClientEvent::Reset {
+ stream_id,
+ error,
+ local,
+ } => Http3Event::Reset {
+ stream_id,
+ 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,
+ }
+ }
+ 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) => Http3Event::ConnectionClosing {
+ error: error_code.into(),
+ },
+ Http3State::Closed(error_code) => Http3Event::ConnectionClosed {
+ error: error_code.into(),
+ },
+ _ => Http3Event::NoEvent,
+ },
+ };
+
+ 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 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 = unsafe { slice::from_raw_parts_mut(buf, len as usize) };
+ match conn
+ .conn
+ .read_response_data(Instant::now(), 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,
+}
+
+#[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();
+ 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(), Instant::now());
+}
+
+#[no_mangle]
+pub extern "C" fn neqo_http3conn_set_resumption_token(
+ conn: &mut NeqoHttp3Conn,
+ token: &mut ThinVec<u8>,
+) {
+ let _ = conn.conn.enable_resumption(Instant::now(), token);
+}
+
+#[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;
+}
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/nsISOCKSSocketInfo.idl b/netwerk/socket/nsISOCKSSocketInfo.idl
new file mode 100644
index 0000000000..b17176f53b
--- /dev/null
+++ b/netwerk/socket/nsISOCKSSocketInfo.idl
@@ -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 "nsISupports.idl"
+
+%{ C++
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+
+[scriptable, uuid(D5C0D1F9-22D7-47DC-BF91-D9AC6E1251A6)]
+interface nsISOCKSSocketInfo : nsISupports
+{
+ [noscript] attribute NetAddrPtr destinationAddr;
+ [noscript] attribute NetAddrPtr externalProxyAddr;
+ [noscript] attribute NetAddrPtr internalProxyAddr;
+};
diff --git a/netwerk/socket/nsISSLSocketControl.idl b/netwerk/socket/nsISSLSocketControl.idl
new file mode 100644
index 0000000000..41e653177f
--- /dev/null
+++ b/netwerk/socket/nsISSLSocketControl.idl
@@ -0,0 +1,170 @@
+/* -*- 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 nsIInterfaceRequestor;
+interface nsIX509Cert;
+
+%{C++
+#include "nsStringFwd.h"
+#include "nsTArrayForwardDeclare.h"
+%}
+[ref] native nsCStringTArrayRef(nsTArray<nsCString>);
+
+[scriptable, builtinclass, uuid(418265c8-654e-4fbb-ba62-4eed27de1f03)]
+interface nsISSLSocketControl : nsISupports {
+ attribute nsIInterfaceRequestor notificationCallbacks;
+
+ void proxyStartSSL();
+ void StartTLS();
+
+ /* NPN (Next Protocol Negotiation) is a mechanism for
+ negotiating the protocol to be spoken inside the SSL
+ tunnel during the SSL handshake. The NPNList is the list
+ of offered client side protocols. setNPNList() needs to
+ be called before any data is read or written (including the
+ handshake to be setup correctly. The server determines the
+ priority when multiple matches occur, but if there is no overlap
+ the first protocol in the list is used. */
+
+ [noscript] void setNPNList(in nsCStringTArrayRef aNPNList);
+
+ /* For 0RTT we need to know the alpn protocol selected for the last tls
+ * session. This function will return a value if applicable or an error
+ * NS_ERROR_NOT_AVAILABLE.
+ */
+ ACString getAlpnEarlySelection();
+
+ /* If 0RTT handshake was applied and some data has been sent, as soon as
+ * the handshake finishes this attribute will be set to appropriate value.
+ */
+ readonly attribute bool earlyDataAccepted;
+
+ /* When 0RTT is performed, PR_Write will not drive the handshake forward.
+ * It must be forced by calling this function.
+ */
+ void driveHandshake();
+
+ /* Determine if a potential SSL connection to hostname:port with
+ * a desired NPN negotiated protocol of npnProtocol can use the socket
+ * associated with this object instead of making a new one. And if so, combine
+ * them.
+ */
+ boolean joinConnection(
+ in ACString npnProtocol, /* e.g. "h2" */
+ in ACString hostname,
+ in long port);
+
+ /* just like JoinConnection() except do not mark a successful test as joined.
+ */
+ boolean testJoinConnection(
+ in ACString npnProtocol, /* e.g. "h2" */
+ in ACString hostname,
+ in long port);
+
+ /* Determine if existing connection should be trusted to convey information about
+ * a hostname.
+ */
+ boolean isAcceptableForHost(in ACString hostname);
+
+ /* The Key Exchange Algorithm is used when determining whether or
+ not HTTP/2 can be used.
+
+ After a handshake is complete it can be read from KEAUsed.
+ The values correspond to the SSLKEAType enum in NSS or the
+ KEY_EXCHANGE_UNKNOWN constant defined below.
+
+ KEAKeyBits is the size/security-level used for the KEA.
+ */
+
+ [infallible] readonly attribute short KEAUsed;
+ [infallible] readonly attribute unsigned long KEAKeyBits;
+
+ const short KEY_EXCHANGE_UNKNOWN = -1;
+
+ /*
+ * The original flags from the socket provider.
+ */
+ readonly attribute uint32_t providerFlags;
+
+ /*
+ * The original TLS flags from the socket provider.
+ */
+ readonly attribute uint32_t providerTlsFlags;
+
+ /* These values are 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 SSL_VERSION_UNKNOWN = -1;
+
+ [infallible] readonly attribute short SSLVersionUsed;
+ [infallible] readonly attribute short SSLVersionOffered;
+
+ /* These values match the NSS defined values in sslt.h */
+ const short SSL_MAC_UNKNOWN = -1;
+ const short SSL_MAC_NULL = 0;
+ const short SSL_MAC_MD5 = 1;
+ const short SSL_MAC_SHA = 2;
+ const short SSL_HMAC_MD5 = 3;
+ const short SSL_HMAC_SHA = 4;
+ const short SSL_HMAC_SHA256 = 5;
+ const short SSL_MAC_AEAD = 6;
+
+ [infallible] readonly attribute short MACAlgorithmUsed;
+
+ /**
+ * If set to true before the server requests a client cert
+ * no cert will be sent.
+ */
+ [notxpcom, nostdcall] attribute boolean denyClientCert;
+
+ /**
+ * If set before the server requests a client cert (assuming it does so at
+ * all), then this cert will be presented to the server, instead of asking
+ * the user or searching the set of rememebered user cert decisions.
+ */
+ attribute nsIX509Cert clientCert;
+
+ /**
+ * True iff a client cert has been sent to the server - i.e. this
+ * socket has been client-cert authenticated.
+ */
+ [infallible] readonly attribute boolean clientCertSent;
+
+ /*
+ * failedVerification is true if any enforced certificate checks have failed.
+ * Connections that have not yet tried to verify, or are using acceptable
+ * exceptions will all return false.
+ */
+ [infallible] readonly attribute boolean failedVerification;
+
+ /*
+ * esniTxt is a string that consists of the concatenated _esni. TXT records.
+ * This is a base64 encoded ESNIKeys structure.
+ */
+ attribute ACString esniTxt;
+
+ /*
+ * echConfig is defined for conveying the ECH configuration.
+ * This is encoded in base64.
+ */
+ attribute ACString echConfig;
+
+ /**
+ * The id used to uniquely identify the connection to the peer.
+ */
+ readonly attribute ACString peerId;
+
+ /**
+ * The echConfig that should be used to retry for the connection setup.
+ */
+ readonly attribute ACString retryEchConfig;
+};
+
diff --git a/netwerk/socket/nsISocketProvider.idl b/netwerk/socket/nsISocketProvider.idl
new file mode 100644
index 0000000000..bbc13a7778
--- /dev/null
+++ b/netwerk/socket/nsISocketProvider.idl
@@ -0,0 +1,114 @@
+/* -*- 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;
+[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 aSecurityInfo
+ * Any security info that should be associated with aFileDesc. This
+ * object typically implements nsITransportSecurityInfo.
+ */
+ [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 nsISupports aSecurityInfo);
+
+ /**
+ * 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 nsISupports aSecurityInfo);
+
+ /**
+ * 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;
+};
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/nsITransportSecurityInfo.idl b/netwerk/socket/nsITransportSecurityInfo.idl
new file mode 100644
index 0000000000..b5722d328d
--- /dev/null
+++ b/netwerk/socket/nsITransportSecurityInfo.idl
@@ -0,0 +1,114 @@
+/* -*- 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 nsIX509Cert;
+
+%{ C++
+namespace IPC {
+ class Message;
+}
+class PickleIterator;
+%}
+
+[ptr] native IpcMessagePtr(IPC::Message);
+[ptr] native PickleIteratorPtr(PickleIterator);
+
+[builtinclass, scriptable, uuid(216112d3-28bc-4671-b057-f98cc09ba1ea)]
+interface nsITransportSecurityInfo : nsISupports {
+ readonly attribute unsigned long securityState;
+ readonly attribute long errorCode; // PRErrorCode
+ // errorCode as string (e.g. "SEC_ERROR_UNKNOWN_ISSUER")
+ readonly attribute AString errorCodeString;
+
+ /**
+ * The following parameters are only valid after the TLS handshake
+ * has completed. Check securityState first.
+ */
+
+ /**
+ * If certificate verification failed, this will be the peer certificate
+ * chain provided in the handshake, so it can be used for error reporting.
+ * If verification succeeded, this will be empty.
+ */
+ readonly attribute Array<nsIX509Cert> failedCertChain;
+
+ readonly attribute nsIX509Cert serverCert;
+ readonly attribute Array<nsIX509Cert> succeededCertChain;
+
+ [must_use]
+ readonly attribute ACString cipherName;
+ [must_use]
+ readonly attribute unsigned long keyLength;
+ [must_use]
+ readonly attribute unsigned long secretKeyLength;
+ [must_use]
+ readonly attribute ACString keaGroupName;
+ [must_use]
+ readonly attribute ACString signatureSchemeName;
+
+ const short SSL_VERSION_3 = 0;
+ const short TLS_VERSION_1 = 1;
+ const short TLS_VERSION_1_1 = 2;
+ const short TLS_VERSION_1_2 = 3;
+ const short TLS_VERSION_1_3 = 4;
+ [must_use]
+ readonly attribute unsigned short protocolVersion;
+
+ const short CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE = 0;
+ const short CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT = 5;
+ const short CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS = 6;
+ const short CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS = 7;
+ [must_use]
+ readonly attribute unsigned short certificateTransparencyStatus;
+
+ [must_use]
+ readonly attribute boolean isAcceptedEch;
+ [must_use]
+ readonly attribute boolean isDelegatedCredential;
+ [must_use]
+ readonly attribute boolean isDomainMismatch;
+ [must_use]
+ readonly attribute boolean isNotValidAtThisTime;
+
+ [must_use]
+ readonly attribute boolean isUntrusted;
+
+ /**
+ * True only if (and after) serverCert was successfully validated as
+ * Extended Validation (EV).
+ */
+ [must_use]
+ readonly attribute boolean isExtendedValidation;
+
+ [notxpcom, noscript]
+ void SerializeToIPC(in IpcMessagePtr aMsg);
+
+ [notxpcom, noscript]
+ bool DeserializeFromIPC([const] in IpcMessagePtr aMsg, in PickleIteratorPtr aIter);
+
+ /* negotiatedNPN is '' if no NPN list was provided by the client,
+ * or if the server did not select any protocol choice from that
+ * list. That also includes the case where the server does not
+ * implement NPN.
+ *
+ * If negotiatedNPN is read before NPN has progressed to the point
+ * where this information is available NS_ERROR_NOT_CONNECTED is
+ * raised.
+ */
+ readonly attribute ACString negotiatedNPN;
+
+ /**
+ * True iff the connection was resumed using the resumption token.
+ */
+ readonly attribute boolean resumed;
+
+ /**
+ * True iff the succeededCertChain is built in root.
+ */
+ attribute boolean isBuiltCertChainRootBuiltInRoot;
+};
diff --git a/netwerk/socket/nsNamedPipeIOLayer.cpp b/netwerk/socket/nsNamedPipeIOLayer.cpp
new file mode 100644
index 0000000000..3fb31a3a45
--- /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 (%d)", this, GetLastError());
+ return NS_ERROR_FAILURE;
+ }
+
+ DWORD pipeMode = PIPE_READMODE_MESSAGE;
+ if (!SetNamedPipeHandleState(pipe, &pipeMode, nullptr, nullptr)) {
+ LOG_NPIO_ERROR("[%p] SetNamedPipeHandleState error (%d)", 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] %d 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 (%d)", __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] %d bytes read", __func__, this, mReadEnd);
+ return mReadEnd;
+ }
+
+ switch (GetLastError()) {
+ case ERROR_MORE_DATA:
+ mHasPendingRead = false;
+ LOG_NPIO_DEBUG("[%s][%p] %d bytes read", __func__, this, mReadEnd);
+ return mReadEnd;
+ case ERROR_IO_INCOMPLETE: // still in progress
+ break;
+ default:
+ LOG_NPIO_ERROR("[%s]: GetOverlappedResult failed (%d)", __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] %d bytes written", __func__, this, bytesWritten);
+ return bytesWritten;
+ }
+
+ if (GetLastError() != ERROR_IO_PENDING) {
+ LOG_NPIO_ERROR("[%s] WriteFile failed (%d)", __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 (%d)", __func__,
+ GetLastError());
+ Disconnect();
+ PR_SetError(PR_IO_ERROR, 0);
+ return -1;
+ }
+
+ mHasPendingWrite = false;
+ mWriteBegin += bytesWritten;
+ LOG_NPIO_DEBUG("[%s][%p] %d 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..06be8562a8
--- /dev/null
+++ b/netwerk/socket/nsNamedPipeService.cpp
@@ -0,0 +1,318 @@
+/* -*- 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"
+
+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 (%d)", 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)", 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 (%d)", 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=%d", obs.get(),
+ bytesTransferred);
+ obs->OnDataAvailable(bytesTransferred, overlapped);
+ } else {
+ LOG_NPS_ERROR("GetQueuedCompletionStatus %p failed, error=%d", 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..5d6e5f86fe
--- /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;
+ 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..0a16d6c723
--- /dev/null
+++ b/netwerk/socket/nsSOCKSIOLayer.cpp
@@ -0,0 +1,1527 @@
+/* -*- 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 "nsISOCKSSocketInfo.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 nsISOCKSSocketInfo, 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_NSISOCKSSOCKETINFO
+ 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; }
+
+ 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;
+ uint8_t* mData;
+ uint8_t* mDataIoPtr;
+ uint32_t mDataLength;
+ uint32_t mReadOffset;
+ uint32_t mAmountToRead;
+ nsCOMPtr<nsIDNSRecord> mDnsRec;
+ nsCOMPtr<nsICancelable> mLookup;
+ nsresult mLookupStatus;
+ PRFileDesc* mFD;
+
+ nsCString mDestinationHost;
+ nsCOMPtr<nsIProxyInfo> mProxy;
+ int32_t mVersion; // SOCKS version 4 or 5
+ int32_t mDestinationFamily;
+ uint32_t mFlags;
+ uint32_t mTlsFlags;
+ NetAddr mInternalProxyAddr;
+ NetAddr mExternalProxyAddr;
+ NetAddr mDestinationAddr;
+ PRIntervalTime mTimeout;
+ nsCString mProxyUsername; // Cache, from mProxy
+};
+
+nsSOCKSSocketInfo::nsSOCKSSocketInfo()
+ : mState(SOCKS_INITIAL),
+ mDataIoPtr(nullptr),
+ mDataLength(0),
+ mReadOffset(0),
+ mAmountToRead(0),
+ mLookupStatus(NS_ERROR_NOT_INITIALIZED),
+ mFD(nullptr),
+ mVersion(-1),
+ mDestinationFamily(AF_INET),
+ mFlags(0),
+ mTlsFlags(0),
+ mTimeout(PR_INTERVAL_NO_TIMEOUT) {
+ this->mInternalProxyAddr.inet.family = 0;
+ this->mInternalProxyAddr.inet6.family = 0;
+ this->mInternalProxyAddr.inet6.port = 0;
+ this->mInternalProxyAddr.inet6.flowinfo = 0;
+ this->mInternalProxyAddr.inet6.scope_id = 0;
+ this->mInternalProxyAddr.local.family = 0;
+ this->mExternalProxyAddr.inet.family = 0;
+ this->mExternalProxyAddr.inet6.family = 0;
+ this->mExternalProxyAddr.inet6.port = 0;
+ this->mExternalProxyAddr.inet6.flowinfo = 0;
+ this->mExternalProxyAddr.inet6.scope_id = 0;
+ this->mExternalProxyAddr.local.family = 0;
+ this->mDestinationAddr.inet.family = 0;
+ this->mDestinationAddr.inet6.family = 0;
+ this->mDestinationAddr.inet6.port = 0;
+ this->mDestinationAddr.inet6.flowinfo = 0;
+ this->mDestinationAddr.inet6.scope_id = 0;
+ this->mDestinationAddr.local.family = 0;
+ 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() : mBuf(nullptr), mLength(0) {}
+
+ 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);
+ } else 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;
+ size_t mLength;
+};
+
+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, nsISOCKSSocketInfo, nsIDNSListener)
+
+NS_IMETHODIMP
+nsSOCKSSocketInfo::GetExternalProxyAddr(NetAddr** aExternalProxyAddr) {
+ memcpy(*aExternalProxyAddr, &mExternalProxyAddr, sizeof(NetAddr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSOCKSSocketInfo::SetExternalProxyAddr(NetAddr* aExternalProxyAddr) {
+ memcpy(&mExternalProxyAddr, aExternalProxyAddr, sizeof(NetAddr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSOCKSSocketInfo::GetDestinationAddr(NetAddr** aDestinationAddr) {
+ memcpy(*aDestinationAddr, &mDestinationAddr, sizeof(NetAddr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSOCKSSocketInfo::SetDestinationAddr(NetAddr* aDestinationAddr) {
+ memcpy(&mDestinationAddr, aDestinationAddr, sizeof(NetAddr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSOCKSSocketInfo::GetInternalProxyAddr(NetAddr** aInternalProxyAddr) {
+ memcpy(*aInternalProxyAddr, &mInternalProxyAddr, sizeof(NetAddr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSOCKSSocketInfo::SetInternalProxyAddr(NetAddr* aInternalProxyAddr) {
+ memcpy(&mInternalProxyAddr, aInternalProxyAddr, sizeof(NetAddr));
+ return NS_OK;
+}
+
+// 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::GetCurrentEventTarget(), 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;
+ } else 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();
+ } else if (!mProxyUsername.IsEmpty() && authMethod == 0x02) { // username/pw
+ LOGDEBUG(("socks5: auth method accepted by server"));
+ return WriteV5UsernameRequest();
+ } else { // 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;
+ } else 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;
+ NetAddr* tempPtr = &temp;
+ if (info->GetExternalProxyAddr(&tempPtr) == NS_OK) {
+ NetAddrToPRNetAddr(tempPtr, 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;
+ NetAddr* tempPtr = &temp;
+ if (info->GetDestinationAddr(&tempPtr) == NS_OK) {
+ NetAddrToPRNetAddr(tempPtr, 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,
+ nsISupports** info) {
+ 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;
+ }
+
+ *info = static_cast<nsISOCKSSocketInfo*>(infoObject);
+ NS_ADDREF(*info);
+ 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..5b82f3827d
--- /dev/null
+++ b/netwerk/socket/nsSOCKSIOLayer.h
@@ -0,0 +1,22 @@
+/* -*- 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,
+ nsISupports** info);
+
+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..536e06c962
--- /dev/null
+++ b/netwerk/socket/nsSOCKSSocketProvider.cpp
@@ -0,0 +1,119 @@
+/* -*- 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;
+
+//////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsSOCKSSocketProvider, nsISocketProvider)
+
+nsresult nsSOCKSSocketProvider::CreateV4(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ nsresult rv;
+ nsCOMPtr<nsISocketProvider> inst =
+ new nsSOCKSSocketProvider(NS_SOCKS_VERSION_4);
+ if (!inst)
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ else
+ rv = inst->QueryInterface(aIID, aResult);
+ return rv;
+}
+
+nsresult nsSOCKSSocketProvider::CreateV5(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ nsresult rv;
+ nsCOMPtr<nsISocketProvider> inst =
+ new nsSOCKSSocketProvider(NS_SOCKS_VERSION_5);
+ if (!inst)
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ else
+ rv = inst->QueryInterface(aIID, aResult);
+ return rv;
+}
+
+// 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, nsISupports** socksInfo) {
+ PRFileDesc* sock = OpenTCPSocket(family, proxy);
+ if (!sock) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsresult rv = nsSOCKSIOLayerAddToSocket(family, host, port, proxy, mVersion,
+ flags, tlsFlags, sock, socksInfo);
+ 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, nsISupports** socksInfo) {
+ nsresult rv = nsSOCKSIOLayerAddToSocket(family, host, port, proxy, mVersion,
+ flags, tlsFlags, sock, socksInfo);
+
+ 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..7dc34dae60
--- /dev/null
+++ b/netwerk/socket/nsSOCKSSocketProvider.h
@@ -0,0 +1,31 @@
+/* -*- 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) {}
+
+ static nsresult CreateV4(nsISupports*, REFNSIID aIID, void** aResult);
+ static nsresult CreateV5(nsISupports*, REFNSIID aIID, void** aResult);
+
+ 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..93a84991f3
--- /dev/null
+++ b/netwerk/socket/nsSocketProviderService.cpp
@@ -0,0 +1,70 @@
+/* -*- 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"
+
+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..c81a1614d5
--- /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,
+ nsISupports** aSecurityInfo) {
+ 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,
+ nsISupports** aSecurityInfo) {
+ // 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/srtp/src/LICENSE b/netwerk/srtp/src/LICENSE
new file mode 100644
index 0000000000..af0a2ac312
--- /dev/null
+++ b/netwerk/srtp/src/LICENSE
@@ -0,0 +1,35 @@
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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/srtp/src/README.md b/netwerk/srtp/src/README.md
new file mode 100644
index 0000000000..3f1e5bbfde
--- /dev/null
+++ b/netwerk/srtp/src/README.md
@@ -0,0 +1,487 @@
+[![Build Status](https://travis-ci.org/cisco/libsrtp.svg?branch=master)](https://travis-ci.org/cisco/libsrtp)
+[![Coverity Scan Build Status](https://scan.coverity.com/projects/14274/badge.svg)](https://scan.coverity.com/projects/cisco-libsrtp)
+
+<a name="introduction-to-libsrtp"></a>
+# Introduction to libSRTP
+
+This package provides an implementation of the Secure Real-time
+Transport Protocol (SRTP), the Universal Security Transform (UST), and
+a supporting cryptographic kernel. The SRTP API is documented in include/srtp.h,
+and the library is in libsrtp2.a (after compilation).
+
+This document describes libSRTP, the Open Source Secure RTP library
+from Cisco Systems, Inc. RTP is the Real-time Transport Protocol, an
+IETF standard for the transport of real-time data such as telephony,
+audio, and video, defined by [RFC 3550](https://www.ietf.org/rfc/rfc3550.txt).
+Secure RTP (SRTP) is an RTP profile for providing confidentiality to RTP data
+and authentication to the RTP header and payload. SRTP is an IETF Standard,
+defined in [RFC 3711](https://www.ietf.org/rfc/rfc3711.txt), and was developed
+in the IETF Audio/Video Transport (AVT) Working Group. This library supports
+all of the mandatory features of SRTP, but not all of the optional features. See
+the [Supported Features](#supported-features) section for more detailed information.
+
+This document is also used to generate the documentation files in the /doc/
+folder where a more detailed reference to the libSRTP API and related functions
+can be created (requires installing doxygen.). The reference material is created
+automatically from comments embedded in some of the C header files. The
+documentation is organized into modules in order to improve its clarity. These
+modules do not directly correspond to files. An underlying cryptographic kernel
+provides much of the basic functionality of libSRTP but is mostly undocumented
+because it does its work behind the scenes.
+
+--------------------------------------------------------------------------------
+
+<a name="contact"></a>
+# Contact Us
+
+- [libsrtp@lists.packetizer.com](mailto:libsrtp@lists.packetizer.com) general mailing list for news / announcements / discussions. This is an open list, see
+[https://lists.packetizer.com/mailman/listinfo/libsrtp](https://lists.packetizer.com/mailman/listinfo/libsrtp) for singing up.
+
+- [libsrtp-security@lists.packetizer.com](mailto:libsrtp-security@lists.packetizer.com) for disclosing security issues to the libsrtp maintenance team. This is a closed list but anyone can send to it.
+
+
+--------------------------------------------------------------------------------
+
+<a name="contents"></a>
+## Contents
+
+- [Introduction to libSRTP](#introduction-to-libsrtp)
+ - [Contact Us](#contact)
+ - [Contents](#contents)
+- [License and Disclaimer](#license-and-disclaimer)
+- [libSRTP Overview](#libsrtp-overview)
+ - [Secure RTP Background](#secure-rtp-background)
+ - [Supported Features](#supported-features)
+ - [Implementation Notes](#implementation-notes)
+- [Installing and Building libSRTP](#installing-and-building-libsrtp)
+ - [Changing Build Configuration](#changing-build-configuration)
+- [Applications](#applications)
+ - [Example Code](#example-code)
+- [Credits](#credits)
+- [References](#references)
+
+--------------------------------------------------------------------------------
+
+<a name="license-and-disclaimer"></a>
+# License and Disclaimer
+
+libSRTP is distributed under the following license, which is included
+in the source code distribution. It is reproduced in the manual in
+case you got the library from another source.
+
+> Copyright (c) 2001-2017 Cisco Systems, Inc. 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 the 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 HOLDERS 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.
+
+--------------------------------------------------------------------------------
+
+<a name="libsrtp-overview"></a>
+# libSRTP Overview
+
+libSRTP provides functions for protecting RTP and RTCP. RTP packets
+can be encrypted and authenticated (using the `srtp_protect()`
+function), turning them into SRTP packets. Similarly, SRTP packets
+can be decrypted and have their authentication verified (using the
+`srtp_unprotect()` function), turning them into RTP packets. Similar
+functions apply security to RTCP packets.
+
+The typedef `srtp_stream_t` points to a structure holding all of the
+state associated with an SRTP stream, including the keys and
+parameters for cipher and message authentication functions and the
+anti-replay data. A particular `srtp_stream_t` holds the information
+needed to protect a particular RTP and RTCP stream. This datatype
+is intentionally opaque in order to better seperate the libSRTP
+API from its implementation.
+
+Within an SRTP session, there can be multiple streams, each
+originating from a particular sender. Each source uses a distinct
+stream context to protect the RTP and RTCP stream that it is
+originating. The typedef `srtp_t` points to a structure holding all of
+the state associated with an SRTP session. There can be multiple
+stream contexts associated with a single `srtp_t`. A stream context
+cannot exist indepent from an `srtp_t`, though of course an `srtp_t` can
+be created that contains only a single stream context. A device
+participating in an SRTP session must have a stream context for each
+source in that session, so that it can process the data that it
+receives from each sender.
+
+In libSRTP, a session is created using the function `srtp_create()`.
+The policy to be implemented in the session is passed into this
+function as an `srtp_policy_t` structure. A single one of these
+structures describes the policy of a single stream. These structures
+can also be linked together to form an entire session policy. A linked
+list of `srtp_policy_t` structures is equivalent to a session policy.
+In such a policy, we refer to a single `srtp_policy_t` as an *element*.
+
+An `srtp_policy_t` strucutre contains two `crypto_policy_t` structures
+that describe the cryptograhic policies for RTP and RTCP, as well as
+the SRTP master key and the SSRC value. The SSRC describes what to
+protect (e.g. which stream), and the `crypto_policy_t` structures
+describe how to protect it. The key is contained in a policy element
+because it simplifies the interface to the library. In many cases, it
+is desirable to use the same cryptographic policies across all of the
+streams in a session, but to use a distinct key for each stream. A
+`crypto_policy_t` structure can be initialized by using either the
+`crypto_policy_set_rtp_default()` or `crypto_policy_set_rtcp_default()`
+functions, which set a crypto policy structure to the default policies
+for RTP and RTCP protection, respectively.
+
+--------------------------------------------------------------------------------
+
+<a name="secure-rtp-background"></a>
+## Secure RTP Background
+
+In this section we review SRTP and introduce some terms that are used
+in libSRTP. An RTP session is defined by a pair of destination
+transport addresses, that is, a network address plus a pair of UDP
+ports for RTP and RTCP. RTCP, the RTP control protocol, is used to
+coordinate between the participants in an RTP session, e.g. to provide
+feedback from receivers to senders. An *SRTP session* is
+similarly defined; it is just an RTP session for which the SRTP
+profile is being used. An SRTP session consists of the traffic sent
+to the SRTP or SRTCP destination transport addresses. Each
+participant in a session is identified by a synchronization source
+(SSRC) identifier. Some participants may not send any SRTP traffic;
+they are called receivers, even though they send out SRTCP traffic,
+such as receiver reports.
+
+RTP allows multiple sources to send RTP and RTCP traffic during the
+same session. The synchronization source identifier (SSRC) is used to
+distinguish these sources. In libSRTP, we call the SRTP and SRTCP
+traffic from a particular source a *stream*. Each stream has its own
+SSRC, sequence number, rollover counter, and other data. A particular
+choice of options, cryptographic mechanisms, and keys is called a
+*policy*. Each stream within a session can have a distinct policy
+applied to it. A session policy is a collection of stream policies.
+
+A single policy can be used for all of the streams in a given session,
+though the case in which a single *key* is shared across multiple
+streams requires care. When key sharing is used, the SSRC values that
+identify the streams **must** be distinct. This requirement can be
+enforced by using the convention that each SRTP and SRTCP key is used
+for encryption by only a single sender. In other words, the key is
+shared only across streams that originate from a particular device (of
+course, other SRTP participants will need to use the key for
+decryption). libSRTP supports this enforcement by detecting the case
+in which a key is used for both inbound and outbound data.
+
+--------------------------------------------------------------------------------
+
+<a name="supported-features"></a>
+## Supported Features
+
+This library supports all of the mandatory-to-implement features of
+SRTP (as defined in [RFC 3711](https://www.ietf.org/rfc/rfc3711.txt)). Some of these
+features can be selected (or de-selected) at run time by setting an
+appropriate policy; this is done using the structure `srtp_policy_t`.
+Some other behaviors of the protocol can be adapted by defining an
+approriate event handler for the exceptional events; see the SRTPevents
+section in the generated documentation.
+
+Some options that are described in the SRTP specification are not
+supported. This includes
+
+- key derivation rates other than zero,
+- the cipher F8,
+- the use of the packet index to select between master keys.
+
+The user should be aware that it is possible to misuse this libary,
+and that the result may be that the security level it provides is
+inadequate. If you are implementing a feature using this library, you
+will want to read the Security Considerations section of [RFC 3711](https://www.ietf.org/rfc/rfc3711.txt).
+In addition, it is important that you read and understand the
+terms outlined in the [License and Disclaimer](#license-and-disclaimer) section.
+
+--------------------------------------------------------------------------------
+
+<a name="implementation-notes"></a>
+## Implementation Notes
+
+ * The `srtp_protect()` function assumes that the buffer holding the
+ rtp packet has enough storage allocated that the authentication
+ tag can be written to the end of that packet. If this assumption
+ is not valid, memory corruption will ensue.
+
+ * Automated tests for the crypto functions are provided through
+ the `cipher_type_self_test()` and `auth_type_self_test()` functions.
+ These functions should be used to test each port of this code
+ to a new platform.
+
+ * Replay protection is contained in the crypto engine, and
+ tests for it are provided.
+
+ * This implementation provides calls to initialize, protect, and
+ unprotect RTP packets, and makes as few as possible assumptions
+ about how these functions will be called. For example, the
+ caller is not expected to provide packets in order (though if
+ they're called more than 65k out of sequence, synchronization
+ will be lost).
+
+ * The sequence number in the rtp packet is used as the low 16 bits
+ of the sender's local packet index. Note that RTP will start its
+ sequence number in a random place, and the SRTP layer just jumps
+ forward to that number at its first invocation. An earlier
+ version of this library used initial sequence numbers that are
+ less than 32,768; this trick is no longer required as the
+ `rdbx_estimate_index(...)` function has been made smarter.
+
+ * The replay window for (S)RTCP is hardcoded to 128 bits in length.
+
+--------------------------------------------------------------------------------
+
+<a name="installing-and-building-libsrtp"></a>
+# Installing and Building libSRTP
+
+To install libSRTP, download the latest release of the distribution
+from [https://github.com/cisco/libsrtp/releases](https://github.com/cisco/libsrtp/releases).
+You probably want to get the most recent release. Unpack the distribution and
+extract the source files; the directory into which the source files
+will go is named `libsrtp-A-B-C` where `A` is the version number, `B` is the
+major release number and `C` is the minor release number.
+
+libSRTP uses the GNU `autoconf` and `make` utilities (BSD make will not work; if
+both versions of make are on your platform, you can invoke GNU make as
+`gmake`.). In the `libsrtp` directory, run the configure script and then
+make:
+
+~~~.txt
+./configure [ options ]
+make
+~~~
+
+The configure script accepts the following options:
+
+Option | Description
+-------------------------------|--------------------
+\-\-help \-h | Display help
+\-\-enable-debug-logging | Enable debug logging in all modules
+\-\-enable-log-stdout | Enable logging to stdout
+\-\-enable-openssl | Enable OpenSSL crypto engine
+\-\-enable-openssl-kdf | Enable OpenSSL KDF algorithm
+\-\-with-log-file | Use file for logging
+\-\-with-openssl-dir | Location of OpenSSL installation
+
+By default there is no log output, logging can be enabled to be output to stdout
+or a given file using the configure options.
+
+This package has been tested on the following platforms: Mac OS X
+(powerpc-apple-darwin1.4), Cygwin (i686-pc-cygwin), Solaris
+(sparc-sun-solaris2.6), RedHat Linux 7.1 and 9 (i686-pc-linux), and
+OpenBSD (sparc-unknown-openbsd2.7).
+
+--------------------------------------------------------------------------------
+
+<a name="changing-build-configuration"></a>
+## Changing Build Configuration
+
+To build the `./configure` script mentioned above, libSRTP relies on the
+[automake](https://www.gnu.org/software/automake/) toolchain. Since
+`./configure` is built from `configure.in` by automake, if you make changes in
+how `./configure` works (e.g., to add a new library dependency), you will need
+to rebuild `./configure` and commit the updated version. In addition to
+automake itself, you will need to have the `pkgconfig` tools installed as well.
+
+For example, on macOS:
+
+```
+brew install automake pkgconfig
+# Edit configure.in
+autoremake -ivf
+```
+
+--------------------------------------------------------------------------------
+
+<a name="applications"></a>
+# Applications
+
+Several test drivers and a simple and portable srtp application are
+included in the `test/` subdirectory.
+
+Test driver | Function tested
+--------- | -------
+kernel_driver | crypto kernel (ciphers, auth funcs, rng)
+srtp_driver | srtp in-memory tests (does not use the network)
+rdbx_driver | rdbx (extended replay database)
+roc_driver | extended sequence number functions
+replay_driver | replay database
+cipher_driver | ciphers
+auth_driver | hash functions
+
+The app `rtpw` is a simple rtp application which reads words from
+`/usr/dict/words` and then sends them out one at a time using [s]rtp.
+Manual srtp keying uses the -k option; automated key management
+using gdoi will be added later.
+
+usage:
+~~~.txt
+rtpw [[-d <debug>]* [-k|b <key> [-a][-e <key size>][-g]] [-s | -r] dest_ip dest_port] | [-l]
+~~~
+
+Either the -s (sender) or -r (receiver) option must be chosen. The
+values `dest_ip`, `dest_port` are the IP address and UDP port to which
+the dictionary will be sent, respectively.
+
+The options are:
+
+Option | Description
+--------- | -------
+ -s | (S)RTP sender - causes app to send words
+ -r | (S)RTP receive - causes app to receive words
+ -k <key> | use SRTP master key <key>, where the key is a hexadecimal (without the leading "0x")
+ -b <key> | same as -k but with base64 encoded key
+ -e <keysize> | encrypt/decrypt (for data confidentiality) (requires use of -k option as well) (use 128, 192, or 256 for keysize)
+ -g | use AES-GCM mode (must be used with -e)
+ -a | message authentication (requires use of -k option as well)
+ -l | list the available debug modules
+ -d <debug> | turn on debugging for module <debug>
+
+In order to get random 30-byte values for use as key/salt pairs , you
+can use the following bash function to format the output of
+`/dev/random` (where that device is available).
+
+~~~.txt
+function randhex() {
+ cat /dev/random | od --read-bytes=32 --width=32 -x | awk '{ print $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15 $16 }'
+}
+~~~
+
+An example of an SRTP session using two rtpw programs follows:
+
+~~~.txt
+set k=c1eec3717da76195bb878578790af71c4ee9f859e197a414a78d5abc7451
+
+[sh1]$ test/rtpw -s -k $k -e 128 -a 0.0.0.0 9999
+Security services: confidentiality message authentication
+set master key/salt to C1EEC3717DA76195BB878578790AF71C/4EE9F859E197A414A78D5ABC7451
+setting SSRC to 2078917053
+sending word: A
+sending word: a
+sending word: aa
+sending word: aal
+...
+
+[sh2]$ test/rtpw -r -k $k -e 128 -a 0.0.0.0 9999
+security services: confidentiality message authentication
+set master key/salt to C1EEC3717DA76195BB878578790AF71C/4EE9F859E197A414A78D5ABC7451
+19 octets received from SSRC 2078917053 word: A
+19 octets received from SSRC 2078917053 word: a
+20 octets received from SSRC 2078917053 word: aa
+21 octets received from SSRC 2078917053 word: aal
+...
+~~~
+
+--------------------------------------------------------------------------------
+
+<a name="example-code"></a>
+## Example Code
+
+This section provides a simple example of how to use libSRTP. The
+example code lacks error checking, but is functional. Here we assume
+that the value ssrc is already set to describe the SSRC of the stream
+that we are sending, and that the functions `get_rtp_packet()` and
+`send_srtp_packet()` are available to us. The former puts an RTP packet
+into the buffer and returns the number of octets written to that
+buffer. The latter sends the RTP packet in the buffer, given the
+length as its second argument.
+
+~~~.c
+srtp_t session;
+srtp_policy_t policy;
+
+// Set key to predetermined value
+uint8_t key[30] = {0x00, 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};
+
+// initialize libSRTP
+srtp_init();
+
+// default policy values
+memset(&policy, 0x0, sizeof(srtp_policy_t));
+
+// set policy to describe a policy for an SRTP stream
+crypto_policy_set_rtp_default(&policy.rtp);
+crypto_policy_set_rtcp_default(&policy.rtcp);
+policy.ssrc = ssrc;
+policy.key = key;
+policy.next = NULL;
+
+// allocate and initialize the SRTP session
+srtp_create(&session, &policy);
+
+// main loop: get rtp packets, send srtp packets
+while (1) {
+ char rtp_buffer[2048];
+ unsigned len;
+
+ len = get_rtp_packet(rtp_buffer);
+ srtp_protect(session, rtp_buffer, &len);
+ send_srtp_packet(rtp_buffer, len);
+}
+~~~
+
+--------------------------------------------------------------------------------
+
+<a name="credits"></a>
+# Credits
+
+The original implementation and documentation of libSRTP was written
+by David McGrew of Cisco Systems, Inc. in order to promote the use,
+understanding, and interoperability of Secure RTP. Michael Jerris
+contributed support for building under MSVC. Andris Pavenis
+contributed many important fixes. Brian West contributed changes to
+enable dynamic linking. Yves Shumann reported documentation bugs.
+Randell Jesup contributed a working SRTCP implementation and other
+fixes. Steve Underwood contributed x86_64 portability changes. We also give
+thanks to Fredrik Thulin, Brian Weis, Mark Baugher, Jeff Chan, Bill
+Simon, Douglas Smith, Bill May, Richard Preistley, Joe Tardo and
+others for contributions, comments, and corrections.
+
+This reference material, when applicable, in this documenation was generated
+using the doxygen utility for automatic documentation of source code.
+
+Copyright 2001-2005 by David A. McGrew, Cisco Systems, Inc.
+
+--------------------------------------------------------------------------------
+
+<a name="references"></a>
+# References
+
+SRTP and ICM References
+September, 2005
+
+Secure RTP is defined in [RFC 3711](https://www.ietf.org/rfc/rfc3711.txt).
+The counter mode definition is in Section 4.1.1.
+
+SHA-1 is defined in [FIPS PUB 180-4](http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).
+
+HMAC is defined in [RFC 2104](https://www.ietf.org/rfc/rfc2104.txt)
+and HMAC-SHA1 test vectors are available
+in [RFC 2202](https://www.ietf.org/rfc/rfc2202.txt).
+
+AES-GCM usage in SRTP is defined in [RFC 7714](https://www.ietf.org/html/rfc7714)
diff --git a/netwerk/srtp/src/VERSION b/netwerk/srtp/src/VERSION
new file mode 100644
index 0000000000..5432261487
--- /dev/null
+++ b/netwerk/srtp/src/VERSION
@@ -0,0 +1 @@
+2.2.0-pre
diff --git a/netwerk/srtp/src/crypto/Makefile.in b/netwerk/srtp/src/crypto/Makefile.in
new file mode 100644
index 0000000000..44f29ad2cf
--- /dev/null
+++ b/netwerk/srtp/src/crypto/Makefile.in
@@ -0,0 +1,119 @@
+# Makefile for crypto test suite
+#
+# David A. McGrew
+# Cisco Systems, Inc.
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+top_builddir = @top_builddir@
+VPATH = @srcdir@
+
+CC = @CC@
+INCDIR = -Iinclude -I$(srcdir)/include -I$(top_srcdir)/include
+DEFS = @DEFS@
+CPPFLAGS= @CPPFLAGS@
+CFLAGS = @CFLAGS@
+LIBS = @LIBS@
+LDFLAGS = @LDFLAGS@ -L. -L..
+COMPILE = $(CC) $(DEFS) $(INCDIR) $(CPPFLAGS) $(CFLAGS)
+CRYPTOLIB = -lsrtp2
+CRYPTO_LIBDIR = @CRYPTO_LIBDIR@
+
+RANLIB = @RANLIB@
+
+# Specify how tests should find shared libraries on macOS and Linux
+#
+# macOS purges DYLD_LIBRARY_PATH when spawning subprocesses, so it's
+# not possible to pass this in from the outside; we have to specify
+# it for any subprocesses we call. No support for dynamic linked
+# tests on Windows.
+ifneq ($(strip $(CRYPTO_LIBDIR)),)
+ ifneq ($(OS),Windows_NT)
+ UNAME_S = $(shell uname -s)
+ ifeq ($(UNAME_S),Linux)
+ FIND_LIBRARIES = LD_LIBRARY_PATH=$(CRYPTO_LIBDIR)
+ endif
+ ifeq ($(UNAME_S),Darwin)
+ FIND_LIBRARIES = DYLD_LIBRARY_PATH=$(CRYPTO_LIBDIR)
+ endif
+ endif
+endif
+
+# EXE defines the suffix on executables - it's .exe for cygwin, and
+# null on linux, bsd, and OS X and other OSes. we define this so that
+# `make clean` will work on the cygwin platform
+EXE = @EXE@
+# Random source.
+USE_EXTERNAL_CRYPTO = @USE_EXTERNAL_CRYPTO@
+
+ifdef ARCH
+ DEFS += -D$(ARCH)=1
+endif
+
+ifdef sysname
+ DEFS += -D$(sysname)=1
+endif
+
+.PHONY: dummy all runtest clean superclean
+
+dummy : all runtest
+
+# test applications
+ifneq (1, $(USE_EXTERNAL_CRYPTO))
+AES_CALC = test/aes_calc$(EXE)
+endif
+
+testapp = test/cipher_driver$(EXE) test/datatypes_driver$(EXE) \
+ test/stat_driver$(EXE) test/sha1_driver$(EXE) \
+ test/kernel_driver$(EXE) $(AES_CALC) \
+ test/env$(EXE)
+
+# data values used to test the aes_calc application for AES-128
+k128=000102030405060708090a0b0c0d0e0f
+p128=00112233445566778899aabbccddeeff
+c128=69c4e0d86a7b0430d8cdb78070b4c55a
+
+
+# data values used to test the aes_calc application for AES-256
+k256=000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
+p256=00112233445566778899aabbccddeeff
+c256=8ea2b7ca516745bfeafc49904b496089
+
+
+runtest: $(testapp)
+ $(FIND_LIBRARIES) test/env$(EXE) # print out information on the build environment
+ @echo "running crypto test applications..."
+ifneq (1, $(USE_EXTERNAL_CRYPTO))
+ $(FIND_LIBRARIES) test `test/aes_calc $(k128) $(p128)` = $(c128)
+ $(FIND_LIBRARIES) test `test/aes_calc $(k256) $(p256)` = $(c256)
+endif
+ $(FIND_LIBRARIES) test/cipher_driver$(EXE) -v >/dev/null
+ $(FIND_LIBRARIES) test/datatypes_driver$(EXE) -v >/dev/null
+ $(FIND_LIBRARIES) test/stat_driver$(EXE) >/dev/null
+ $(FIND_LIBRARIES) test/sha1_driver$(EXE) -v >/dev/null
+ $(FIND_LIBRARIES) test/kernel_driver$(EXE) -v >/dev/null
+ @echo "crypto test applications passed."
+
+
+# the rule for making object files and test apps
+
+%.o: %.c
+ $(COMPILE) -c $< -o $@
+
+%$(EXE): %.c $(srcdir)/../test/getopt_s.c
+ $(COMPILE) $(LDFLAGS) $< $(srcdir)/../test/getopt_s.c -o $@ $(CRYPTOLIB) $(LIBS)
+
+all: $(testapp)
+
+# housekeeping functions
+
+clean:
+ rm -f $(testapp) *.o */*.o
+ for a in * .* */*; do if [ -f "$$a~" ] ; then rm $$a~; fi; done;
+ rm -f `find . -name "*.[ch]~*~"`
+ rm -rf latex
+
+superclean: clean
+ rm -f *core TAGS ktrace.out
+
+# EOF
diff --git a/netwerk/srtp/src/crypto/cipher/aes.c b/netwerk/srtp/src/crypto/cipher/aes.c
new file mode 100644
index 0000000000..c9cd774201
--- /dev/null
+++ b/netwerk/srtp/src/crypto/cipher/aes.c
@@ -0,0 +1,2189 @@
+/*
+ * aes.c
+ *
+ * An implemnetation of the AES block cipher.
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "aes.h"
+#include "err.h"
+
+/*
+ * we use the tables T0, T1, T2, T3, and T4 to compute AES, and
+ * the tables U0, U1, U2, and U4 to compute its inverse
+ *
+ * different tables are used on little-endian (Intel, VMS) and
+ * big-endian processors (everything else)
+ *
+ * these tables are computed using the program tables/aes_tables; use
+ * this program to generate different tables for porting or
+ * optimization on a different platform
+ */
+
+#ifndef WORDS_BIGENDIAN
+/* clang-format off */
+static const uint32_t T0[256] = {
+ 0xa56363c6, 0x847c7cf8, 0x997777ee, 0x8d7b7bf6,
+ 0xdf2f2ff, 0xbd6b6bd6, 0xb16f6fde, 0x54c5c591,
+ 0x50303060, 0x3010102, 0xa96767ce, 0x7d2b2b56,
+ 0x19fefee7, 0x62d7d7b5, 0xe6abab4d, 0x9a7676ec,
+ 0x45caca8f, 0x9d82821f, 0x40c9c989, 0x877d7dfa,
+ 0x15fafaef, 0xeb5959b2, 0xc947478e, 0xbf0f0fb,
+ 0xecadad41, 0x67d4d4b3, 0xfda2a25f, 0xeaafaf45,
+ 0xbf9c9c23, 0xf7a4a453, 0x967272e4, 0x5bc0c09b,
+ 0xc2b7b775, 0x1cfdfde1, 0xae93933d, 0x6a26264c,
+ 0x5a36366c, 0x413f3f7e, 0x2f7f7f5, 0x4fcccc83,
+ 0x5c343468, 0xf4a5a551, 0x34e5e5d1, 0x8f1f1f9,
+ 0x937171e2, 0x73d8d8ab, 0x53313162, 0x3f15152a,
+ 0xc040408, 0x52c7c795, 0x65232346, 0x5ec3c39d,
+ 0x28181830, 0xa1969637, 0xf05050a, 0xb59a9a2f,
+ 0x907070e, 0x36121224, 0x9b80801b, 0x3de2e2df,
+ 0x26ebebcd, 0x6927274e, 0xcdb2b27f, 0x9f7575ea,
+ 0x1b090912, 0x9e83831d, 0x742c2c58, 0x2e1a1a34,
+ 0x2d1b1b36, 0xb26e6edc, 0xee5a5ab4, 0xfba0a05b,
+ 0xf65252a4, 0x4d3b3b76, 0x61d6d6b7, 0xceb3b37d,
+ 0x7b292952, 0x3ee3e3dd, 0x712f2f5e, 0x97848413,
+ 0xf55353a6, 0x68d1d1b9, 0x0, 0x2cededc1,
+ 0x60202040, 0x1ffcfce3, 0xc8b1b179, 0xed5b5bb6,
+ 0xbe6a6ad4, 0x46cbcb8d, 0xd9bebe67, 0x4b393972,
+ 0xde4a4a94, 0xd44c4c98, 0xe85858b0, 0x4acfcf85,
+ 0x6bd0d0bb, 0x2aefefc5, 0xe5aaaa4f, 0x16fbfbed,
+ 0xc5434386, 0xd74d4d9a, 0x55333366, 0x94858511,
+ 0xcf45458a, 0x10f9f9e9, 0x6020204, 0x817f7ffe,
+ 0xf05050a0, 0x443c3c78, 0xba9f9f25, 0xe3a8a84b,
+ 0xf35151a2, 0xfea3a35d, 0xc0404080, 0x8a8f8f05,
+ 0xad92923f, 0xbc9d9d21, 0x48383870, 0x4f5f5f1,
+ 0xdfbcbc63, 0xc1b6b677, 0x75dadaaf, 0x63212142,
+ 0x30101020, 0x1affffe5, 0xef3f3fd, 0x6dd2d2bf,
+ 0x4ccdcd81, 0x140c0c18, 0x35131326, 0x2fececc3,
+ 0xe15f5fbe, 0xa2979735, 0xcc444488, 0x3917172e,
+ 0x57c4c493, 0xf2a7a755, 0x827e7efc, 0x473d3d7a,
+ 0xac6464c8, 0xe75d5dba, 0x2b191932, 0x957373e6,
+ 0xa06060c0, 0x98818119, 0xd14f4f9e, 0x7fdcdca3,
+ 0x66222244, 0x7e2a2a54, 0xab90903b, 0x8388880b,
+ 0xca46468c, 0x29eeeec7, 0xd3b8b86b, 0x3c141428,
+ 0x79dedea7, 0xe25e5ebc, 0x1d0b0b16, 0x76dbdbad,
+ 0x3be0e0db, 0x56323264, 0x4e3a3a74, 0x1e0a0a14,
+ 0xdb494992, 0xa06060c, 0x6c242448, 0xe45c5cb8,
+ 0x5dc2c29f, 0x6ed3d3bd, 0xefacac43, 0xa66262c4,
+ 0xa8919139, 0xa4959531, 0x37e4e4d3, 0x8b7979f2,
+ 0x32e7e7d5, 0x43c8c88b, 0x5937376e, 0xb76d6dda,
+ 0x8c8d8d01, 0x64d5d5b1, 0xd24e4e9c, 0xe0a9a949,
+ 0xb46c6cd8, 0xfa5656ac, 0x7f4f4f3, 0x25eaeacf,
+ 0xaf6565ca, 0x8e7a7af4, 0xe9aeae47, 0x18080810,
+ 0xd5baba6f, 0x887878f0, 0x6f25254a, 0x722e2e5c,
+ 0x241c1c38, 0xf1a6a657, 0xc7b4b473, 0x51c6c697,
+ 0x23e8e8cb, 0x7cdddda1, 0x9c7474e8, 0x211f1f3e,
+ 0xdd4b4b96, 0xdcbdbd61, 0x868b8b0d, 0x858a8a0f,
+ 0x907070e0, 0x423e3e7c, 0xc4b5b571, 0xaa6666cc,
+ 0xd8484890, 0x5030306, 0x1f6f6f7, 0x120e0e1c,
+ 0xa36161c2, 0x5f35356a, 0xf95757ae, 0xd0b9b969,
+ 0x91868617, 0x58c1c199, 0x271d1d3a, 0xb99e9e27,
+ 0x38e1e1d9, 0x13f8f8eb, 0xb398982b, 0x33111122,
+ 0xbb6969d2, 0x70d9d9a9, 0x898e8e07, 0xa7949433,
+ 0xb69b9b2d, 0x221e1e3c, 0x92878715, 0x20e9e9c9,
+ 0x49cece87, 0xff5555aa, 0x78282850, 0x7adfdfa5,
+ 0x8f8c8c03, 0xf8a1a159, 0x80898909, 0x170d0d1a,
+ 0xdabfbf65, 0x31e6e6d7, 0xc6424284, 0xb86868d0,
+ 0xc3414182, 0xb0999929, 0x772d2d5a, 0x110f0f1e,
+ 0xcbb0b07b, 0xfc5454a8, 0xd6bbbb6d, 0x3a16162c,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t T1[256] = {
+ 0x6363c6a5, 0x7c7cf884, 0x7777ee99, 0x7b7bf68d,
+ 0xf2f2ff0d, 0x6b6bd6bd, 0x6f6fdeb1, 0xc5c59154,
+ 0x30306050, 0x1010203, 0x6767cea9, 0x2b2b567d,
+ 0xfefee719, 0xd7d7b562, 0xabab4de6, 0x7676ec9a,
+ 0xcaca8f45, 0x82821f9d, 0xc9c98940, 0x7d7dfa87,
+ 0xfafaef15, 0x5959b2eb, 0x47478ec9, 0xf0f0fb0b,
+ 0xadad41ec, 0xd4d4b367, 0xa2a25ffd, 0xafaf45ea,
+ 0x9c9c23bf, 0xa4a453f7, 0x7272e496, 0xc0c09b5b,
+ 0xb7b775c2, 0xfdfde11c, 0x93933dae, 0x26264c6a,
+ 0x36366c5a, 0x3f3f7e41, 0xf7f7f502, 0xcccc834f,
+ 0x3434685c, 0xa5a551f4, 0xe5e5d134, 0xf1f1f908,
+ 0x7171e293, 0xd8d8ab73, 0x31316253, 0x15152a3f,
+ 0x404080c, 0xc7c79552, 0x23234665, 0xc3c39d5e,
+ 0x18183028, 0x969637a1, 0x5050a0f, 0x9a9a2fb5,
+ 0x7070e09, 0x12122436, 0x80801b9b, 0xe2e2df3d,
+ 0xebebcd26, 0x27274e69, 0xb2b27fcd, 0x7575ea9f,
+ 0x909121b, 0x83831d9e, 0x2c2c5874, 0x1a1a342e,
+ 0x1b1b362d, 0x6e6edcb2, 0x5a5ab4ee, 0xa0a05bfb,
+ 0x5252a4f6, 0x3b3b764d, 0xd6d6b761, 0xb3b37dce,
+ 0x2929527b, 0xe3e3dd3e, 0x2f2f5e71, 0x84841397,
+ 0x5353a6f5, 0xd1d1b968, 0x00000000, 0xededc12c,
+ 0x20204060, 0xfcfce31f, 0xb1b179c8, 0x5b5bb6ed,
+ 0x6a6ad4be, 0xcbcb8d46, 0xbebe67d9, 0x3939724b,
+ 0x4a4a94de, 0x4c4c98d4, 0x5858b0e8, 0xcfcf854a,
+ 0xd0d0bb6b, 0xefefc52a, 0xaaaa4fe5, 0xfbfbed16,
+ 0x434386c5, 0x4d4d9ad7, 0x33336655, 0x85851194,
+ 0x45458acf, 0xf9f9e910, 0x2020406, 0x7f7ffe81,
+ 0x5050a0f0, 0x3c3c7844, 0x9f9f25ba, 0xa8a84be3,
+ 0x5151a2f3, 0xa3a35dfe, 0x404080c0, 0x8f8f058a,
+ 0x92923fad, 0x9d9d21bc, 0x38387048, 0xf5f5f104,
+ 0xbcbc63df, 0xb6b677c1, 0xdadaaf75, 0x21214263,
+ 0x10102030, 0xffffe51a, 0xf3f3fd0e, 0xd2d2bf6d,
+ 0xcdcd814c, 0xc0c1814, 0x13132635, 0xececc32f,
+ 0x5f5fbee1, 0x979735a2, 0x444488cc, 0x17172e39,
+ 0xc4c49357, 0xa7a755f2, 0x7e7efc82, 0x3d3d7a47,
+ 0x6464c8ac, 0x5d5dbae7, 0x1919322b, 0x7373e695,
+ 0x6060c0a0, 0x81811998, 0x4f4f9ed1, 0xdcdca37f,
+ 0x22224466, 0x2a2a547e, 0x90903bab, 0x88880b83,
+ 0x46468cca, 0xeeeec729, 0xb8b86bd3, 0x1414283c,
+ 0xdedea779, 0x5e5ebce2, 0xb0b161d, 0xdbdbad76,
+ 0xe0e0db3b, 0x32326456, 0x3a3a744e, 0xa0a141e,
+ 0x494992db, 0x6060c0a, 0x2424486c, 0x5c5cb8e4,
+ 0xc2c29f5d, 0xd3d3bd6e, 0xacac43ef, 0x6262c4a6,
+ 0x919139a8, 0x959531a4, 0xe4e4d337, 0x7979f28b,
+ 0xe7e7d532, 0xc8c88b43, 0x37376e59, 0x6d6ddab7,
+ 0x8d8d018c, 0xd5d5b164, 0x4e4e9cd2, 0xa9a949e0,
+ 0x6c6cd8b4, 0x5656acfa, 0xf4f4f307, 0xeaeacf25,
+ 0x6565caaf, 0x7a7af48e, 0xaeae47e9, 0x8081018,
+ 0xbaba6fd5, 0x7878f088, 0x25254a6f, 0x2e2e5c72,
+ 0x1c1c3824, 0xa6a657f1, 0xb4b473c7, 0xc6c69751,
+ 0xe8e8cb23, 0xdddda17c, 0x7474e89c, 0x1f1f3e21,
+ 0x4b4b96dd, 0xbdbd61dc, 0x8b8b0d86, 0x8a8a0f85,
+ 0x7070e090, 0x3e3e7c42, 0xb5b571c4, 0x6666ccaa,
+ 0x484890d8, 0x3030605, 0xf6f6f701, 0xe0e1c12,
+ 0x6161c2a3, 0x35356a5f, 0x5757aef9, 0xb9b969d0,
+ 0x86861791, 0xc1c19958, 0x1d1d3a27, 0x9e9e27b9,
+ 0xe1e1d938, 0xf8f8eb13, 0x98982bb3, 0x11112233,
+ 0x6969d2bb, 0xd9d9a970, 0x8e8e0789, 0x949433a7,
+ 0x9b9b2db6, 0x1e1e3c22, 0x87871592, 0xe9e9c920,
+ 0xcece8749, 0x5555aaff, 0x28285078, 0xdfdfa57a,
+ 0x8c8c038f, 0xa1a159f8, 0x89890980, 0xd0d1a17,
+ 0xbfbf65da, 0xe6e6d731, 0x424284c6, 0x6868d0b8,
+ 0x414182c3, 0x999929b0, 0x2d2d5a77, 0xf0f1e11,
+ 0xb0b07bcb, 0x5454a8fc, 0xbbbb6dd6, 0x16162c3a,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t T2[256] = {
+ 0x63c6a563, 0x7cf8847c, 0x77ee9977, 0x7bf68d7b,
+ 0xf2ff0df2, 0x6bd6bd6b, 0x6fdeb16f, 0xc59154c5,
+ 0x30605030, 0x1020301, 0x67cea967, 0x2b567d2b,
+ 0xfee719fe, 0xd7b562d7, 0xab4de6ab, 0x76ec9a76,
+ 0xca8f45ca, 0x821f9d82, 0xc98940c9, 0x7dfa877d,
+ 0xfaef15fa, 0x59b2eb59, 0x478ec947, 0xf0fb0bf0,
+ 0xad41ecad, 0xd4b367d4, 0xa25ffda2, 0xaf45eaaf,
+ 0x9c23bf9c, 0xa453f7a4, 0x72e49672, 0xc09b5bc0,
+ 0xb775c2b7, 0xfde11cfd, 0x933dae93, 0x264c6a26,
+ 0x366c5a36, 0x3f7e413f, 0xf7f502f7, 0xcc834fcc,
+ 0x34685c34, 0xa551f4a5, 0xe5d134e5, 0xf1f908f1,
+ 0x71e29371, 0xd8ab73d8, 0x31625331, 0x152a3f15,
+ 0x4080c04, 0xc79552c7, 0x23466523, 0xc39d5ec3,
+ 0x18302818, 0x9637a196, 0x50a0f05, 0x9a2fb59a,
+ 0x70e0907, 0x12243612, 0x801b9b80, 0xe2df3de2,
+ 0xebcd26eb, 0x274e6927, 0xb27fcdb2, 0x75ea9f75,
+ 0x9121b09, 0x831d9e83, 0x2c58742c, 0x1a342e1a,
+ 0x1b362d1b, 0x6edcb26e, 0x5ab4ee5a, 0xa05bfba0,
+ 0x52a4f652, 0x3b764d3b, 0xd6b761d6, 0xb37dceb3,
+ 0x29527b29, 0xe3dd3ee3, 0x2f5e712f, 0x84139784,
+ 0x53a6f553, 0xd1b968d1, 0x0, 0xedc12ced,
+ 0x20406020, 0xfce31ffc, 0xb179c8b1, 0x5bb6ed5b,
+ 0x6ad4be6a, 0xcb8d46cb, 0xbe67d9be, 0x39724b39,
+ 0x4a94de4a, 0x4c98d44c, 0x58b0e858, 0xcf854acf,
+ 0xd0bb6bd0, 0xefc52aef, 0xaa4fe5aa, 0xfbed16fb,
+ 0x4386c543, 0x4d9ad74d, 0x33665533, 0x85119485,
+ 0x458acf45, 0xf9e910f9, 0x2040602, 0x7ffe817f,
+ 0x50a0f050, 0x3c78443c, 0x9f25ba9f, 0xa84be3a8,
+ 0x51a2f351, 0xa35dfea3, 0x4080c040, 0x8f058a8f,
+ 0x923fad92, 0x9d21bc9d, 0x38704838, 0xf5f104f5,
+ 0xbc63dfbc, 0xb677c1b6, 0xdaaf75da, 0x21426321,
+ 0x10203010, 0xffe51aff, 0xf3fd0ef3, 0xd2bf6dd2,
+ 0xcd814ccd, 0xc18140c, 0x13263513, 0xecc32fec,
+ 0x5fbee15f, 0x9735a297, 0x4488cc44, 0x172e3917,
+ 0xc49357c4, 0xa755f2a7, 0x7efc827e, 0x3d7a473d,
+ 0x64c8ac64, 0x5dbae75d, 0x19322b19, 0x73e69573,
+ 0x60c0a060, 0x81199881, 0x4f9ed14f, 0xdca37fdc,
+ 0x22446622, 0x2a547e2a, 0x903bab90, 0x880b8388,
+ 0x468cca46, 0xeec729ee, 0xb86bd3b8, 0x14283c14,
+ 0xdea779de, 0x5ebce25e, 0xb161d0b, 0xdbad76db,
+ 0xe0db3be0, 0x32645632, 0x3a744e3a, 0xa141e0a,
+ 0x4992db49, 0x60c0a06, 0x24486c24, 0x5cb8e45c,
+ 0xc29f5dc2, 0xd3bd6ed3, 0xac43efac, 0x62c4a662,
+ 0x9139a891, 0x9531a495, 0xe4d337e4, 0x79f28b79,
+ 0xe7d532e7, 0xc88b43c8, 0x376e5937, 0x6ddab76d,
+ 0x8d018c8d, 0xd5b164d5, 0x4e9cd24e, 0xa949e0a9,
+ 0x6cd8b46c, 0x56acfa56, 0xf4f307f4, 0xeacf25ea,
+ 0x65caaf65, 0x7af48e7a, 0xae47e9ae, 0x8101808,
+ 0xba6fd5ba, 0x78f08878, 0x254a6f25, 0x2e5c722e,
+ 0x1c38241c, 0xa657f1a6, 0xb473c7b4, 0xc69751c6,
+ 0xe8cb23e8, 0xdda17cdd, 0x74e89c74, 0x1f3e211f,
+ 0x4b96dd4b, 0xbd61dcbd, 0x8b0d868b, 0x8a0f858a,
+ 0x70e09070, 0x3e7c423e, 0xb571c4b5, 0x66ccaa66,
+ 0x4890d848, 0x3060503, 0xf6f701f6, 0xe1c120e,
+ 0x61c2a361, 0x356a5f35, 0x57aef957, 0xb969d0b9,
+ 0x86179186, 0xc19958c1, 0x1d3a271d, 0x9e27b99e,
+ 0xe1d938e1, 0xf8eb13f8, 0x982bb398, 0x11223311,
+ 0x69d2bb69, 0xd9a970d9, 0x8e07898e, 0x9433a794,
+ 0x9b2db69b, 0x1e3c221e, 0x87159287, 0xe9c920e9,
+ 0xce8749ce, 0x55aaff55, 0x28507828, 0xdfa57adf,
+ 0x8c038f8c, 0xa159f8a1, 0x89098089, 0xd1a170d,
+ 0xbf65dabf, 0xe6d731e6, 0x4284c642, 0x68d0b868,
+ 0x4182c341, 0x9929b099, 0x2d5a772d, 0xf1e110f,
+ 0xb07bcbb0, 0x54a8fc54, 0xbb6dd6bb, 0x162c3a16,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t T3[256] = {
+ 0xc6a56363, 0xf8847c7c, 0xee997777, 0xf68d7b7b,
+ 0xff0df2f2, 0xd6bd6b6b, 0xdeb16f6f, 0x9154c5c5,
+ 0x60503030, 0x2030101, 0xcea96767, 0x567d2b2b,
+ 0xe719fefe, 0xb562d7d7, 0x4de6abab, 0xec9a7676,
+ 0x8f45caca, 0x1f9d8282, 0x8940c9c9, 0xfa877d7d,
+ 0xef15fafa, 0xb2eb5959, 0x8ec94747, 0xfb0bf0f0,
+ 0x41ecadad, 0xb367d4d4, 0x5ffda2a2, 0x45eaafaf,
+ 0x23bf9c9c, 0x53f7a4a4, 0xe4967272, 0x9b5bc0c0,
+ 0x75c2b7b7, 0xe11cfdfd, 0x3dae9393, 0x4c6a2626,
+ 0x6c5a3636, 0x7e413f3f, 0xf502f7f7, 0x834fcccc,
+ 0x685c3434, 0x51f4a5a5, 0xd134e5e5, 0xf908f1f1,
+ 0xe2937171, 0xab73d8d8, 0x62533131, 0x2a3f1515,
+ 0x80c0404, 0x9552c7c7, 0x46652323, 0x9d5ec3c3,
+ 0x30281818, 0x37a19696, 0xa0f0505, 0x2fb59a9a,
+ 0xe090707, 0x24361212, 0x1b9b8080, 0xdf3de2e2,
+ 0xcd26ebeb, 0x4e692727, 0x7fcdb2b2, 0xea9f7575,
+ 0x121b0909, 0x1d9e8383, 0x58742c2c, 0x342e1a1a,
+ 0x362d1b1b, 0xdcb26e6e, 0xb4ee5a5a, 0x5bfba0a0,
+ 0xa4f65252, 0x764d3b3b, 0xb761d6d6, 0x7dceb3b3,
+ 0x527b2929, 0xdd3ee3e3, 0x5e712f2f, 0x13978484,
+ 0xa6f55353, 0xb968d1d1, 0x0, 0xc12ceded,
+ 0x40602020, 0xe31ffcfc, 0x79c8b1b1, 0xb6ed5b5b,
+ 0xd4be6a6a, 0x8d46cbcb, 0x67d9bebe, 0x724b3939,
+ 0x94de4a4a, 0x98d44c4c, 0xb0e85858, 0x854acfcf,
+ 0xbb6bd0d0, 0xc52aefef, 0x4fe5aaaa, 0xed16fbfb,
+ 0x86c54343, 0x9ad74d4d, 0x66553333, 0x11948585,
+ 0x8acf4545, 0xe910f9f9, 0x4060202, 0xfe817f7f,
+ 0xa0f05050, 0x78443c3c, 0x25ba9f9f, 0x4be3a8a8,
+ 0xa2f35151, 0x5dfea3a3, 0x80c04040, 0x58a8f8f,
+ 0x3fad9292, 0x21bc9d9d, 0x70483838, 0xf104f5f5,
+ 0x63dfbcbc, 0x77c1b6b6, 0xaf75dada, 0x42632121,
+ 0x20301010, 0xe51affff, 0xfd0ef3f3, 0xbf6dd2d2,
+ 0x814ccdcd, 0x18140c0c, 0x26351313, 0xc32fecec,
+ 0xbee15f5f, 0x35a29797, 0x88cc4444, 0x2e391717,
+ 0x9357c4c4, 0x55f2a7a7, 0xfc827e7e, 0x7a473d3d,
+ 0xc8ac6464, 0xbae75d5d, 0x322b1919, 0xe6957373,
+ 0xc0a06060, 0x19988181, 0x9ed14f4f, 0xa37fdcdc,
+ 0x44662222, 0x547e2a2a, 0x3bab9090, 0xb838888,
+ 0x8cca4646, 0xc729eeee, 0x6bd3b8b8, 0x283c1414,
+ 0xa779dede, 0xbce25e5e, 0x161d0b0b, 0xad76dbdb,
+ 0xdb3be0e0, 0x64563232, 0x744e3a3a, 0x141e0a0a,
+ 0x92db4949, 0xc0a0606, 0x486c2424, 0xb8e45c5c,
+ 0x9f5dc2c2, 0xbd6ed3d3, 0x43efacac, 0xc4a66262,
+ 0x39a89191, 0x31a49595, 0xd337e4e4, 0xf28b7979,
+ 0xd532e7e7, 0x8b43c8c8, 0x6e593737, 0xdab76d6d,
+ 0x18c8d8d, 0xb164d5d5, 0x9cd24e4e, 0x49e0a9a9,
+ 0xd8b46c6c, 0xacfa5656, 0xf307f4f4, 0xcf25eaea,
+ 0xcaaf6565, 0xf48e7a7a, 0x47e9aeae, 0x10180808,
+ 0x6fd5baba, 0xf0887878, 0x4a6f2525, 0x5c722e2e,
+ 0x38241c1c, 0x57f1a6a6, 0x73c7b4b4, 0x9751c6c6,
+ 0xcb23e8e8, 0xa17cdddd, 0xe89c7474, 0x3e211f1f,
+ 0x96dd4b4b, 0x61dcbdbd, 0xd868b8b, 0xf858a8a,
+ 0xe0907070, 0x7c423e3e, 0x71c4b5b5, 0xccaa6666,
+ 0x90d84848, 0x6050303, 0xf701f6f6, 0x1c120e0e,
+ 0xc2a36161, 0x6a5f3535, 0xaef95757, 0x69d0b9b9,
+ 0x17918686, 0x9958c1c1, 0x3a271d1d, 0x27b99e9e,
+ 0xd938e1e1, 0xeb13f8f8, 0x2bb39898, 0x22331111,
+ 0xd2bb6969, 0xa970d9d9, 0x7898e8e, 0x33a79494,
+ 0x2db69b9b, 0x3c221e1e, 0x15928787, 0xc920e9e9,
+ 0x8749cece, 0xaaff5555, 0x50782828, 0xa57adfdf,
+ 0x38f8c8c, 0x59f8a1a1, 0x9808989, 0x1a170d0d,
+ 0x65dabfbf, 0xd731e6e6, 0x84c64242, 0xd0b86868,
+ 0x82c34141, 0x29b09999, 0x5a772d2d, 0x1e110f0f,
+ 0x7bcbb0b0, 0xa8fc5454, 0x6dd6bbbb, 0x2c3a1616,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t U0[256] = {
+ 0x50a7f451, 0x5365417e, 0xc3a4171a, 0x965e273a,
+ 0xcb6bab3b, 0xf1459d1f, 0xab58faac, 0x9303e34b,
+ 0x55fa3020, 0xf66d76ad, 0x9176cc88, 0x254c02f5,
+ 0xfcd7e54f, 0xd7cb2ac5, 0x80443526, 0x8fa362b5,
+ 0x495ab1de, 0x671bba25, 0x980eea45, 0xe1c0fe5d,
+ 0x2752fc3, 0x12f04c81, 0xa397468d, 0xc6f9d36b,
+ 0xe75f8f03, 0x959c9215, 0xeb7a6dbf, 0xda595295,
+ 0x2d83bed4, 0xd3217458, 0x2969e049, 0x44c8c98e,
+ 0x6a89c275, 0x78798ef4, 0x6b3e5899, 0xdd71b927,
+ 0xb64fe1be, 0x17ad88f0, 0x66ac20c9, 0xb43ace7d,
+ 0x184adf63, 0x82311ae5, 0x60335197, 0x457f5362,
+ 0xe07764b1, 0x84ae6bbb, 0x1ca081fe, 0x942b08f9,
+ 0x58684870, 0x19fd458f, 0x876cde94, 0xb7f87b52,
+ 0x23d373ab, 0xe2024b72, 0x578f1fe3, 0x2aab5566,
+ 0x728ebb2, 0x3c2b52f, 0x9a7bc586, 0xa50837d3,
+ 0xf2872830, 0xb2a5bf23, 0xba6a0302, 0x5c8216ed,
+ 0x2b1ccf8a, 0x92b479a7, 0xf0f207f3, 0xa1e2694e,
+ 0xcdf4da65, 0xd5be0506, 0x1f6234d1, 0x8afea6c4,
+ 0x9d532e34, 0xa055f3a2, 0x32e18a05, 0x75ebf6a4,
+ 0x39ec830b, 0xaaef6040, 0x69f715e, 0x51106ebd,
+ 0xf98a213e, 0x3d06dd96, 0xae053edd, 0x46bde64d,
+ 0xb58d5491, 0x55dc471, 0x6fd40604, 0xff155060,
+ 0x24fb9819, 0x97e9bdd6, 0xcc434089, 0x779ed967,
+ 0xbd42e8b0, 0x888b8907, 0x385b19e7, 0xdbeec879,
+ 0x470a7ca1, 0xe90f427c, 0xc91e84f8, 0x0,
+ 0x83868009, 0x48ed2b32, 0xac70111e, 0x4e725a6c,
+ 0xfbff0efd, 0x5638850f, 0x1ed5ae3d, 0x27392d36,
+ 0x64d90f0a, 0x21a65c68, 0xd1545b9b, 0x3a2e3624,
+ 0xb1670a0c, 0xfe75793, 0xd296eeb4, 0x9e919b1b,
+ 0x4fc5c080, 0xa220dc61, 0x694b775a, 0x161a121c,
+ 0xaba93e2, 0xe52aa0c0, 0x43e0223c, 0x1d171b12,
+ 0xb0d090e, 0xadc78bf2, 0xb9a8b62d, 0xc8a91e14,
+ 0x8519f157, 0x4c0775af, 0xbbdd99ee, 0xfd607fa3,
+ 0x9f2601f7, 0xbcf5725c, 0xc53b6644, 0x347efb5b,
+ 0x7629438b, 0xdcc623cb, 0x68fcedb6, 0x63f1e4b8,
+ 0xcadc31d7, 0x10856342, 0x40229713, 0x2011c684,
+ 0x7d244a85, 0xf83dbbd2, 0x1132f9ae, 0x6da129c7,
+ 0x4b2f9e1d, 0xf330b2dc, 0xec52860d, 0xd0e3c177,
+ 0x6c16b32b, 0x99b970a9, 0xfa489411, 0x2264e947,
+ 0xc48cfca8, 0x1a3ff0a0, 0xd82c7d56, 0xef903322,
+ 0xc74e4987, 0xc1d138d9, 0xfea2ca8c, 0x360bd498,
+ 0xcf81f5a6, 0x28de7aa5, 0x268eb7da, 0xa4bfad3f,
+ 0xe49d3a2c, 0xd927850, 0x9bcc5f6a, 0x62467e54,
+ 0xc2138df6, 0xe8b8d890, 0x5ef7392e, 0xf5afc382,
+ 0xbe805d9f, 0x7c93d069, 0xa92dd56f, 0xb31225cf,
+ 0x3b99acc8, 0xa77d1810, 0x6e639ce8, 0x7bbb3bdb,
+ 0x97826cd, 0xf418596e, 0x1b79aec, 0xa89a4f83,
+ 0x656e95e6, 0x7ee6ffaa, 0x8cfbc21, 0xe6e815ef,
+ 0xd99be7ba, 0xce366f4a, 0xd4099fea, 0xd67cb029,
+ 0xafb2a431, 0x31233f2a, 0x3094a5c6, 0xc066a235,
+ 0x37bc4e74, 0xa6ca82fc, 0xb0d090e0, 0x15d8a733,
+ 0x4a9804f1, 0xf7daec41, 0xe50cd7f, 0x2ff69117,
+ 0x8dd64d76, 0x4db0ef43, 0x544daacc, 0xdf0496e4,
+ 0xe3b5d19e, 0x1b886a4c, 0xb81f2cc1, 0x7f516546,
+ 0x4ea5e9d, 0x5d358c01, 0x737487fa, 0x2e410bfb,
+ 0x5a1d67b3, 0x52d2db92, 0x335610e9, 0x1347d66d,
+ 0x8c61d79a, 0x7a0ca137, 0x8e14f859, 0x893c13eb,
+ 0xee27a9ce, 0x35c961b7, 0xede51ce1, 0x3cb1477a,
+ 0x59dfd29c, 0x3f73f255, 0x79ce1418, 0xbf37c773,
+ 0xeacdf753, 0x5baafd5f, 0x146f3ddf, 0x86db4478,
+ 0x81f3afca, 0x3ec468b9, 0x2c342438, 0x5f40a3c2,
+ 0x72c31d16, 0xc25e2bc, 0x8b493c28, 0x41950dff,
+ 0x7101a839, 0xdeb30c08, 0x9ce4b4d8, 0x90c15664,
+ 0x6184cb7b, 0x70b632d5, 0x745c6c48, 0x4257b8d0,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t U1[256] = {
+ 0xa7f45150, 0x65417e53, 0xa4171ac3, 0x5e273a96,
+ 0x6bab3bcb, 0x459d1ff1, 0x58faacab, 0x3e34b93,
+ 0xfa302055, 0x6d76adf6, 0x76cc8891, 0x4c02f525,
+ 0xd7e54ffc, 0xcb2ac5d7, 0x44352680, 0xa362b58f,
+ 0x5ab1de49, 0x1bba2567, 0xeea4598, 0xc0fe5de1,
+ 0x752fc302, 0xf04c8112, 0x97468da3, 0xf9d36bc6,
+ 0x5f8f03e7, 0x9c921595, 0x7a6dbfeb, 0x595295da,
+ 0x83bed42d, 0x217458d3, 0x69e04929, 0xc8c98e44,
+ 0x89c2756a, 0x798ef478, 0x3e58996b, 0x71b927dd,
+ 0x4fe1beb6, 0xad88f017, 0xac20c966, 0x3ace7db4,
+ 0x4adf6318, 0x311ae582, 0x33519760, 0x7f536245,
+ 0x7764b1e0, 0xae6bbb84, 0xa081fe1c, 0x2b08f994,
+ 0x68487058, 0xfd458f19, 0x6cde9487, 0xf87b52b7,
+ 0xd373ab23, 0x24b72e2, 0x8f1fe357, 0xab55662a,
+ 0x28ebb207, 0xc2b52f03, 0x7bc5869a, 0x837d3a5,
+ 0x872830f2, 0xa5bf23b2, 0x6a0302ba, 0x8216ed5c,
+ 0x1ccf8a2b, 0xb479a792, 0xf207f3f0, 0xe2694ea1,
+ 0xf4da65cd, 0xbe0506d5, 0x6234d11f, 0xfea6c48a,
+ 0x532e349d, 0x55f3a2a0, 0xe18a0532, 0xebf6a475,
+ 0xec830b39, 0xef6040aa, 0x9f715e06, 0x106ebd51,
+ 0x8a213ef9, 0x6dd963d, 0x53eddae, 0xbde64d46,
+ 0x8d5491b5, 0x5dc47105, 0xd406046f, 0x155060ff,
+ 0xfb981924, 0xe9bdd697, 0x434089cc, 0x9ed96777,
+ 0x42e8b0bd, 0x8b890788, 0x5b19e738, 0xeec879db,
+ 0xa7ca147, 0xf427ce9, 0x1e84f8c9, 0x0,
+ 0x86800983, 0xed2b3248, 0x70111eac, 0x725a6c4e,
+ 0xff0efdfb, 0x38850f56, 0xd5ae3d1e, 0x392d3627,
+ 0xd90f0a64, 0xa65c6821, 0x545b9bd1, 0x2e36243a,
+ 0x670a0cb1, 0xe757930f, 0x96eeb4d2, 0x919b1b9e,
+ 0xc5c0804f, 0x20dc61a2, 0x4b775a69, 0x1a121c16,
+ 0xba93e20a, 0x2aa0c0e5, 0xe0223c43, 0x171b121d,
+ 0xd090e0b, 0xc78bf2ad, 0xa8b62db9, 0xa91e14c8,
+ 0x19f15785, 0x775af4c, 0xdd99eebb, 0x607fa3fd,
+ 0x2601f79f, 0xf5725cbc, 0x3b6644c5, 0x7efb5b34,
+ 0x29438b76, 0xc623cbdc, 0xfcedb668, 0xf1e4b863,
+ 0xdc31d7ca, 0x85634210, 0x22971340, 0x11c68420,
+ 0x244a857d, 0x3dbbd2f8, 0x32f9ae11, 0xa129c76d,
+ 0x2f9e1d4b, 0x30b2dcf3, 0x52860dec, 0xe3c177d0,
+ 0x16b32b6c, 0xb970a999, 0x489411fa, 0x64e94722,
+ 0x8cfca8c4, 0x3ff0a01a, 0x2c7d56d8, 0x903322ef,
+ 0x4e4987c7, 0xd138d9c1, 0xa2ca8cfe, 0xbd49836,
+ 0x81f5a6cf, 0xde7aa528, 0x8eb7da26, 0xbfad3fa4,
+ 0x9d3a2ce4, 0x9278500d, 0xcc5f6a9b, 0x467e5462,
+ 0x138df6c2, 0xb8d890e8, 0xf7392e5e, 0xafc382f5,
+ 0x805d9fbe, 0x93d0697c, 0x2dd56fa9, 0x1225cfb3,
+ 0x99acc83b, 0x7d1810a7, 0x639ce86e, 0xbb3bdb7b,
+ 0x7826cd09, 0x18596ef4, 0xb79aec01, 0x9a4f83a8,
+ 0x6e95e665, 0xe6ffaa7e, 0xcfbc2108, 0xe815efe6,
+ 0x9be7bad9, 0x366f4ace, 0x99fead4, 0x7cb029d6,
+ 0xb2a431af, 0x233f2a31, 0x94a5c630, 0x66a235c0,
+ 0xbc4e7437, 0xca82fca6, 0xd090e0b0, 0xd8a73315,
+ 0x9804f14a, 0xdaec41f7, 0x50cd7f0e, 0xf691172f,
+ 0xd64d768d, 0xb0ef434d, 0x4daacc54, 0x496e4df,
+ 0xb5d19ee3, 0x886a4c1b, 0x1f2cc1b8, 0x5165467f,
+ 0xea5e9d04, 0x358c015d, 0x7487fa73, 0x410bfb2e,
+ 0x1d67b35a, 0xd2db9252, 0x5610e933, 0x47d66d13,
+ 0x61d79a8c, 0xca1377a, 0x14f8598e, 0x3c13eb89,
+ 0x27a9ceee, 0xc961b735, 0xe51ce1ed, 0xb1477a3c,
+ 0xdfd29c59, 0x73f2553f, 0xce141879, 0x37c773bf,
+ 0xcdf753ea, 0xaafd5f5b, 0x6f3ddf14, 0xdb447886,
+ 0xf3afca81, 0xc468b93e, 0x3424382c, 0x40a3c25f,
+ 0xc31d1672, 0x25e2bc0c, 0x493c288b, 0x950dff41,
+ 0x1a83971, 0xb30c08de, 0xe4b4d89c, 0xc1566490,
+ 0x84cb7b61, 0xb632d570, 0x5c6c4874, 0x57b8d042,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t U2[256] = {
+ 0xf45150a7, 0x417e5365, 0x171ac3a4, 0x273a965e,
+ 0xab3bcb6b, 0x9d1ff145, 0xfaacab58, 0xe34b9303,
+ 0x302055fa, 0x76adf66d, 0xcc889176, 0x2f5254c,
+ 0xe54ffcd7, 0x2ac5d7cb, 0x35268044, 0x62b58fa3,
+ 0xb1de495a, 0xba25671b, 0xea45980e, 0xfe5de1c0,
+ 0x2fc30275, 0x4c8112f0, 0x468da397, 0xd36bc6f9,
+ 0x8f03e75f, 0x9215959c, 0x6dbfeb7a, 0x5295da59,
+ 0xbed42d83, 0x7458d321, 0xe0492969, 0xc98e44c8,
+ 0xc2756a89, 0x8ef47879, 0x58996b3e, 0xb927dd71,
+ 0xe1beb64f, 0x88f017ad, 0x20c966ac, 0xce7db43a,
+ 0xdf63184a, 0x1ae58231, 0x51976033, 0x5362457f,
+ 0x64b1e077, 0x6bbb84ae, 0x81fe1ca0, 0x8f9942b,
+ 0x48705868, 0x458f19fd, 0xde94876c, 0x7b52b7f8,
+ 0x73ab23d3, 0x4b72e202, 0x1fe3578f, 0x55662aab,
+ 0xebb20728, 0xb52f03c2, 0xc5869a7b, 0x37d3a508,
+ 0x2830f287, 0xbf23b2a5, 0x302ba6a, 0x16ed5c82,
+ 0xcf8a2b1c, 0x79a792b4, 0x7f3f0f2, 0x694ea1e2,
+ 0xda65cdf4, 0x506d5be, 0x34d11f62, 0xa6c48afe,
+ 0x2e349d53, 0xf3a2a055, 0x8a0532e1, 0xf6a475eb,
+ 0x830b39ec, 0x6040aaef, 0x715e069f, 0x6ebd5110,
+ 0x213ef98a, 0xdd963d06, 0x3eddae05, 0xe64d46bd,
+ 0x5491b58d, 0xc471055d, 0x6046fd4, 0x5060ff15,
+ 0x981924fb, 0xbdd697e9, 0x4089cc43, 0xd967779e,
+ 0xe8b0bd42, 0x8907888b, 0x19e7385b, 0xc879dbee,
+ 0x7ca1470a, 0x427ce90f, 0x84f8c91e, 0x0,
+ 0x80098386, 0x2b3248ed, 0x111eac70, 0x5a6c4e72,
+ 0xefdfbff, 0x850f5638, 0xae3d1ed5, 0x2d362739,
+ 0xf0a64d9, 0x5c6821a6, 0x5b9bd154, 0x36243a2e,
+ 0xa0cb167, 0x57930fe7, 0xeeb4d296, 0x9b1b9e91,
+ 0xc0804fc5, 0xdc61a220, 0x775a694b, 0x121c161a,
+ 0x93e20aba, 0xa0c0e52a, 0x223c43e0, 0x1b121d17,
+ 0x90e0b0d, 0x8bf2adc7, 0xb62db9a8, 0x1e14c8a9,
+ 0xf1578519, 0x75af4c07, 0x99eebbdd, 0x7fa3fd60,
+ 0x1f79f26, 0x725cbcf5, 0x6644c53b, 0xfb5b347e,
+ 0x438b7629, 0x23cbdcc6, 0xedb668fc, 0xe4b863f1,
+ 0x31d7cadc, 0x63421085, 0x97134022, 0xc6842011,
+ 0x4a857d24, 0xbbd2f83d, 0xf9ae1132, 0x29c76da1,
+ 0x9e1d4b2f, 0xb2dcf330, 0x860dec52, 0xc177d0e3,
+ 0xb32b6c16, 0x70a999b9, 0x9411fa48, 0xe9472264,
+ 0xfca8c48c, 0xf0a01a3f, 0x7d56d82c, 0x3322ef90,
+ 0x4987c74e, 0x38d9c1d1, 0xca8cfea2, 0xd498360b,
+ 0xf5a6cf81, 0x7aa528de, 0xb7da268e, 0xad3fa4bf,
+ 0x3a2ce49d, 0x78500d92, 0x5f6a9bcc, 0x7e546246,
+ 0x8df6c213, 0xd890e8b8, 0x392e5ef7, 0xc382f5af,
+ 0x5d9fbe80, 0xd0697c93, 0xd56fa92d, 0x25cfb312,
+ 0xacc83b99, 0x1810a77d, 0x9ce86e63, 0x3bdb7bbb,
+ 0x26cd0978, 0x596ef418, 0x9aec01b7, 0x4f83a89a,
+ 0x95e6656e, 0xffaa7ee6, 0xbc2108cf, 0x15efe6e8,
+ 0xe7bad99b, 0x6f4ace36, 0x9fead409, 0xb029d67c,
+ 0xa431afb2, 0x3f2a3123, 0xa5c63094, 0xa235c066,
+ 0x4e7437bc, 0x82fca6ca, 0x90e0b0d0, 0xa73315d8,
+ 0x4f14a98, 0xec41f7da, 0xcd7f0e50, 0x91172ff6,
+ 0x4d768dd6, 0xef434db0, 0xaacc544d, 0x96e4df04,
+ 0xd19ee3b5, 0x6a4c1b88, 0x2cc1b81f, 0x65467f51,
+ 0x5e9d04ea, 0x8c015d35, 0x87fa7374, 0xbfb2e41,
+ 0x67b35a1d, 0xdb9252d2, 0x10e93356, 0xd66d1347,
+ 0xd79a8c61, 0xa1377a0c, 0xf8598e14, 0x13eb893c,
+ 0xa9ceee27, 0x61b735c9, 0x1ce1ede5, 0x477a3cb1,
+ 0xd29c59df, 0xf2553f73, 0x141879ce, 0xc773bf37,
+ 0xf753eacd, 0xfd5f5baa, 0x3ddf146f, 0x447886db,
+ 0xafca81f3, 0x68b93ec4, 0x24382c34, 0xa3c25f40,
+ 0x1d1672c3, 0xe2bc0c25, 0x3c288b49, 0xdff4195,
+ 0xa8397101, 0xc08deb3, 0xb4d89ce4, 0x566490c1,
+ 0xcb7b6184, 0x32d570b6, 0x6c48745c, 0xb8d04257,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t U3[256] = {
+ 0x5150a7f4, 0x7e536541, 0x1ac3a417, 0x3a965e27,
+ 0x3bcb6bab, 0x1ff1459d, 0xacab58fa, 0x4b9303e3,
+ 0x2055fa30, 0xadf66d76, 0x889176cc, 0xf5254c02,
+ 0x4ffcd7e5, 0xc5d7cb2a, 0x26804435, 0xb58fa362,
+ 0xde495ab1, 0x25671bba, 0x45980eea, 0x5de1c0fe,
+ 0xc302752f, 0x8112f04c, 0x8da39746, 0x6bc6f9d3,
+ 0x3e75f8f, 0x15959c92, 0xbfeb7a6d, 0x95da5952,
+ 0xd42d83be, 0x58d32174, 0x492969e0, 0x8e44c8c9,
+ 0x756a89c2, 0xf478798e, 0x996b3e58, 0x27dd71b9,
+ 0xbeb64fe1, 0xf017ad88, 0xc966ac20, 0x7db43ace,
+ 0x63184adf, 0xe582311a, 0x97603351, 0x62457f53,
+ 0xb1e07764, 0xbb84ae6b, 0xfe1ca081, 0xf9942b08,
+ 0x70586848, 0x8f19fd45, 0x94876cde, 0x52b7f87b,
+ 0xab23d373, 0x72e2024b, 0xe3578f1f, 0x662aab55,
+ 0xb20728eb, 0x2f03c2b5, 0x869a7bc5, 0xd3a50837,
+ 0x30f28728, 0x23b2a5bf, 0x2ba6a03, 0xed5c8216,
+ 0x8a2b1ccf, 0xa792b479, 0xf3f0f207, 0x4ea1e269,
+ 0x65cdf4da, 0x6d5be05, 0xd11f6234, 0xc48afea6,
+ 0x349d532e, 0xa2a055f3, 0x532e18a, 0xa475ebf6,
+ 0xb39ec83, 0x40aaef60, 0x5e069f71, 0xbd51106e,
+ 0x3ef98a21, 0x963d06dd, 0xddae053e, 0x4d46bde6,
+ 0x91b58d54, 0x71055dc4, 0x46fd406, 0x60ff1550,
+ 0x1924fb98, 0xd697e9bd, 0x89cc4340, 0x67779ed9,
+ 0xb0bd42e8, 0x7888b89, 0xe7385b19, 0x79dbeec8,
+ 0xa1470a7c, 0x7ce90f42, 0xf8c91e84, 0x0,
+ 0x9838680, 0x3248ed2b, 0x1eac7011, 0x6c4e725a,
+ 0xfdfbff0e, 0xf563885, 0x3d1ed5ae, 0x3627392d,
+ 0xa64d90f, 0x6821a65c, 0x9bd1545b, 0x243a2e36,
+ 0xcb1670a, 0x930fe757, 0xb4d296ee, 0x1b9e919b,
+ 0x804fc5c0, 0x61a220dc, 0x5a694b77, 0x1c161a12,
+ 0xe20aba93, 0xc0e52aa0, 0x3c43e022, 0x121d171b,
+ 0xe0b0d09, 0xf2adc78b, 0x2db9a8b6, 0x14c8a91e,
+ 0x578519f1, 0xaf4c0775, 0xeebbdd99, 0xa3fd607f,
+ 0xf79f2601, 0x5cbcf572, 0x44c53b66, 0x5b347efb,
+ 0x8b762943, 0xcbdcc623, 0xb668fced, 0xb863f1e4,
+ 0xd7cadc31, 0x42108563, 0x13402297, 0x842011c6,
+ 0x857d244a, 0xd2f83dbb, 0xae1132f9, 0xc76da129,
+ 0x1d4b2f9e, 0xdcf330b2, 0xdec5286, 0x77d0e3c1,
+ 0x2b6c16b3, 0xa999b970, 0x11fa4894, 0x472264e9,
+ 0xa8c48cfc, 0xa01a3ff0, 0x56d82c7d, 0x22ef9033,
+ 0x87c74e49, 0xd9c1d138, 0x8cfea2ca, 0x98360bd4,
+ 0xa6cf81f5, 0xa528de7a, 0xda268eb7, 0x3fa4bfad,
+ 0x2ce49d3a, 0x500d9278, 0x6a9bcc5f, 0x5462467e,
+ 0xf6c2138d, 0x90e8b8d8, 0x2e5ef739, 0x82f5afc3,
+ 0x9fbe805d, 0x697c93d0, 0x6fa92dd5, 0xcfb31225,
+ 0xc83b99ac, 0x10a77d18, 0xe86e639c, 0xdb7bbb3b,
+ 0xcd097826, 0x6ef41859, 0xec01b79a, 0x83a89a4f,
+ 0xe6656e95, 0xaa7ee6ff, 0x2108cfbc, 0xefe6e815,
+ 0xbad99be7, 0x4ace366f, 0xead4099f, 0x29d67cb0,
+ 0x31afb2a4, 0x2a31233f, 0xc63094a5, 0x35c066a2,
+ 0x7437bc4e, 0xfca6ca82, 0xe0b0d090, 0x3315d8a7,
+ 0xf14a9804, 0x41f7daec, 0x7f0e50cd, 0x172ff691,
+ 0x768dd64d, 0x434db0ef, 0xcc544daa, 0xe4df0496,
+ 0x9ee3b5d1, 0x4c1b886a, 0xc1b81f2c, 0x467f5165,
+ 0x9d04ea5e, 0x15d358c, 0xfa737487, 0xfb2e410b,
+ 0xb35a1d67, 0x9252d2db, 0xe9335610, 0x6d1347d6,
+ 0x9a8c61d7, 0x377a0ca1, 0x598e14f8, 0xeb893c13,
+ 0xceee27a9, 0xb735c961, 0xe1ede51c, 0x7a3cb147,
+ 0x9c59dfd2, 0x553f73f2, 0x1879ce14, 0x73bf37c7,
+ 0x53eacdf7, 0x5f5baafd, 0xdf146f3d, 0x7886db44,
+ 0xca81f3af, 0xb93ec468, 0x382c3424, 0xc25f40a3,
+ 0x1672c31d, 0xbc0c25e2, 0x288b493c, 0xff41950d,
+ 0x397101a8, 0x8deb30c, 0xd89ce4b4, 0x6490c156,
+ 0x7b6184cb, 0xd570b632, 0x48745c6c, 0xd04257b8,
+};
+/* clang-format on */
+
+#else /* assume big endian */
+/* clang-format off */
+static const uint32_t T0[256] = {
+ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d,
+ 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554,
+ 0x60303050, 0x2010103, 0xce6767a9, 0x562b2b7d,
+ 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a,
+ 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87,
+ 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b,
+ 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea,
+ 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b,
+ 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a,
+ 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f,
+ 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108,
+ 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f,
+ 0x804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e,
+ 0x30181828, 0x379696a1, 0xa05050f, 0x2f9a9ab5,
+ 0xe070709, 0x24121236, 0x1b80809b, 0xdfe2e23d,
+ 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f,
+ 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e,
+ 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb,
+ 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce,
+ 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497,
+ 0xa65353f5, 0xb9d1d168, 0x0, 0xc1eded2c,
+ 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed,
+ 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b,
+ 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a,
+ 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16,
+ 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594,
+ 0x8a4545cf, 0xe9f9f910, 0x4020206, 0xfe7f7f81,
+ 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3,
+ 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x58f8f8a,
+ 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504,
+ 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163,
+ 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d,
+ 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f,
+ 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739,
+ 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47,
+ 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395,
+ 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f,
+ 0x44222266, 0x542a2a7e, 0x3b9090ab, 0xb888883,
+ 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c,
+ 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76,
+ 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e,
+ 0x924949db, 0xc06060a, 0x4824246c, 0xb85c5ce4,
+ 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6,
+ 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b,
+ 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7,
+ 0x18d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0,
+ 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25,
+ 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818,
+ 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72,
+ 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651,
+ 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21,
+ 0x964b4bdd, 0x61bdbddc, 0xd8b8b86, 0xf8a8a85,
+ 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa,
+ 0x904848d8, 0x6030305, 0xf7f6f601, 0x1c0e0e12,
+ 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0,
+ 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9,
+ 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133,
+ 0xd26969bb, 0xa9d9d970, 0x78e8e89, 0x339494a7,
+ 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920,
+ 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a,
+ 0x38c8c8f, 0x59a1a1f8, 0x9898980, 0x1a0d0d17,
+ 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8,
+ 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11,
+ 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t T1[256] = {
+ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b,
+ 0xdfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5,
+ 0x50603030, 0x3020101, 0xa9ce6767, 0x7d562b2b,
+ 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676,
+ 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d,
+ 0x15effafa, 0xebb25959, 0xc98e4747, 0xbfbf0f0,
+ 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf,
+ 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0,
+ 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626,
+ 0x5a6c3636, 0x417e3f3f, 0x2f5f7f7, 0x4f83cccc,
+ 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x8f9f1f1,
+ 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515,
+ 0xc080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3,
+ 0x28301818, 0xa1379696, 0xf0a0505, 0xb52f9a9a,
+ 0x90e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2,
+ 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575,
+ 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a,
+ 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0,
+ 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3,
+ 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484,
+ 0xf5a65353, 0x68b9d1d1, 0x0, 0x2cc1eded,
+ 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b,
+ 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939,
+ 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf,
+ 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb,
+ 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585,
+ 0xcf8a4545, 0x10e9f9f9, 0x6040202, 0x81fe7f7f,
+ 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8,
+ 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f,
+ 0xad3f9292, 0xbc219d9d, 0x48703838, 0x4f1f5f5,
+ 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121,
+ 0x30201010, 0x1ae5ffff, 0xefdf3f3, 0x6dbfd2d2,
+ 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec,
+ 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717,
+ 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d,
+ 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373,
+ 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc,
+ 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888,
+ 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414,
+ 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb,
+ 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a,
+ 0xdb924949, 0xa0c0606, 0x6c482424, 0xe4b85c5c,
+ 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262,
+ 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979,
+ 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d,
+ 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9,
+ 0xb4d86c6c, 0xfaac5656, 0x7f3f4f4, 0x25cfeaea,
+ 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808,
+ 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e,
+ 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6,
+ 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f,
+ 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a,
+ 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666,
+ 0xd8904848, 0x5060303, 0x1f7f6f6, 0x121c0e0e,
+ 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9,
+ 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e,
+ 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111,
+ 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494,
+ 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9,
+ 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf,
+ 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d,
+ 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868,
+ 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f,
+ 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t T2[256] = {
+ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b,
+ 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5,
+ 0x30506030, 0x1030201, 0x67a9ce67, 0x2b7d562b,
+ 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76,
+ 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d,
+ 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0,
+ 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af,
+ 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0,
+ 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26,
+ 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc,
+ 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1,
+ 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15,
+ 0x40c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3,
+ 0x18283018, 0x96a13796, 0x50f0a05, 0x9ab52f9a,
+ 0x7090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2,
+ 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75,
+ 0x91b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a,
+ 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0,
+ 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3,
+ 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384,
+ 0x53f5a653, 0xd168b9d1, 0x0, 0xed2cc1ed,
+ 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b,
+ 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239,
+ 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf,
+ 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb,
+ 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185,
+ 0x45cf8a45, 0xf910e9f9, 0x2060402, 0x7f81fe7f,
+ 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8,
+ 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f,
+ 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5,
+ 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221,
+ 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2,
+ 0xcd4c81cd, 0xc14180c, 0x13352613, 0xec2fc3ec,
+ 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17,
+ 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d,
+ 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673,
+ 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc,
+ 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88,
+ 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814,
+ 0xde79a7de, 0x5ee2bc5e, 0xb1d160b, 0xdb76addb,
+ 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0xa1e140a,
+ 0x49db9249, 0x60a0c06, 0x246c4824, 0x5ce4b85c,
+ 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462,
+ 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279,
+ 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d,
+ 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9,
+ 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea,
+ 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x8181008,
+ 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e,
+ 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6,
+ 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f,
+ 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a,
+ 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66,
+ 0x48d89048, 0x3050603, 0xf601f7f6, 0xe121c0e,
+ 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9,
+ 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e,
+ 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211,
+ 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394,
+ 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9,
+ 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df,
+ 0x8c8f038c, 0xa1f859a1, 0x89800989, 0xd171a0d,
+ 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068,
+ 0x41c38241, 0x99b02999, 0x2d775a2d, 0xf111e0f,
+ 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t T3[256] = {
+ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6,
+ 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491,
+ 0x30305060, 0x1010302, 0x6767a9ce, 0x2b2b7d56,
+ 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec,
+ 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa,
+ 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb,
+ 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45,
+ 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b,
+ 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c,
+ 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83,
+ 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9,
+ 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a,
+ 0x4040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d,
+ 0x18182830, 0x9696a137, 0x5050f0a, 0x9a9ab52f,
+ 0x707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf,
+ 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea,
+ 0x9091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34,
+ 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b,
+ 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d,
+ 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713,
+ 0x5353f5a6, 0xd1d168b9, 0x0, 0xeded2cc1,
+ 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6,
+ 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72,
+ 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85,
+ 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed,
+ 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411,
+ 0x4545cf8a, 0xf9f910e9, 0x2020604, 0x7f7f81fe,
+ 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b,
+ 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05,
+ 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1,
+ 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342,
+ 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf,
+ 0xcdcd4c81, 0xc0c1418, 0x13133526, 0xecec2fc3,
+ 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e,
+ 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a,
+ 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6,
+ 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3,
+ 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b,
+ 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28,
+ 0xdede79a7, 0x5e5ee2bc, 0xb0b1d16, 0xdbdb76ad,
+ 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0xa0a1e14,
+ 0x4949db92, 0x6060a0c, 0x24246c48, 0x5c5ce4b8,
+ 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4,
+ 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2,
+ 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da,
+ 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049,
+ 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf,
+ 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x8081810,
+ 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c,
+ 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197,
+ 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e,
+ 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f,
+ 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc,
+ 0x4848d890, 0x3030506, 0xf6f601f7, 0xe0e121c,
+ 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069,
+ 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927,
+ 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322,
+ 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733,
+ 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9,
+ 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5,
+ 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0xd0d171a,
+ 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0,
+ 0x4141c382, 0x9999b029, 0x2d2d775a, 0xf0f111e,
+ 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t U0[256] = {
+ 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96,
+ 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393,
+ 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25,
+ 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f,
+ 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1,
+ 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6,
+ 0x38f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da,
+ 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844,
+ 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd,
+ 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4,
+ 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45,
+ 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94,
+ 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7,
+ 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a,
+ 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5,
+ 0x302887f2, 0x23bfa5b2, 0x2036aba, 0xed16825c,
+ 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1,
+ 0x65daf4cd, 0x605bed5, 0xd134621f, 0xc4a6fe8a,
+ 0x342e539d, 0xa2f355a0, 0x58ae132, 0xa4f6eb75,
+ 0xb83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051,
+ 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46,
+ 0x91548db5, 0x71c45d05, 0x406d46f, 0x605015ff,
+ 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77,
+ 0xb0e842bd, 0x7898b88, 0xe7195b38, 0x79c8eedb,
+ 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x0,
+ 0x9808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e,
+ 0xfd0efffb, 0xf853856, 0x3daed51e, 0x362d3927,
+ 0xa0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a,
+ 0xc0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e,
+ 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16,
+ 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d,
+ 0xe090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8,
+ 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd,
+ 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34,
+ 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163,
+ 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120,
+ 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d,
+ 0x1d9e2f4b, 0xdcb230f3, 0xd8652ec, 0x77c1e3d0,
+ 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422,
+ 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef,
+ 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36,
+ 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4,
+ 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662,
+ 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5,
+ 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3,
+ 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b,
+ 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8,
+ 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6,
+ 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6,
+ 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0,
+ 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815,
+ 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f,
+ 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df,
+ 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f,
+ 0x9d5eea04, 0x18c355d, 0xfa877473, 0xfb0b412e,
+ 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713,
+ 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89,
+ 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c,
+ 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf,
+ 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86,
+ 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f,
+ 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541,
+ 0x39a80171, 0x80cb3de, 0xd8b4e49c, 0x6456c190,
+ 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t U1[256] = {
+ 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e,
+ 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303,
+ 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c,
+ 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3,
+ 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0,
+ 0x2c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9,
+ 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259,
+ 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8,
+ 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971,
+ 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a,
+ 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f,
+ 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b,
+ 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8,
+ 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab,
+ 0x7b2eb28, 0x32fb5c2, 0x9a86c57b, 0xa5d33708,
+ 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682,
+ 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2,
+ 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe,
+ 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb,
+ 0x390b83ec, 0xaa4060ef, 0x65e719f, 0x51bd6e10,
+ 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd,
+ 0xb591548d, 0x571c45d, 0x6f0406d4, 0xff605015,
+ 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e,
+ 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee,
+ 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x0,
+ 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72,
+ 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39,
+ 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e,
+ 0xb10c0a67, 0xf9357e7, 0xd2b4ee96, 0x9e1b9b91,
+ 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a,
+ 0xae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17,
+ 0xb0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9,
+ 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60,
+ 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e,
+ 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1,
+ 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611,
+ 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1,
+ 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3,
+ 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964,
+ 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390,
+ 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b,
+ 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf,
+ 0xe42c3a9d, 0xd507892, 0x9b6a5fcc, 0x62547e46,
+ 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af,
+ 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512,
+ 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb,
+ 0x9cd2678, 0xf46e5918, 0x1ec9ab7, 0xa8834f9a,
+ 0x65e6956e, 0x7eaaffe6, 0x821bccf, 0xe6ef15e8,
+ 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c,
+ 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266,
+ 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8,
+ 0x4af10498, 0xf741ecda, 0xe7fcd50, 0x2f1791f6,
+ 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604,
+ 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551,
+ 0x49d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41,
+ 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647,
+ 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c,
+ 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1,
+ 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737,
+ 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db,
+ 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340,
+ 0x72161dc3, 0xcbce225, 0x8b283c49, 0x41ff0d95,
+ 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1,
+ 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t U2[256] = {
+ 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27,
+ 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x3934be3,
+ 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502,
+ 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562,
+ 0x5a49deb1, 0x1b6725ba, 0xe9845ea, 0xc0e15dfe,
+ 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3,
+ 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552,
+ 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9,
+ 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9,
+ 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce,
+ 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253,
+ 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908,
+ 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b,
+ 0xd323ab73, 0x2e2724b, 0x8f57e31f, 0xab2a6655,
+ 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x8a5d337,
+ 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16,
+ 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69,
+ 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6,
+ 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6,
+ 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e,
+ 0x8af93e21, 0x63d96dd, 0x5aedd3e, 0xbd464de6,
+ 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050,
+ 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9,
+ 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8,
+ 0xa47a17c, 0xfe97c42, 0x1ec9f884, 0x0,
+ 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a,
+ 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d,
+ 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436,
+ 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b,
+ 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12,
+ 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b,
+ 0xd0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e,
+ 0x198557f1, 0x74caf75, 0xddbbee99, 0x60fda37f,
+ 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb,
+ 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4,
+ 0xdccad731, 0x85104263, 0x22401397, 0x112084c6,
+ 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729,
+ 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1,
+ 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9,
+ 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233,
+ 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0xb3698d4,
+ 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad,
+ 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e,
+ 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3,
+ 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25,
+ 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b,
+ 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f,
+ 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15,
+ 0x9bd9bae7, 0x36ce4a6f, 0x9d4ea9f, 0x7cd629b0,
+ 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2,
+ 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7,
+ 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791,
+ 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x4dfe496,
+ 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665,
+ 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b,
+ 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6,
+ 0x618c9ad7, 0xc7a37a1, 0x148e59f8, 0x3c89eb13,
+ 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47,
+ 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7,
+ 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844,
+ 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3,
+ 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d,
+ 0x17139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456,
+ 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t U3[256] = {
+ 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a,
+ 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b,
+ 0x30fa5520, 0x766df6ad, 0xcc769188, 0x24c25f5,
+ 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5,
+ 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d,
+ 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b,
+ 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95,
+ 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e,
+ 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27,
+ 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d,
+ 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562,
+ 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x82b94f9,
+ 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752,
+ 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66,
+ 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3,
+ 0x2887f230, 0xbfa5b223, 0x36aba02, 0x16825ced,
+ 0xcf1c2b8a, 0x79b492a7, 0x7f2f0f3, 0x69e2a14e,
+ 0xdaf4cd65, 0x5bed506, 0x34621fd1, 0xa6fe8ac4,
+ 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4,
+ 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd,
+ 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d,
+ 0x548db591, 0xc45d0571, 0x6d46f04, 0x5015ff60,
+ 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767,
+ 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79,
+ 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x0,
+ 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c,
+ 0xefffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736,
+ 0xfd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24,
+ 0xa67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b,
+ 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c,
+ 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12,
+ 0x90d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814,
+ 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3,
+ 0x1269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b,
+ 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8,
+ 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084,
+ 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7,
+ 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077,
+ 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247,
+ 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22,
+ 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698,
+ 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f,
+ 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254,
+ 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582,
+ 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf,
+ 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb,
+ 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883,
+ 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef,
+ 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629,
+ 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035,
+ 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533,
+ 0x4984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17,
+ 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4,
+ 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46,
+ 0x5eea049d, 0x8c355d01, 0x877473fa, 0xb412efb,
+ 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d,
+ 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb,
+ 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a,
+ 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73,
+ 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678,
+ 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2,
+ 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0xd9541ff,
+ 0xa8017139, 0xcb3de08, 0xb4e49cd8, 0x56c19064,
+ 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0
+};
+/* clang-format on */
+#endif
+
+/*
+ * the following tables (aes_sbox, aes_inv_sbox, T4, U4) are
+ * endian-neutral
+ */
+/* clang-format off */
+static const uint8_t aes_sbox[256] = {
+ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
+ 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
+ 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
+ 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
+ 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
+ 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+ 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
+ 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
+ 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
+ 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
+ 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
+ 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+ 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
+ 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
+ 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
+ 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
+ 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
+ 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+ 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
+ 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
+ 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
+ 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
+ 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
+ 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+ 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
+ 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
+ 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
+ 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
+ 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
+ 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+ 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
+ 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
+};
+/* clang-format on */
+
+#ifndef CPU_RISC
+/* clang-format off */
+static const uint8_t aes_inv_sbox[256] = {
+ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38,
+ 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
+ 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
+ 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
+ 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d,
+ 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
+ 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2,
+ 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
+ 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16,
+ 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
+ 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda,
+ 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
+ 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a,
+ 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
+ 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
+ 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
+ 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea,
+ 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
+ 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85,
+ 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
+ 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89,
+ 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
+ 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20,
+ 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
+ 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31,
+ 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
+ 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d,
+ 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
+ 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0,
+ 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
+ 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26,
+ 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
+};
+/* clang-format on */
+#endif /* ! CPU_RISC */
+
+#ifdef CPU_RISC
+/* clang-format off */
+static const uint32_t T4[256] = {
+ 0x63636363, 0x7c7c7c7c, 0x77777777, 0x7b7b7b7b,
+ 0xf2f2f2f2, 0x6b6b6b6b, 0x6f6f6f6f, 0xc5c5c5c5,
+ 0x30303030, 0x01010101, 0x67676767, 0x2b2b2b2b,
+ 0xfefefefe, 0xd7d7d7d7, 0xabababab, 0x76767676,
+ 0xcacacaca, 0x82828282, 0xc9c9c9c9, 0x7d7d7d7d,
+ 0xfafafafa, 0x59595959, 0x47474747, 0xf0f0f0f0,
+ 0xadadadad, 0xd4d4d4d4, 0xa2a2a2a2, 0xafafafaf,
+ 0x9c9c9c9c, 0xa4a4a4a4, 0x72727272, 0xc0c0c0c0,
+ 0xb7b7b7b7, 0xfdfdfdfd, 0x93939393, 0x26262626,
+ 0x36363636, 0x3f3f3f3f, 0xf7f7f7f7, 0xcccccccc,
+ 0x34343434, 0xa5a5a5a5, 0xe5e5e5e5, 0xf1f1f1f1,
+ 0x71717171, 0xd8d8d8d8, 0x31313131, 0x15151515,
+ 0x04040404, 0xc7c7c7c7, 0x23232323, 0xc3c3c3c3,
+ 0x18181818, 0x96969696, 0x05050505, 0x9a9a9a9a,
+ 0x07070707, 0x12121212, 0x80808080, 0xe2e2e2e2,
+ 0xebebebeb, 0x27272727, 0xb2b2b2b2, 0x75757575,
+ 0x09090909, 0x83838383, 0x2c2c2c2c, 0x1a1a1a1a,
+ 0x1b1b1b1b, 0x6e6e6e6e, 0x5a5a5a5a, 0xa0a0a0a0,
+ 0x52525252, 0x3b3b3b3b, 0xd6d6d6d6, 0xb3b3b3b3,
+ 0x29292929, 0xe3e3e3e3, 0x2f2f2f2f, 0x84848484,
+ 0x53535353, 0xd1d1d1d1, 0x00000000, 0xedededed,
+ 0x20202020, 0xfcfcfcfc, 0xb1b1b1b1, 0x5b5b5b5b,
+ 0x6a6a6a6a, 0xcbcbcbcb, 0xbebebebe, 0x39393939,
+ 0x4a4a4a4a, 0x4c4c4c4c, 0x58585858, 0xcfcfcfcf,
+ 0xd0d0d0d0, 0xefefefef, 0xaaaaaaaa, 0xfbfbfbfb,
+ 0x43434343, 0x4d4d4d4d, 0x33333333, 0x85858585,
+ 0x45454545, 0xf9f9f9f9, 0x02020202, 0x7f7f7f7f,
+ 0x50505050, 0x3c3c3c3c, 0x9f9f9f9f, 0xa8a8a8a8,
+ 0x51515151, 0xa3a3a3a3, 0x40404040, 0x8f8f8f8f,
+ 0x92929292, 0x9d9d9d9d, 0x38383838, 0xf5f5f5f5,
+ 0xbcbcbcbc, 0xb6b6b6b6, 0xdadadada, 0x21212121,
+ 0x10101010, 0xffffffff, 0xf3f3f3f3, 0xd2d2d2d2,
+ 0xcdcdcdcd, 0x0c0c0c0c, 0x13131313, 0xecececec,
+ 0x5f5f5f5f, 0x97979797, 0x44444444, 0x17171717,
+ 0xc4c4c4c4, 0xa7a7a7a7, 0x7e7e7e7e, 0x3d3d3d3d,
+ 0x64646464, 0x5d5d5d5d, 0x19191919, 0x73737373,
+ 0x60606060, 0x81818181, 0x4f4f4f4f, 0xdcdcdcdc,
+ 0x22222222, 0x2a2a2a2a, 0x90909090, 0x88888888,
+ 0x46464646, 0xeeeeeeee, 0xb8b8b8b8, 0x14141414,
+ 0xdededede, 0x5e5e5e5e, 0x0b0b0b0b, 0xdbdbdbdb,
+ 0xe0e0e0e0, 0x32323232, 0x3a3a3a3a, 0x0a0a0a0a,
+ 0x49494949, 0x06060606, 0x24242424, 0x5c5c5c5c,
+ 0xc2c2c2c2, 0xd3d3d3d3, 0xacacacac, 0x62626262,
+ 0x91919191, 0x95959595, 0xe4e4e4e4, 0x79797979,
+ 0xe7e7e7e7, 0xc8c8c8c8, 0x37373737, 0x6d6d6d6d,
+ 0x8d8d8d8d, 0xd5d5d5d5, 0x4e4e4e4e, 0xa9a9a9a9,
+ 0x6c6c6c6c, 0x56565656, 0xf4f4f4f4, 0xeaeaeaea,
+ 0x65656565, 0x7a7a7a7a, 0xaeaeaeae, 0x08080808,
+ 0xbabababa, 0x78787878, 0x25252525, 0x2e2e2e2e,
+ 0x1c1c1c1c, 0xa6a6a6a6, 0xb4b4b4b4, 0xc6c6c6c6,
+ 0xe8e8e8e8, 0xdddddddd, 0x74747474, 0x1f1f1f1f,
+ 0x4b4b4b4b, 0xbdbdbdbd, 0x8b8b8b8b, 0x8a8a8a8a,
+ 0x70707070, 0x3e3e3e3e, 0xb5b5b5b5, 0x66666666,
+ 0x48484848, 0x03030303, 0xf6f6f6f6, 0x0e0e0e0e,
+ 0x61616161, 0x35353535, 0x57575757, 0xb9b9b9b9,
+ 0x86868686, 0xc1c1c1c1, 0x1d1d1d1d, 0x9e9e9e9e,
+ 0xe1e1e1e1, 0xf8f8f8f8, 0x98989898, 0x11111111,
+ 0x69696969, 0xd9d9d9d9, 0x8e8e8e8e, 0x94949494,
+ 0x9b9b9b9b, 0x1e1e1e1e, 0x87878787, 0xe9e9e9e9,
+ 0xcececece, 0x55555555, 0x28282828, 0xdfdfdfdf,
+ 0x8c8c8c8c, 0xa1a1a1a1, 0x89898989, 0x0d0d0d0d,
+ 0xbfbfbfbf, 0xe6e6e6e6, 0x42424242, 0x68686868,
+ 0x41414141, 0x99999999, 0x2d2d2d2d, 0x0f0f0f0f,
+ 0xb0b0b0b0, 0x54545454, 0xbbbbbbbb, 0x16161616
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint32_t U4[256] = {
+ 0x52525252, 0x9090909, 0x6a6a6a6a, 0xd5d5d5d5,
+ 0x30303030, 0x36363636, 0xa5a5a5a5, 0x38383838,
+ 0xbfbfbfbf, 0x40404040, 0xa3a3a3a3, 0x9e9e9e9e,
+ 0x81818181, 0xf3f3f3f3, 0xd7d7d7d7, 0xfbfbfbfb,
+ 0x7c7c7c7c, 0xe3e3e3e3, 0x39393939, 0x82828282,
+ 0x9b9b9b9b, 0x2f2f2f2f, 0xffffffff, 0x87878787,
+ 0x34343434, 0x8e8e8e8e, 0x43434343, 0x44444444,
+ 0xc4c4c4c4, 0xdededede, 0xe9e9e9e9, 0xcbcbcbcb,
+ 0x54545454, 0x7b7b7b7b, 0x94949494, 0x32323232,
+ 0xa6a6a6a6, 0xc2c2c2c2, 0x23232323, 0x3d3d3d3d,
+ 0xeeeeeeee, 0x4c4c4c4c, 0x95959595, 0xb0b0b0b,
+ 0x42424242, 0xfafafafa, 0xc3c3c3c3, 0x4e4e4e4e,
+ 0x8080808, 0x2e2e2e2e, 0xa1a1a1a1, 0x66666666,
+ 0x28282828, 0xd9d9d9d9, 0x24242424, 0xb2b2b2b2,
+ 0x76767676, 0x5b5b5b5b, 0xa2a2a2a2, 0x49494949,
+ 0x6d6d6d6d, 0x8b8b8b8b, 0xd1d1d1d1, 0x25252525,
+ 0x72727272, 0xf8f8f8f8, 0xf6f6f6f6, 0x64646464,
+ 0x86868686, 0x68686868, 0x98989898, 0x16161616,
+ 0xd4d4d4d4, 0xa4a4a4a4, 0x5c5c5c5c, 0xcccccccc,
+ 0x5d5d5d5d, 0x65656565, 0xb6b6b6b6, 0x92929292,
+ 0x6c6c6c6c, 0x70707070, 0x48484848, 0x50505050,
+ 0xfdfdfdfd, 0xedededed, 0xb9b9b9b9, 0xdadadada,
+ 0x5e5e5e5e, 0x15151515, 0x46464646, 0x57575757,
+ 0xa7a7a7a7, 0x8d8d8d8d, 0x9d9d9d9d, 0x84848484,
+ 0x90909090, 0xd8d8d8d8, 0xabababab, 0x0,
+ 0x8c8c8c8c, 0xbcbcbcbc, 0xd3d3d3d3, 0xa0a0a0a,
+ 0xf7f7f7f7, 0xe4e4e4e4, 0x58585858, 0x5050505,
+ 0xb8b8b8b8, 0xb3b3b3b3, 0x45454545, 0x6060606,
+ 0xd0d0d0d0, 0x2c2c2c2c, 0x1e1e1e1e, 0x8f8f8f8f,
+ 0xcacacaca, 0x3f3f3f3f, 0xf0f0f0f, 0x2020202,
+ 0xc1c1c1c1, 0xafafafaf, 0xbdbdbdbd, 0x3030303,
+ 0x1010101, 0x13131313, 0x8a8a8a8a, 0x6b6b6b6b,
+ 0x3a3a3a3a, 0x91919191, 0x11111111, 0x41414141,
+ 0x4f4f4f4f, 0x67676767, 0xdcdcdcdc, 0xeaeaeaea,
+ 0x97979797, 0xf2f2f2f2, 0xcfcfcfcf, 0xcececece,
+ 0xf0f0f0f0, 0xb4b4b4b4, 0xe6e6e6e6, 0x73737373,
+ 0x96969696, 0xacacacac, 0x74747474, 0x22222222,
+ 0xe7e7e7e7, 0xadadadad, 0x35353535, 0x85858585,
+ 0xe2e2e2e2, 0xf9f9f9f9, 0x37373737, 0xe8e8e8e8,
+ 0x1c1c1c1c, 0x75757575, 0xdfdfdfdf, 0x6e6e6e6e,
+ 0x47474747, 0xf1f1f1f1, 0x1a1a1a1a, 0x71717171,
+ 0x1d1d1d1d, 0x29292929, 0xc5c5c5c5, 0x89898989,
+ 0x6f6f6f6f, 0xb7b7b7b7, 0x62626262, 0xe0e0e0e,
+ 0xaaaaaaaa, 0x18181818, 0xbebebebe, 0x1b1b1b1b,
+ 0xfcfcfcfc, 0x56565656, 0x3e3e3e3e, 0x4b4b4b4b,
+ 0xc6c6c6c6, 0xd2d2d2d2, 0x79797979, 0x20202020,
+ 0x9a9a9a9a, 0xdbdbdbdb, 0xc0c0c0c0, 0xfefefefe,
+ 0x78787878, 0xcdcdcdcd, 0x5a5a5a5a, 0xf4f4f4f4,
+ 0x1f1f1f1f, 0xdddddddd, 0xa8a8a8a8, 0x33333333,
+ 0x88888888, 0x7070707, 0xc7c7c7c7, 0x31313131,
+ 0xb1b1b1b1, 0x12121212, 0x10101010, 0x59595959,
+ 0x27272727, 0x80808080, 0xecececec, 0x5f5f5f5f,
+ 0x60606060, 0x51515151, 0x7f7f7f7f, 0xa9a9a9a9,
+ 0x19191919, 0xb5b5b5b5, 0x4a4a4a4a, 0xd0d0d0d,
+ 0x2d2d2d2d, 0xe5e5e5e5, 0x7a7a7a7a, 0x9f9f9f9f,
+ 0x93939393, 0xc9c9c9c9, 0x9c9c9c9c, 0xefefefef,
+ 0xa0a0a0a0, 0xe0e0e0e0, 0x3b3b3b3b, 0x4d4d4d4d,
+ 0xaeaeaeae, 0x2a2a2a2a, 0xf5f5f5f5, 0xb0b0b0b0,
+ 0xc8c8c8c8, 0xebebebeb, 0xbbbbbbbb, 0x3c3c3c3c,
+ 0x83838383, 0x53535353, 0x99999999, 0x61616161,
+ 0x17171717, 0x2b2b2b2b, 0x4040404, 0x7e7e7e7e,
+ 0xbabababa, 0x77777777, 0xd6d6d6d6, 0x26262626,
+ 0xe1e1e1e1, 0x69696969, 0x14141414, 0x63636363,
+ 0x55555555, 0x21212121, 0xc0c0c0c, 0x7d7d7d7d
+};
+/* clang-format on */
+#endif /* CPU_RISC */
+
+#define gf2_8_field_polynomial 0x1B
+/*
+ * gf2_8_shift(z) returns the result of the GF(2^8) 'multiply by x'
+ * operation, using the field representation from AES; that is, the
+ * next gf2_8 value in the cyclic representation of that field. The
+ * value z should be an uint8_t.
+ */
+#define gf2_8_shift(z) \
+ (((z)&128) ? (((z) << 1) ^ gf2_8_field_polynomial) : ((z) << 1))
+
+/* aes internals */
+
+static void aes_128_expand_encryption_key(const uint8_t *key,
+ srtp_aes_expanded_key_t *expanded_key)
+{
+ int i;
+ uint8_t rc;
+
+ /* initialize round constant */
+ rc = 1;
+
+ expanded_key->num_rounds = 10;
+
+ v128_copy_octet_string(&expanded_key->round[0], key);
+
+#if 0
+ debug_print(srtp_mod_aes_icm,
+ "expanded key[0]: %s", v128_hex_string(&expanded_key->round[0]));
+#endif
+
+ /* loop over round keys */
+ for (i = 1; i < 11; i++) {
+ /* munge first word of round key */
+ expanded_key->round[i].v8[0] =
+ aes_sbox[expanded_key->round[i - 1].v8[13]] ^ rc;
+ expanded_key->round[i].v8[1] =
+ aes_sbox[expanded_key->round[i - 1].v8[14]];
+ expanded_key->round[i].v8[2] =
+ aes_sbox[expanded_key->round[i - 1].v8[15]];
+ expanded_key->round[i].v8[3] =
+ aes_sbox[expanded_key->round[i - 1].v8[12]];
+
+ expanded_key->round[i].v32[0] ^= expanded_key->round[i - 1].v32[0];
+
+ /* set remaining 32 bit words to the exor of the one previous with
+ * the one four words previous */
+
+ expanded_key->round[i].v32[1] =
+ expanded_key->round[i].v32[0] ^ expanded_key->round[i - 1].v32[1];
+
+ expanded_key->round[i].v32[2] =
+ expanded_key->round[i].v32[1] ^ expanded_key->round[i - 1].v32[2];
+
+ expanded_key->round[i].v32[3] =
+ expanded_key->round[i].v32[2] ^ expanded_key->round[i - 1].v32[3];
+
+#if 0
+ debug_print2(srtp_mod_aes_icm,
+ "expanded key[%d]: %s", i, v128_hex_string(&expanded_key->round[i]));
+#endif
+
+ /* modify round constant */
+ rc = gf2_8_shift(rc);
+ }
+}
+
+static void aes_256_expand_encryption_key(const unsigned char *key,
+ srtp_aes_expanded_key_t *expanded_key)
+{
+ int i;
+ uint8_t rc;
+
+ /* initialize round constant */
+ rc = 1;
+
+ expanded_key->num_rounds = 14;
+
+ v128_copy_octet_string(&expanded_key->round[0], key);
+ v128_copy_octet_string(&expanded_key->round[1], key + 16);
+
+#if 0
+ debug_print(srtp_mod_aes_icm,
+ "expanded key[0]: %s", v128_hex_string(&expanded_key->round[0]));
+ debug_print(srtp_mod_aes_icm,
+ "expanded key[1]: %s", v128_hex_string(&expanded_key->round[1]));
+#endif
+
+ /* loop over rest of round keys */
+ for (i = 2; i < 15; i++) {
+ /* munge first word of round key */
+ if ((i & 1) == 0) {
+ expanded_key->round[i].v8[0] =
+ aes_sbox[expanded_key->round[i - 1].v8[13]] ^ rc;
+ expanded_key->round[i].v8[1] =
+ aes_sbox[expanded_key->round[i - 1].v8[14]];
+ expanded_key->round[i].v8[2] =
+ aes_sbox[expanded_key->round[i - 1].v8[15]];
+ expanded_key->round[i].v8[3] =
+ aes_sbox[expanded_key->round[i - 1].v8[12]];
+
+ /* modify round constant */
+ rc = gf2_8_shift(rc);
+ } else {
+ expanded_key->round[i].v8[0] =
+ aes_sbox[expanded_key->round[i - 1].v8[12]];
+ expanded_key->round[i].v8[1] =
+ aes_sbox[expanded_key->round[i - 1].v8[13]];
+ expanded_key->round[i].v8[2] =
+ aes_sbox[expanded_key->round[i - 1].v8[14]];
+ expanded_key->round[i].v8[3] =
+ aes_sbox[expanded_key->round[i - 1].v8[15]];
+ }
+
+ expanded_key->round[i].v32[0] ^= expanded_key->round[i - 2].v32[0];
+
+ /* set remaining 32 bit words to the exor of the one previous with
+ * the one eight words previous */
+
+ expanded_key->round[i].v32[1] =
+ expanded_key->round[i].v32[0] ^ expanded_key->round[i - 2].v32[1];
+
+ expanded_key->round[i].v32[2] =
+ expanded_key->round[i].v32[1] ^ expanded_key->round[i - 2].v32[2];
+
+ expanded_key->round[i].v32[3] =
+ expanded_key->round[i].v32[2] ^ expanded_key->round[i - 2].v32[3];
+
+#if 0
+ debug_print2(srtp_mod_aes_icm,
+ "expanded key[%d]: %s", i, v128_hex_string(&expanded_key->round[i]));
+#endif
+ }
+}
+
+srtp_err_status_t srtp_aes_expand_encryption_key(
+ const uint8_t *key,
+ int key_len,
+ srtp_aes_expanded_key_t *expanded_key)
+{
+ if (key_len == 16) {
+ aes_128_expand_encryption_key(key, expanded_key);
+ return srtp_err_status_ok;
+ } else if (key_len == 24) {
+ /* AES-192 not yet supported */
+ return srtp_err_status_bad_param;
+ } else if (key_len == 32) {
+ aes_256_expand_encryption_key(key, expanded_key);
+ return srtp_err_status_ok;
+ } else {
+ return srtp_err_status_bad_param;
+ }
+}
+
+srtp_err_status_t srtp_aes_expand_decryption_key(
+ const uint8_t *key,
+ int key_len,
+ srtp_aes_expanded_key_t *expanded_key)
+{
+ int i;
+ srtp_err_status_t status;
+ int num_rounds = expanded_key->num_rounds;
+
+ status = srtp_aes_expand_encryption_key(key, key_len, expanded_key);
+ if (status) {
+ return status;
+ }
+
+ /* invert the order of the round keys */
+ for (i = 0; i < num_rounds / 2; i++) {
+ v128_t tmp;
+ v128_copy(&tmp, &expanded_key->round[num_rounds - i]);
+ v128_copy(&expanded_key->round[num_rounds - i],
+ &expanded_key->round[i]);
+ v128_copy(&expanded_key->round[i], &tmp);
+ }
+
+ /*
+ * apply the inverse mixColumn transform to the round keys (except
+ * for the first and the last)
+ *
+ * mixColumn is implemented by using the tables U0, U1, U2, U3,
+ * followed by the T4 table (which cancels out the use of the sbox
+ * in the U-tables)
+ */
+ for (i = 1; i < num_rounds; i++) {
+#ifdef CPU_RISC
+ uint32_t tmp;
+
+#ifdef WORDS_BIGENDIAN
+ /* clang-format off */
+ tmp = expanded_key->round[i].v32[0];
+ expanded_key->round[i].v32[0] =
+ U0[T4[(tmp >> 24) ] & 0xff] ^
+ U1[T4[(tmp >> 16) & 0xff] & 0xff] ^
+ U2[T4[(tmp >> 8) & 0xff] & 0xff] ^
+ U3[T4[(tmp) & 0xff] & 0xff];
+
+ tmp = expanded_key->round[i].v32[1];
+ expanded_key->round[i].v32[1] =
+ U0[T4[(tmp >> 24) ] & 0xff] ^
+ U1[T4[(tmp >> 16) & 0xff] & 0xff] ^
+ U2[T4[(tmp >> 8) & 0xff] & 0xff] ^
+ U3[T4[(tmp) & 0xff] & 0xff];
+
+ tmp = expanded_key->round[i].v32[2];
+ expanded_key->round[i].v32[2] =
+ U0[T4[(tmp >> 24) ] & 0xff] ^
+ U1[T4[(tmp >> 16) & 0xff] & 0xff] ^
+ U2[T4[(tmp >> 8) & 0xff] & 0xff] ^
+ U3[T4[(tmp) & 0xff] & 0xff];
+
+ tmp = expanded_key->round[i].v32[3];
+ expanded_key->round[i].v32[3] =
+ U0[T4[(tmp >> 24) ] & 0xff] ^
+ U1[T4[(tmp >> 16) & 0xff] & 0xff] ^
+ U2[T4[(tmp >> 8) & 0xff] & 0xff] ^
+ U3[T4[(tmp) & 0xff] & 0xff];
+#else
+ tmp = expanded_key->round[i].v32[0];
+ expanded_key->round[i].v32[0] =
+ U3[T4[(tmp >> 24) ] & 0xff] ^
+ U2[T4[(tmp >> 16) & 0xff] & 0xff] ^
+ U1[T4[(tmp >> 8) & 0xff] & 0xff] ^
+ U0[T4[(tmp) & 0xff] & 0xff];
+
+ tmp = expanded_key->round[i].v32[1];
+ expanded_key->round[i].v32[1] =
+ U3[T4[(tmp >> 24) ] & 0xff] ^
+ U2[T4[(tmp >> 16) & 0xff] & 0xff] ^
+ U1[T4[(tmp >> 8) & 0xff] & 0xff] ^
+ U0[T4[(tmp) & 0xff] & 0xff];
+
+ tmp = expanded_key->round[i].v32[2];
+ expanded_key->round[i].v32[2] =
+ U3[T4[(tmp >> 24) ] & 0xff] ^
+ U2[T4[(tmp >> 16) & 0xff] & 0xff] ^
+ U1[T4[(tmp >> 8) & 0xff] & 0xff] ^
+ U0[T4[(tmp) & 0xff] & 0xff];
+
+ tmp = expanded_key->round[i].v32[3];
+ expanded_key->round[i].v32[3] =
+ U3[T4[(tmp >> 24) ] & 0xff] ^
+ U2[T4[(tmp >> 16) & 0xff] & 0xff] ^
+ U1[T4[(tmp >> 8) & 0xff] & 0xff] ^
+ U0[T4[(tmp) & 0xff] & 0xff];
+/* clang-format on */
+#endif /* WORDS_BIGENDIAN */
+
+#else /* assume CPU_CISC */
+
+ uint32_t c0, c1, c2, c3;
+
+ c0 = U0[aes_sbox[expanded_key->round[i].v8[0]]] ^
+ U1[aes_sbox[expanded_key->round[i].v8[1]]] ^
+ U2[aes_sbox[expanded_key->round[i].v8[2]]] ^
+ U3[aes_sbox[expanded_key->round[i].v8[3]]];
+
+ c1 = U0[aes_sbox[expanded_key->round[i].v8[4]]] ^
+ U1[aes_sbox[expanded_key->round[i].v8[5]]] ^
+ U2[aes_sbox[expanded_key->round[i].v8[6]]] ^
+ U3[aes_sbox[expanded_key->round[i].v8[7]]];
+
+ c2 = U0[aes_sbox[expanded_key->round[i].v8[8]]] ^
+ U1[aes_sbox[expanded_key->round[i].v8[9]]] ^
+ U2[aes_sbox[expanded_key->round[i].v8[10]]] ^
+ U3[aes_sbox[expanded_key->round[i].v8[11]]];
+
+ c3 = U0[aes_sbox[expanded_key->round[i].v8[12]]] ^
+ U1[aes_sbox[expanded_key->round[i].v8[13]]] ^
+ U2[aes_sbox[expanded_key->round[i].v8[14]]] ^
+ U3[aes_sbox[expanded_key->round[i].v8[15]]];
+
+ expanded_key->round[i].v32[0] = c0;
+ expanded_key->round[i].v32[1] = c1;
+ expanded_key->round[i].v32[2] = c2;
+ expanded_key->round[i].v32[3] = c3;
+
+#endif
+ }
+
+ return srtp_err_status_ok;
+}
+
+#ifdef CPU_CISC
+
+static inline void aes_round(v128_t *state, const v128_t *round_key)
+{
+ uint32_t column0, column1, column2, column3;
+
+ /* compute the columns of the output square in terms of the octets
+ of state, using the tables T0, T1, T2, T3 */
+
+ column0 = T0[state->v8[0]] ^ T1[state->v8[5]] ^ T2[state->v8[10]] ^
+ T3[state->v8[15]];
+
+ column1 = T0[state->v8[4]] ^ T1[state->v8[9]] ^ T2[state->v8[14]] ^
+ T3[state->v8[3]];
+
+ column2 = T0[state->v8[8]] ^ T1[state->v8[13]] ^ T2[state->v8[2]] ^
+ T3[state->v8[7]];
+
+ column3 = T0[state->v8[12]] ^ T1[state->v8[1]] ^ T2[state->v8[6]] ^
+ T3[state->v8[11]];
+
+ state->v32[0] = column0 ^ round_key->v32[0];
+ state->v32[1] = column1 ^ round_key->v32[1];
+ state->v32[2] = column2 ^ round_key->v32[2];
+ state->v32[3] = column3 ^ round_key->v32[3];
+}
+
+static inline void aes_inv_round(v128_t *state, const v128_t *round_key)
+{
+ uint32_t column0, column1, column2, column3;
+
+ /* compute the columns of the output square in terms of the octets
+ of state, using the tables U0, U1, U2, U3 */
+
+ column0 = U0[state->v8[0]] ^ U1[state->v8[13]] ^ U2[state->v8[10]] ^
+ U3[state->v8[7]];
+
+ column1 = U0[state->v8[4]] ^ U1[state->v8[1]] ^ U2[state->v8[14]] ^
+ U3[state->v8[11]];
+
+ column2 = U0[state->v8[8]] ^ U1[state->v8[5]] ^ U2[state->v8[2]] ^
+ U3[state->v8[15]];
+
+ column3 = U0[state->v8[12]] ^ U1[state->v8[9]] ^ U2[state->v8[6]] ^
+ U3[state->v8[3]];
+
+ state->v32[0] = column0 ^ round_key->v32[0];
+ state->v32[1] = column1 ^ round_key->v32[1];
+ state->v32[2] = column2 ^ round_key->v32[2];
+ state->v32[3] = column3 ^ round_key->v32[3];
+}
+
+static inline void aes_final_round(v128_t *state, const v128_t *round_key)
+{
+ uint8_t tmp;
+
+ /* byte substitutions and row shifts */
+ /* first row - no shift */
+ state->v8[0] = aes_sbox[state->v8[0]];
+ state->v8[4] = aes_sbox[state->v8[4]];
+ state->v8[8] = aes_sbox[state->v8[8]];
+ state->v8[12] = aes_sbox[state->v8[12]];
+
+ /* second row - shift one left */
+ tmp = aes_sbox[state->v8[1]];
+ state->v8[1] = aes_sbox[state->v8[5]];
+ state->v8[5] = aes_sbox[state->v8[9]];
+ state->v8[9] = aes_sbox[state->v8[13]];
+ state->v8[13] = tmp;
+
+ /* third row - shift two left */
+ tmp = aes_sbox[state->v8[10]];
+ state->v8[10] = aes_sbox[state->v8[2]];
+ state->v8[2] = tmp;
+ tmp = aes_sbox[state->v8[14]];
+ state->v8[14] = aes_sbox[state->v8[6]];
+ state->v8[6] = tmp;
+
+ /* fourth row - shift three left */
+ tmp = aes_sbox[state->v8[15]];
+ state->v8[15] = aes_sbox[state->v8[11]];
+ state->v8[11] = aes_sbox[state->v8[7]];
+ state->v8[7] = aes_sbox[state->v8[3]];
+ state->v8[3] = tmp;
+
+ v128_xor_eq(state, round_key);
+}
+
+static inline void aes_inv_final_round(v128_t *state, const v128_t *round_key)
+{
+ uint8_t tmp;
+
+ /* byte substitutions and row shifts */
+ /* first row - no shift */
+ state->v8[0] = aes_inv_sbox[state->v8[0]];
+ state->v8[4] = aes_inv_sbox[state->v8[4]];
+ state->v8[8] = aes_inv_sbox[state->v8[8]];
+ state->v8[12] = aes_inv_sbox[state->v8[12]];
+
+ /* second row - shift one right */
+ tmp = aes_inv_sbox[state->v8[13]];
+ state->v8[13] = aes_inv_sbox[state->v8[9]];
+ state->v8[9] = aes_inv_sbox[state->v8[5]];
+ state->v8[5] = aes_inv_sbox[state->v8[1]];
+ state->v8[1] = tmp;
+
+ /* third row - shift two right */
+ tmp = aes_inv_sbox[state->v8[2]];
+ state->v8[2] = aes_inv_sbox[state->v8[10]];
+ state->v8[10] = tmp;
+ tmp = aes_inv_sbox[state->v8[6]];
+ state->v8[6] = aes_inv_sbox[state->v8[14]];
+ state->v8[14] = tmp;
+
+ /* fourth row - shift three right */
+ tmp = aes_inv_sbox[state->v8[3]];
+ state->v8[3] = aes_inv_sbox[state->v8[7]];
+ state->v8[7] = aes_inv_sbox[state->v8[11]];
+ state->v8[11] = aes_inv_sbox[state->v8[15]];
+ state->v8[15] = tmp;
+
+ v128_xor_eq(state, round_key);
+}
+
+#elif CPU_RISC
+
+static inline void aes_round(v128_t *state, const v128_t *round_key)
+{
+ uint32_t column0, column1, column2, column3;
+
+/* compute the columns of the output square in terms of the octets
+ of state, using the tables T0, T1, T2, T3 */
+#ifdef WORDS_BIGENDIAN
+ column0 = T0[state->v32[0] >> 24] ^ T1[(state->v32[1] >> 16) & 0xff] ^
+ T2[(state->v32[2] >> 8) & 0xff] ^ T3[state->v32[3] & 0xff];
+
+ column1 = T0[state->v32[1] >> 24] ^ T1[(state->v32[2] >> 16) & 0xff] ^
+ T2[(state->v32[3] >> 8) & 0xff] ^ T3[state->v32[0] & 0xff];
+
+ column2 = T0[state->v32[2] >> 24] ^ T1[(state->v32[3] >> 16) & 0xff] ^
+ T2[(state->v32[0] >> 8) & 0xff] ^ T3[state->v32[1] & 0xff];
+
+ column3 = T0[state->v32[3] >> 24] ^ T1[(state->v32[0] >> 16) & 0xff] ^
+ T2[(state->v32[1] >> 8) & 0xff] ^ T3[state->v32[2] & 0xff];
+#else
+ column0 = T0[state->v32[0] & 0xff] ^ T1[(state->v32[1] >> 8) & 0xff] ^
+ T2[(state->v32[2] >> 16) & 0xff] ^ T3[state->v32[3] >> 24];
+
+ column1 = T0[state->v32[1] & 0xff] ^ T1[(state->v32[2] >> 8) & 0xff] ^
+ T2[(state->v32[3] >> 16) & 0xff] ^ T3[state->v32[0] >> 24];
+
+ column2 = T0[state->v32[2] & 0xff] ^ T1[(state->v32[3] >> 8) & 0xff] ^
+ T2[(state->v32[0] >> 16) & 0xff] ^ T3[state->v32[1] >> 24];
+
+ column3 = T0[state->v32[3] & 0xff] ^ T1[(state->v32[0] >> 8) & 0xff] ^
+ T2[(state->v32[1] >> 16) & 0xff] ^ T3[state->v32[2] >> 24];
+#endif /* WORDS_BIGENDIAN */
+
+ state->v32[0] = column0 ^ round_key->v32[0];
+ state->v32[1] = column1 ^ round_key->v32[1];
+ state->v32[2] = column2 ^ round_key->v32[2];
+ state->v32[3] = column3 ^ round_key->v32[3];
+}
+
+static inline void aes_inv_round(v128_t *state, const v128_t *round_key)
+{
+ uint32_t column0, column1, column2, column3;
+
+/* compute the columns of the output square in terms of the octets
+ of state, using the tables U0, U1, U2, U3 */
+
+#ifdef WORDS_BIGENDIAN
+ column0 = U0[state->v32[0] >> 24] ^ U1[(state->v32[3] >> 16) & 0xff] ^
+ U2[(state->v32[2] >> 8) & 0xff] ^ U3[state->v32[1] & 0xff];
+
+ column1 = U0[state->v32[1] >> 24] ^ U1[(state->v32[0] >> 16) & 0xff] ^
+ U2[(state->v32[3] >> 8) & 0xff] ^ U3[state->v32[2] & 0xff];
+
+ column2 = U0[state->v32[2] >> 24] ^ U1[(state->v32[1] >> 16) & 0xff] ^
+ U2[(state->v32[0] >> 8) & 0xff] ^ U3[state->v32[3] & 0xff];
+
+ column3 = U0[state->v32[3] >> 24] ^ U1[(state->v32[2] >> 16) & 0xff] ^
+ U2[(state->v32[1] >> 8) & 0xff] ^ U3[state->v32[0] & 0xff];
+#else
+ column0 = U0[state->v32[0] & 0xff] ^ U1[(state->v32[3] >> 8) & 0xff] ^
+ U2[(state->v32[2] >> 16) & 0xff] ^
+ U3[(state->v32[1] >> 24) & 0xff];
+
+ column1 = U0[state->v32[1] & 0xff] ^ U1[(state->v32[0] >> 8) & 0xff] ^
+ U2[(state->v32[3] >> 16) & 0xff] ^
+ U3[(state->v32[2] >> 24) & 0xff];
+
+ column2 = U0[state->v32[2] & 0xff] ^ U1[(state->v32[1] >> 8) & 0xff] ^
+ U2[(state->v32[0] >> 16) & 0xff] ^
+ U3[(state->v32[3] >> 24) & 0xff];
+
+ column3 = U0[state->v32[3] & 0xff] ^ U1[(state->v32[2] >> 8) & 0xff] ^
+ U2[(state->v32[1] >> 16) & 0xff] ^
+ U3[(state->v32[0] >> 24) & 0xff];
+#endif /* WORDS_BIGENDIAN */
+
+ state->v32[0] = column0 ^ round_key->v32[0];
+ state->v32[1] = column1 ^ round_key->v32[1];
+ state->v32[2] = column2 ^ round_key->v32[2];
+ state->v32[3] = column3 ^ round_key->v32[3];
+}
+
+static inline void aes_final_round(v128_t *state, const v128_t *round_key)
+{
+ uint32_t tmp0, tmp1, tmp2, tmp3;
+
+#ifdef WORDS_BIGENDIAN
+ /* clang-format off */
+ tmp0 = (T4[(state->v32[0] >> 24)] & 0xff000000) ^
+ (T4[(state->v32[1] >> 16) & 0xff] & 0x00ff0000) ^
+ (T4[(state->v32[2] >> 8) & 0xff] & 0x0000ff00) ^
+ (T4[(state->v32[3] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[0];
+
+ tmp1 = (T4[(state->v32[1] >> 24)] & 0xff000000) ^
+ (T4[(state->v32[2] >> 16) & 0xff] & 0x00ff0000) ^
+ (T4[(state->v32[3] >> 8) & 0xff] & 0x0000ff00) ^
+ (T4[(state->v32[0] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[1];
+
+ tmp2 = (T4[(state->v32[2] >> 24)] & 0xff000000) ^
+ (T4[(state->v32[3] >> 16) & 0xff] & 0x00ff0000) ^
+ (T4[(state->v32[0] >> 8) & 0xff] & 0x0000ff00) ^
+ (T4[(state->v32[1] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[2];
+
+ tmp3 = (T4[(state->v32[3] >> 24)] & 0xff000000) ^
+ (T4[(state->v32[0] >> 16) & 0xff] & 0x00ff0000) ^
+ (T4[(state->v32[1] >> 8) & 0xff] & 0x0000ff00) ^
+ (T4[(state->v32[2] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[3];
+#else
+ tmp0 = (T4[(state->v32[3] >> 24)] & 0xff000000) ^
+ (T4[(state->v32[2] >> 16) & 0xff] & 0x00ff0000) ^
+ (T4[(state->v32[1] >> 8) & 0xff] & 0x0000ff00) ^
+ (T4[(state->v32[0] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[0];
+
+ tmp1 = (T4[(state->v32[0] >> 24)] & 0xff000000) ^
+ (T4[(state->v32[3] >> 16) & 0xff] & 0x00ff0000) ^
+ (T4[(state->v32[2] >> 8) & 0xff] & 0x0000ff00) ^
+ (T4[(state->v32[1] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[1];
+
+ tmp2 = (T4[(state->v32[1] >> 24)] & 0xff000000) ^
+ (T4[(state->v32[0] >> 16) & 0xff] & 0x00ff0000) ^
+ (T4[(state->v32[3] >> 8) & 0xff] & 0x0000ff00) ^
+ (T4[(state->v32[2] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[2];
+
+ tmp3 = (T4[(state->v32[2] >> 24)] & 0xff000000) ^
+ (T4[(state->v32[1] >> 16) & 0xff] & 0x00ff0000) ^
+ (T4[(state->v32[0] >> 8) & 0xff] & 0x0000ff00) ^
+ (T4[(state->v32[3] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[3];
+/* clang-format on */
+#endif /* WORDS_BIGENDIAN */
+
+ state->v32[0] = tmp0;
+ state->v32[1] = tmp1;
+ state->v32[2] = tmp2;
+ state->v32[3] = tmp3;
+}
+
+static inline void aes_inv_final_round(v128_t *state, const v128_t *round_key)
+{
+ uint32_t tmp0, tmp1, tmp2, tmp3;
+
+#ifdef WORDS_BIGENDIAN
+ /* clang-format off */
+ tmp0 = (U4[(state->v32[0] >> 24)] & 0xff000000) ^
+ (U4[(state->v32[3] >> 16) & 0xff] & 0x00ff0000) ^
+ (U4[(state->v32[2] >> 8) & 0xff] & 0x0000ff00) ^
+ (U4[(state->v32[1] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[0];
+
+ tmp1 = (U4[(state->v32[1] >> 24)] & 0xff000000) ^
+ (U4[(state->v32[0] >> 16) & 0xff] & 0x00ff0000) ^
+ (U4[(state->v32[3] >> 8) & 0xff] & 0x0000ff00) ^
+ (U4[(state->v32[2] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[1];
+
+ tmp2 = (U4[(state->v32[2] >> 24)] & 0xff000000) ^
+ (U4[(state->v32[1] >> 16) & 0xff] & 0x00ff0000) ^
+ (U4[(state->v32[0] >> 8) & 0xff] & 0x0000ff00) ^
+ (U4[(state->v32[3] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[2];
+
+ tmp3 = (U4[(state->v32[3] >> 24)] & 0xff000000) ^
+ (U4[(state->v32[2] >> 16) & 0xff] & 0x00ff0000) ^
+ (U4[(state->v32[1] >> 8) & 0xff] & 0x0000ff00) ^
+ (U4[(state->v32[0] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[3];
+#else
+ tmp0 = (U4[(state->v32[1] >> 24)] & 0xff000000) ^
+ (U4[(state->v32[2] >> 16) & 0xff] & 0x00ff0000) ^
+ (U4[(state->v32[3] >> 8) & 0xff] & 0x0000ff00) ^
+ (U4[(state->v32[0] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[0];
+
+ tmp1 = (U4[(state->v32[2] >> 24)] & 0xff000000) ^
+ (U4[(state->v32[3] >> 16) & 0xff] & 0x00ff0000) ^
+ (U4[(state->v32[0] >> 8) & 0xff] & 0x0000ff00) ^
+ (U4[(state->v32[1] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[1];
+
+ tmp2 = (U4[(state->v32[3] >> 24)] & 0xff000000) ^
+ (U4[(state->v32[0] >> 16) & 0xff] & 0x00ff0000) ^
+ (U4[(state->v32[1] >> 8) & 0xff] & 0x0000ff00) ^
+ (U4[(state->v32[2] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[2];
+
+ tmp3 = (U4[(state->v32[0] >> 24)] & 0xff000000) ^
+ (U4[(state->v32[1] >> 16) & 0xff] & 0x00ff0000) ^
+ (U4[(state->v32[2] >> 8) & 0xff] & 0x0000ff00) ^
+ (U4[(state->v32[3] ) & 0xff] & 0x000000ff) ^
+ round_key->v32[3];
+/* clang-format on */
+#endif /* WORDS_BIGENDIAN */
+
+ state->v32[0] = tmp0;
+ state->v32[1] = tmp1;
+ state->v32[2] = tmp2;
+ state->v32[3] = tmp3;
+}
+
+#elif CPU_16 /* assume 16-bit word size on processor */
+
+static inline void aes_round(v128_t *state, const v128_t *round_key)
+{
+ uint32_t column0, column1, column2, column3;
+ /* compute the columns of the output square in terms of the octets
+ of state, using the tables T0, T1, T2, T3 */
+
+ column0 = T0[state->v8[0]] ^ T1[state->v8[5]] ^ T2[state->v8[10]] ^
+ T3[state->v8[15]];
+
+ column1 = T0[state->v8[4]] ^ T1[state->v8[9]] ^ T2[state->v8[14]] ^
+ T3[state->v8[3]];
+
+ column2 = T0[state->v8[8]] ^ T1[state->v8[13]] ^ T2[state->v8[2]] ^
+ T3[state->v8[7]];
+
+ column3 = T0[state->v8[12]] ^ T1[state->v8[1]] ^ T2[state->v8[6]] ^
+ T3[state->v8[11]];
+
+ state->v32[0] = column0 ^ round_key->v32[0];
+ state->v32[1] = column1 ^ round_key->v32[1];
+ state->v32[2] = column2 ^ round_key->v32[2];
+ state->v32[3] = column3 ^ round_key->v32[3];
+}
+
+static inline void aes_inv_round(v128_t *state, const v128_t *round_key)
+{
+ uint32_t column0, column1, column2, column3;
+
+ /* compute the columns of the output square in terms of the octets
+ of state, using the tables U0, U1, U2, U3 */
+
+ column0 = U0[state->v8[0]] ^ U1[state->v8[5]] ^ U2[state->v8[10]] ^
+ U3[state->v8[15]];
+
+ column1 = U0[state->v8[4]] ^ U1[state->v8[9]] ^ U2[state->v8[14]] ^
+ U3[state->v8[3]];
+
+ column2 = U0[state->v8[8]] ^ U1[state->v8[13]] ^ U2[state->v8[2]] ^
+ U3[state->v8[7]];
+
+ column3 = U0[state->v8[12]] ^ U1[state->v8[1]] ^ U2[state->v8[6]] ^
+ U3[state->v8[11]];
+
+ state->v32[0] = column0 ^ round_key->v32[0];
+ state->v32[1] = column1 ^ round_key->v32[1];
+ state->v32[2] = column2 ^ round_key->v32[2];
+ state->v32[3] = column3 ^ round_key->v32[3];
+}
+
+static inline void aes_final_round(v128_t *state, const v128_t *round_key)
+{
+ uint8_t tmp;
+
+ /* byte substitutions and row shifts */
+ /* first row - no shift */
+ state->v8[0] = aes_sbox[state->v8[0]];
+ state->v8[4] = aes_sbox[state->v8[4]];
+ state->v8[8] = aes_sbox[state->v8[8]];
+ state->v8[12] = aes_sbox[state->v8[12]];
+
+ /* second row - shift one left */
+ tmp = aes_sbox[state->v8[1]];
+ state->v8[1] = aes_sbox[state->v8[5]];
+ state->v8[5] = aes_sbox[state->v8[9]];
+ state->v8[9] = aes_sbox[state->v8[13]];
+ state->v8[13] = tmp;
+
+ /* third row - shift two left */
+ tmp = aes_sbox[state->v8[10]];
+ state->v8[10] = aes_sbox[state->v8[2]];
+ state->v8[2] = tmp;
+ tmp = aes_sbox[state->v8[14]];
+ state->v8[14] = aes_sbox[state->v8[6]];
+ state->v8[6] = tmp;
+
+ /* fourth row - shift three left */
+ tmp = aes_sbox[state->v8[15]];
+ state->v8[15] = aes_sbox[state->v8[11]];
+ state->v8[11] = aes_sbox[state->v8[7]];
+ state->v8[7] = aes_sbox[state->v8[3]];
+ state->v8[3] = tmp;
+
+ v128_xor_eq(state, round_key);
+}
+
+static inline void aes_inv_final_round(v128_t *state, const v128_t *round_key)
+{
+ uint8_t tmp;
+
+ /* byte substitutions and row shifts */
+ /* first row - no shift */
+ state->v8[0] = aes_inv_sbox[state->v8[0]];
+ state->v8[4] = aes_inv_sbox[state->v8[4]];
+ state->v8[8] = aes_inv_sbox[state->v8[8]];
+ state->v8[12] = aes_inv_sbox[state->v8[12]];
+
+ /* second row - shift one left */
+ tmp = aes_inv_sbox[state->v8[1]];
+ state->v8[1] = aes_inv_sbox[state->v8[5]];
+ state->v8[5] = aes_inv_sbox[state->v8[9]];
+ state->v8[9] = aes_inv_sbox[state->v8[13]];
+ state->v8[13] = tmp;
+
+ /* third row - shift two left */
+ tmp = aes_inv_sbox[state->v8[10]];
+ state->v8[10] = aes_inv_sbox[state->v8[2]];
+ state->v8[2] = tmp;
+ tmp = aes_inv_sbox[state->v8[14]];
+ state->v8[14] = aes_inv_sbox[state->v8[6]];
+ state->v8[6] = tmp;
+
+ /* fourth row - shift three left */
+ tmp = aes_inv_sbox[state->v8[15]];
+ state->v8[15] = aes_inv_sbox[state->v8[11]];
+ state->v8[11] = aes_inv_sbox[state->v8[7]];
+ state->v8[7] = aes_inv_sbox[state->v8[3]];
+ state->v8[3] = tmp;
+
+ v128_xor_eq(state, round_key);
+}
+
+#endif /* CPU type */
+
+void srtp_aes_encrypt(v128_t *plaintext, const srtp_aes_expanded_key_t *exp_key)
+{
+ /* add in the subkey */
+ v128_xor_eq(plaintext, &exp_key->round[0]);
+
+ /* now do the rounds */
+ aes_round(plaintext, &exp_key->round[1]);
+ aes_round(plaintext, &exp_key->round[2]);
+ aes_round(plaintext, &exp_key->round[3]);
+ aes_round(plaintext, &exp_key->round[4]);
+ aes_round(plaintext, &exp_key->round[5]);
+ aes_round(plaintext, &exp_key->round[6]);
+ aes_round(plaintext, &exp_key->round[7]);
+ aes_round(plaintext, &exp_key->round[8]);
+ aes_round(plaintext, &exp_key->round[9]);
+ if (exp_key->num_rounds == 10) {
+ aes_final_round(plaintext, &exp_key->round[10]);
+ } else if (exp_key->num_rounds == 12) {
+ aes_round(plaintext, &exp_key->round[10]);
+ aes_round(plaintext, &exp_key->round[11]);
+ aes_final_round(plaintext, &exp_key->round[12]);
+ } else if (exp_key->num_rounds == 14) {
+ aes_round(plaintext, &exp_key->round[10]);
+ aes_round(plaintext, &exp_key->round[11]);
+ aes_round(plaintext, &exp_key->round[12]);
+ aes_round(plaintext, &exp_key->round[13]);
+ aes_final_round(plaintext, &exp_key->round[14]);
+ }
+}
+
+void srtp_aes_decrypt(v128_t *plaintext, const srtp_aes_expanded_key_t *exp_key)
+{
+ /* add in the subkey */
+ v128_xor_eq(plaintext, &exp_key->round[0]);
+
+ /* now do the rounds */
+ aes_inv_round(plaintext, &exp_key->round[1]);
+ aes_inv_round(plaintext, &exp_key->round[2]);
+ aes_inv_round(plaintext, &exp_key->round[3]);
+ aes_inv_round(plaintext, &exp_key->round[4]);
+ aes_inv_round(plaintext, &exp_key->round[5]);
+ aes_inv_round(plaintext, &exp_key->round[6]);
+ aes_inv_round(plaintext, &exp_key->round[7]);
+ aes_inv_round(plaintext, &exp_key->round[8]);
+ aes_inv_round(plaintext, &exp_key->round[9]);
+ if (exp_key->num_rounds == 10) {
+ aes_inv_final_round(plaintext, &exp_key->round[10]);
+ } else if (exp_key->num_rounds == 12) {
+ aes_inv_round(plaintext, &exp_key->round[10]);
+ aes_inv_round(plaintext, &exp_key->round[11]);
+ aes_inv_final_round(plaintext, &exp_key->round[12]);
+ } else if (exp_key->num_rounds == 14) {
+ aes_inv_round(plaintext, &exp_key->round[10]);
+ aes_inv_round(plaintext, &exp_key->round[11]);
+ aes_inv_round(plaintext, &exp_key->round[12]);
+ aes_inv_round(plaintext, &exp_key->round[13]);
+ aes_inv_final_round(plaintext, &exp_key->round[14]);
+ }
+}
diff --git a/netwerk/srtp/src/crypto/cipher/aes_gcm_nss.c b/netwerk/srtp/src/crypto/cipher/aes_gcm_nss.c
new file mode 100644
index 0000000000..2be2ce932d
--- /dev/null
+++ b/netwerk/srtp/src/crypto/cipher/aes_gcm_nss.c
@@ -0,0 +1,597 @@
+/*
+ * aes_gcm_nss.c
+ *
+ * AES Galois Counter Mode
+ *
+ * Richard L. Barnes
+ * Cisco Systems, Inc.
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2013-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "aes_gcm.h"
+#include "alloc.h"
+#include "err.h" /* for srtp_debug */
+#include "crypto_types.h"
+#include "cipher_types.h"
+#include <nss.h>
+#include <secerr.h>
+#include <nspr.h>
+
+srtp_debug_module_t srtp_mod_aes_gcm = {
+ 0, /* debugging is off by default */
+ "aes gcm nss" /* printable module name */
+};
+
+/*
+ * For now we only support 8 and 16 octet tags. The spec allows for
+ * optional 12 byte tag, which may be supported in the future.
+ */
+#define GCM_IV_LEN 12
+#define GCM_AUTH_TAG_LEN 16
+#define GCM_AUTH_TAG_LEN_8 8
+
+/*
+ * This function allocates a new instance of this crypto engine.
+ * The key_len parameter should be one of 28 or 44 for
+ * AES-128-GCM or AES-256-GCM respectively. Note that the
+ * key length includes the 14 byte salt value that is used when
+ * initializing the KDF.
+ */
+static srtp_err_status_t srtp_aes_gcm_nss_alloc(srtp_cipher_t **c,
+ int key_len,
+ int tlen)
+{
+ srtp_aes_gcm_ctx_t *gcm;
+
+ debug_print(srtp_mod_aes_gcm, "allocating cipher with key length %d",
+ key_len);
+ debug_print(srtp_mod_aes_gcm, "allocating cipher with tag length %d", tlen);
+
+ /*
+ * Verify the key_len is valid for one of: AES-128/256
+ */
+ if (key_len != SRTP_AES_GCM_128_KEY_LEN_WSALT &&
+ key_len != SRTP_AES_GCM_256_KEY_LEN_WSALT) {
+ return (srtp_err_status_bad_param);
+ }
+
+ if (tlen != GCM_AUTH_TAG_LEN && tlen != GCM_AUTH_TAG_LEN_8) {
+ return (srtp_err_status_bad_param);
+ }
+
+ /* Initialize NSS */
+ if (!NSS_IsInitialized() && NSS_NoDB_Init(NULL) != SECSuccess) {
+ return (srtp_err_status_cipher_fail);
+ }
+
+ /* allocate memory a cipher of type aes_gcm */
+ *c = (srtp_cipher_t *)srtp_crypto_alloc(sizeof(srtp_cipher_t));
+ if (*c == NULL) {
+ return (srtp_err_status_alloc_fail);
+ }
+
+ gcm = (srtp_aes_gcm_ctx_t *)srtp_crypto_alloc(sizeof(srtp_aes_gcm_ctx_t));
+ if (gcm == NULL) {
+ srtp_crypto_free(*c);
+ *c = NULL;
+ return (srtp_err_status_alloc_fail);
+ }
+
+ /* set pointers */
+ (*c)->state = gcm;
+
+ /* setup cipher attributes */
+ switch (key_len) {
+ case SRTP_AES_GCM_128_KEY_LEN_WSALT:
+ (*c)->type = &srtp_aes_gcm_128;
+ (*c)->algorithm = SRTP_AES_GCM_128;
+ gcm->key_size = SRTP_AES_128_KEY_LEN;
+ gcm->tag_size = tlen;
+ gcm->params.ulTagBits = 8 * tlen;
+ break;
+ case SRTP_AES_GCM_256_KEY_LEN_WSALT:
+ (*c)->type = &srtp_aes_gcm_256;
+ (*c)->algorithm = SRTP_AES_GCM_256;
+ gcm->key_size = SRTP_AES_256_KEY_LEN;
+ gcm->tag_size = tlen;
+ gcm->params.ulTagBits = 8 * tlen;
+ break;
+ default:
+ /* this should never hit, but to be sure... */
+ return (srtp_err_status_bad_param);
+ }
+
+ /* set key size and tag size*/
+ (*c)->key_len = key_len;
+
+ return (srtp_err_status_ok);
+}
+
+/*
+ * This function deallocates a GCM session
+ */
+static srtp_err_status_t srtp_aes_gcm_nss_dealloc(srtp_cipher_t *c)
+{
+ srtp_aes_gcm_ctx_t *ctx;
+
+ ctx = (srtp_aes_gcm_ctx_t *)c->state;
+ if (ctx) {
+ /* release NSS resources */
+ if (ctx->key) {
+ PK11_FreeSymKey(ctx->key);
+ }
+
+ /* zeroize the key material */
+ octet_string_set_to_zero(ctx, sizeof(srtp_aes_gcm_ctx_t));
+ srtp_crypto_free(ctx);
+ }
+
+ /* free memory */
+ srtp_crypto_free(c);
+
+ return (srtp_err_status_ok);
+}
+
+/*
+ * aes_gcm_nss_context_init(...) initializes the aes_gcm_context
+ * using the value in key[].
+ *
+ * the key is the secret key
+ */
+static srtp_err_status_t srtp_aes_gcm_nss_context_init(void *cv,
+ const uint8_t *key)
+{
+ srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv;
+
+ c->dir = srtp_direction_any;
+
+ debug_print(srtp_mod_aes_gcm, "key: %s",
+ srtp_octet_string_hex_string(key, c->key_size));
+
+ if (c->key) {
+ PK11_FreeSymKey(c->key);
+ c->key = NULL;
+ }
+
+ PK11SlotInfo *slot = PK11_GetBestSlot(CKM_AES_GCM, NULL);
+ if (!slot) {
+ return (srtp_err_status_cipher_fail);
+ }
+
+ SECItem key_item = { siBuffer, (unsigned char *)key, c->key_size };
+ c->key = PK11_ImportSymKey(slot, CKM_AES_GCM, PK11_OriginUnwrap,
+ CKA_ENCRYPT, &key_item, NULL);
+ PK11_FreeSlot(slot);
+
+ if (!c->key) {
+ return (srtp_err_status_cipher_fail);
+ }
+
+ return (srtp_err_status_ok);
+}
+
+/*
+ * aes_gcm_nss_set_iv(c, iv) sets the counter value to the exor of iv with
+ * the offset
+ */
+static srtp_err_status_t srtp_aes_gcm_nss_set_iv(
+ void *cv,
+ uint8_t *iv,
+ srtp_cipher_direction_t direction)
+{
+ srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv;
+
+ if (direction != srtp_direction_encrypt &&
+ direction != srtp_direction_decrypt) {
+ return (srtp_err_status_bad_param);
+ }
+ c->dir = direction;
+
+ debug_print(srtp_mod_aes_gcm, "setting iv: %s",
+ srtp_octet_string_hex_string(iv, GCM_IV_LEN));
+
+ memcpy(c->iv, iv, GCM_IV_LEN);
+
+ return (srtp_err_status_ok);
+}
+
+/*
+ * This function processes the AAD
+ *
+ * Parameters:
+ * c Crypto context
+ * aad Additional data to process for AEAD cipher suites
+ * aad_len length of aad buffer
+ */
+static srtp_err_status_t srtp_aes_gcm_nss_set_aad(void *cv,
+ const uint8_t *aad,
+ uint32_t aad_len)
+{
+ srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv;
+
+ debug_print(srtp_mod_aes_gcm, "setting AAD: %s",
+ srtp_octet_string_hex_string(aad, aad_len));
+
+ if (aad_len + c->aad_size > MAX_AD_SIZE) {
+ return srtp_err_status_bad_param;
+ }
+
+ memcpy(c->aad + c->aad_size, aad, aad_len);
+ c->aad_size += aad_len;
+
+ return (srtp_err_status_ok);
+}
+
+static srtp_err_status_t srtp_aes_gcm_nss_do_crypto(void *cv,
+ int encrypt,
+ unsigned char *buf,
+ unsigned int *enc_len)
+{
+ srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv;
+
+ c->params.pIv = c->iv;
+ c->params.ulIvLen = GCM_IV_LEN;
+ c->params.ulIvBits = GCM_IV_LEN * 8;
+ c->params.pAAD = c->aad;
+ c->params.ulAADLen = c->aad_size;
+
+ // Reset AAD
+ c->aad_size = 0;
+
+ int rv;
+ SECItem param = { siBuffer, (unsigned char *)&c->params,
+ sizeof(CK_GCM_PARAMS) };
+ if (encrypt) {
+ rv = PK11_Encrypt(c->key, CKM_AES_GCM, &param, buf, enc_len,
+ *enc_len + 16, buf, *enc_len);
+ } else {
+ rv = PK11_Decrypt(c->key, CKM_AES_GCM, &param, buf, enc_len, *enc_len,
+ buf, *enc_len);
+ }
+
+ srtp_err_status_t status = (srtp_err_status_ok);
+ if (rv != SECSuccess) {
+ status = (srtp_err_status_cipher_fail);
+ }
+
+ return status;
+}
+
+/*
+ * This function encrypts a buffer using AES GCM mode
+ *
+ * XXX(rlb@ipv.sx): We're required to break off and cache the tag
+ * here, because the get_tag() method is separate and the tests expect
+ * encrypt() not to change the size of the plaintext. It might be
+ * good to update the calling API so that this is cleaner.
+ *
+ * Parameters:
+ * c Crypto context
+ * buf data to encrypt
+ * enc_len length of encrypt buffer
+ */
+static srtp_err_status_t srtp_aes_gcm_nss_encrypt(void *cv,
+ unsigned char *buf,
+ unsigned int *enc_len)
+{
+ srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv;
+
+ // When we get a non-NULL buffer, we know that the caller is
+ // prepared to also take the tag. When we get a NULL buffer,
+ // even though there's no data, we need to give NSS a buffer
+ // where it can write the tag. We can't just use c->tag because
+ // memcpy has undefined behavior on overlapping ranges.
+ unsigned char tagbuf[16];
+ unsigned char *non_null_buf = buf;
+ if (!non_null_buf && (*enc_len == 0)) {
+ non_null_buf = tagbuf;
+ } else if (!non_null_buf) {
+ return srtp_err_status_bad_param;
+ }
+
+ srtp_err_status_t status =
+ srtp_aes_gcm_nss_do_crypto(cv, 1, non_null_buf, enc_len);
+ if (status != srtp_err_status_ok) {
+ return status;
+ }
+
+ memcpy(c->tag, non_null_buf + (*enc_len - c->tag_size), c->tag_size);
+ *enc_len -= c->tag_size;
+ return srtp_err_status_ok;
+}
+
+/*
+ * This function calculates and returns the GCM tag for a given context.
+ * This should be called after encrypting the data. The *len value
+ * is increased by the tag size. The caller must ensure that *buf has
+ * enough room to accept the appended tag.
+ *
+ * Parameters:
+ * c Crypto context
+ * buf data to encrypt
+ * len length of encrypt buffer
+ */
+static srtp_err_status_t srtp_aes_gcm_nss_get_tag(void *cv,
+ uint8_t *buf,
+ uint32_t *len)
+{
+ srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv;
+ *len = c->tag_size;
+ memcpy(buf, c->tag, c->tag_size);
+ return (srtp_err_status_ok);
+}
+
+/*
+ * This function decrypts a buffer using AES GCM mode
+ *
+ * Parameters:
+ * c Crypto context
+ * buf data to encrypt
+ * enc_len length of encrypt buffer
+ */
+static srtp_err_status_t srtp_aes_gcm_nss_decrypt(void *cv,
+ unsigned char *buf,
+ unsigned int *enc_len)
+{
+ srtp_err_status_t status = srtp_aes_gcm_nss_do_crypto(cv, 0, buf, enc_len);
+ if (status != srtp_err_status_ok) {
+ int err = PR_GetError();
+ if (err == SEC_ERROR_BAD_DATA) {
+ status = srtp_err_status_auth_fail;
+ }
+ }
+
+ return status;
+}
+
+/*
+ * Name of this crypto engine
+ */
+static const char srtp_aes_gcm_128_nss_description[] = "AES-128 GCM using NSS";
+static const char srtp_aes_gcm_256_nss_description[] = "AES-256 GCM using NSS";
+
+/*
+ * KAT values for AES self-test. These
+ * values we're derived from independent test code
+ * using OpenSSL.
+ */
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_0_key[SRTP_AES_GCM_128_KEY_LEN_WSALT] = {
+ 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+ 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c,
+};
+/* clang-format on */
+
+/* clang-format off */
+static uint8_t srtp_aes_gcm_test_case_0_iv[12] = {
+ 0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
+ 0xde, 0xca, 0xf8, 0x88
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_0_plaintext[60] = {
+ 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+ 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+ 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+ 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+ 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+ 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+ 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+ 0xba, 0x63, 0x7b, 0x39
+};
+
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_0_aad[20] = {
+ 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xab, 0xad, 0xda, 0xd2
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_0_ciphertext[76] = {
+ 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24,
+ 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c,
+ 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0,
+ 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e,
+ 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c,
+ 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05,
+ 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
+ 0x3d, 0x58, 0xe0, 0x91,
+ /* the last 16 bytes are the tag */
+ 0x5b, 0xc9, 0x4f, 0xbc, 0x32, 0x21, 0xa5, 0xdb,
+ 0x94, 0xfa, 0xe9, 0x5a, 0xe7, 0x12, 0x1a, 0x47,
+};
+/* clang-format on */
+
+static const srtp_cipher_test_case_t srtp_aes_gcm_test_case_0a = {
+ SRTP_AES_GCM_128_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_gcm_test_case_0_key, /* key */
+ srtp_aes_gcm_test_case_0_iv, /* packet index */
+ 60, /* octets in plaintext */
+ srtp_aes_gcm_test_case_0_plaintext, /* plaintext */
+ 68, /* octets in ciphertext */
+ srtp_aes_gcm_test_case_0_ciphertext, /* ciphertext + tag */
+ 20, /* octets in AAD */
+ srtp_aes_gcm_test_case_0_aad, /* AAD */
+ GCM_AUTH_TAG_LEN_8, /* */
+ NULL /* pointer to next testcase */
+};
+
+static const srtp_cipher_test_case_t srtp_aes_gcm_test_case_0 = {
+ SRTP_AES_GCM_128_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_gcm_test_case_0_key, /* key */
+ srtp_aes_gcm_test_case_0_iv, /* packet index */
+ 60, /* octets in plaintext */
+ srtp_aes_gcm_test_case_0_plaintext, /* plaintext */
+ 76, /* octets in ciphertext */
+ srtp_aes_gcm_test_case_0_ciphertext, /* ciphertext + tag */
+ 20, /* octets in AAD */
+ srtp_aes_gcm_test_case_0_aad, /* AAD */
+ GCM_AUTH_TAG_LEN, /* */
+ &srtp_aes_gcm_test_case_0a /* pointer to next testcase */
+};
+
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_1_key[SRTP_AES_GCM_256_KEY_LEN_WSALT] = {
+ 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+ 0xa5, 0x59, 0x09, 0xc5, 0x54, 0x66, 0x93, 0x1c,
+ 0xaf, 0xf5, 0x26, 0x9a, 0x21, 0xd5, 0x14, 0xb2,
+ 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c,
+};
+/* clang-format on */
+
+/* clang-format off */
+static uint8_t srtp_aes_gcm_test_case_1_iv[12] = {
+ 0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
+ 0xde, 0xca, 0xf8, 0x88
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_1_plaintext[60] = {
+ 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+ 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+ 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+ 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+ 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+ 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+ 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+ 0xba, 0x63, 0x7b, 0x39
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_1_aad[20] = {
+ 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xab, 0xad, 0xda, 0xd2
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_1_ciphertext[76] = {
+ 0x0b, 0x11, 0xcf, 0xaf, 0x68, 0x4d, 0xae, 0x46,
+ 0xc7, 0x90, 0xb8, 0x8e, 0xb7, 0x6a, 0x76, 0x2a,
+ 0x94, 0x82, 0xca, 0xab, 0x3e, 0x39, 0xd7, 0x86,
+ 0x1b, 0xc7, 0x93, 0xed, 0x75, 0x7f, 0x23, 0x5a,
+ 0xda, 0xfd, 0xd3, 0xe2, 0x0e, 0x80, 0x87, 0xa9,
+ 0x6d, 0xd7, 0xe2, 0x6a, 0x7d, 0x5f, 0xb4, 0x80,
+ 0xef, 0xef, 0xc5, 0x29, 0x12, 0xd1, 0xaa, 0x10,
+ 0x09, 0xc9, 0x86, 0xc1,
+ /* the last 16 bytes are the tag */
+ 0x45, 0xbc, 0x03, 0xe6, 0xe1, 0xac, 0x0a, 0x9f,
+ 0x81, 0xcb, 0x8e, 0x5b, 0x46, 0x65, 0x63, 0x1d,
+};
+/* clang-format on */
+
+static const srtp_cipher_test_case_t srtp_aes_gcm_test_case_1a = {
+ SRTP_AES_GCM_256_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_gcm_test_case_1_key, /* key */
+ srtp_aes_gcm_test_case_1_iv, /* packet index */
+ 60, /* octets in plaintext */
+ srtp_aes_gcm_test_case_1_plaintext, /* plaintext */
+ 68, /* octets in ciphertext */
+ srtp_aes_gcm_test_case_1_ciphertext, /* ciphertext + tag */
+ 20, /* octets in AAD */
+ srtp_aes_gcm_test_case_1_aad, /* AAD */
+ GCM_AUTH_TAG_LEN_8, /* */
+ NULL /* pointer to next testcase */
+};
+
+static const srtp_cipher_test_case_t srtp_aes_gcm_test_case_1 = {
+ SRTP_AES_GCM_256_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_gcm_test_case_1_key, /* key */
+ srtp_aes_gcm_test_case_1_iv, /* packet index */
+ 60, /* octets in plaintext */
+ srtp_aes_gcm_test_case_1_plaintext, /* plaintext */
+ 76, /* octets in ciphertext */
+ srtp_aes_gcm_test_case_1_ciphertext, /* ciphertext + tag */
+ 20, /* octets in AAD */
+ srtp_aes_gcm_test_case_1_aad, /* AAD */
+ GCM_AUTH_TAG_LEN, /* */
+ &srtp_aes_gcm_test_case_1a /* pointer to next testcase */
+};
+
+/*
+ * This is the vector function table for this crypto engine.
+ */
+/* clang-format off */
+const srtp_cipher_type_t srtp_aes_gcm_128 = {
+ srtp_aes_gcm_nss_alloc,
+ srtp_aes_gcm_nss_dealloc,
+ srtp_aes_gcm_nss_context_init,
+ srtp_aes_gcm_nss_set_aad,
+ srtp_aes_gcm_nss_encrypt,
+ srtp_aes_gcm_nss_decrypt,
+ srtp_aes_gcm_nss_set_iv,
+ srtp_aes_gcm_nss_get_tag,
+ srtp_aes_gcm_128_nss_description,
+ &srtp_aes_gcm_test_case_0,
+ SRTP_AES_GCM_128
+};
+/* clang-format on */
+
+/*
+ * This is the vector function table for this crypto engine.
+ */
+/* clang-format off */
+const srtp_cipher_type_t srtp_aes_gcm_256 = {
+ srtp_aes_gcm_nss_alloc,
+ srtp_aes_gcm_nss_dealloc,
+ srtp_aes_gcm_nss_context_init,
+ srtp_aes_gcm_nss_set_aad,
+ srtp_aes_gcm_nss_encrypt,
+ srtp_aes_gcm_nss_decrypt,
+ srtp_aes_gcm_nss_set_iv,
+ srtp_aes_gcm_nss_get_tag,
+ srtp_aes_gcm_256_nss_description,
+ &srtp_aes_gcm_test_case_1,
+ SRTP_AES_GCM_256
+};
+/* clang-format on */
diff --git a/netwerk/srtp/src/crypto/cipher/aes_gcm_ossl.c b/netwerk/srtp/src/crypto/cipher/aes_gcm_ossl.c
new file mode 100644
index 0000000000..578cad5ee9
--- /dev/null
+++ b/netwerk/srtp/src/crypto/cipher/aes_gcm_ossl.c
@@ -0,0 +1,582 @@
+/*
+ * aes_gcm_ossl.c
+ *
+ * AES Galois Counter Mode
+ *
+ * John A. Foley
+ * Cisco Systems, Inc.
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2013-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <openssl/evp.h>
+#include "aes_gcm.h"
+#include "alloc.h"
+#include "err.h" /* for srtp_debug */
+#include "crypto_types.h"
+#include "cipher_types.h"
+
+srtp_debug_module_t srtp_mod_aes_gcm = {
+ 0, /* debugging is off by default */
+ "aes gcm" /* printable module name */
+};
+
+/*
+ * For now we only support 8 and 16 octet tags. The spec allows for
+ * optional 12 byte tag, which may be supported in the future.
+ */
+#define GCM_AUTH_TAG_LEN 16
+#define GCM_AUTH_TAG_LEN_8 8
+
+/*
+ * This function allocates a new instance of this crypto engine.
+ * The key_len parameter should be one of 28 or 44 for
+ * AES-128-GCM or AES-256-GCM respectively. Note that the
+ * key length includes the 14 byte salt value that is used when
+ * initializing the KDF.
+ */
+static srtp_err_status_t srtp_aes_gcm_openssl_alloc(srtp_cipher_t **c,
+ int key_len,
+ int tlen)
+{
+ srtp_aes_gcm_ctx_t *gcm;
+
+ debug_print(srtp_mod_aes_gcm, "allocating cipher with key length %d",
+ key_len);
+ debug_print(srtp_mod_aes_gcm, "allocating cipher with tag length %d", tlen);
+
+ /*
+ * Verify the key_len is valid for one of: AES-128/256
+ */
+ if (key_len != SRTP_AES_GCM_128_KEY_LEN_WSALT &&
+ key_len != SRTP_AES_GCM_256_KEY_LEN_WSALT) {
+ return (srtp_err_status_bad_param);
+ }
+
+ if (tlen != GCM_AUTH_TAG_LEN && tlen != GCM_AUTH_TAG_LEN_8) {
+ return (srtp_err_status_bad_param);
+ }
+
+ /* allocate memory a cipher of type aes_gcm */
+ *c = (srtp_cipher_t *)srtp_crypto_alloc(sizeof(srtp_cipher_t));
+ if (*c == NULL) {
+ return (srtp_err_status_alloc_fail);
+ }
+
+ gcm = (srtp_aes_gcm_ctx_t *)srtp_crypto_alloc(sizeof(srtp_aes_gcm_ctx_t));
+ if (gcm == NULL) {
+ srtp_crypto_free(*c);
+ *c = NULL;
+ return (srtp_err_status_alloc_fail);
+ }
+
+ gcm->ctx = EVP_CIPHER_CTX_new();
+ if (gcm->ctx == NULL) {
+ srtp_crypto_free(gcm);
+ srtp_crypto_free(*c);
+ *c = NULL;
+ return srtp_err_status_alloc_fail;
+ }
+
+ /* set pointers */
+ (*c)->state = gcm;
+
+ /* setup cipher attributes */
+ switch (key_len) {
+ case SRTP_AES_GCM_128_KEY_LEN_WSALT:
+ (*c)->type = &srtp_aes_gcm_128;
+ (*c)->algorithm = SRTP_AES_GCM_128;
+ gcm->key_size = SRTP_AES_128_KEY_LEN;
+ gcm->tag_len = tlen;
+ break;
+ case SRTP_AES_GCM_256_KEY_LEN_WSALT:
+ (*c)->type = &srtp_aes_gcm_256;
+ (*c)->algorithm = SRTP_AES_GCM_256;
+ gcm->key_size = SRTP_AES_256_KEY_LEN;
+ gcm->tag_len = tlen;
+ break;
+ }
+
+ /* set key size */
+ (*c)->key_len = key_len;
+
+ return (srtp_err_status_ok);
+}
+
+/*
+ * This function deallocates a GCM session
+ */
+static srtp_err_status_t srtp_aes_gcm_openssl_dealloc(srtp_cipher_t *c)
+{
+ srtp_aes_gcm_ctx_t *ctx;
+
+ ctx = (srtp_aes_gcm_ctx_t *)c->state;
+ if (ctx) {
+ EVP_CIPHER_CTX_free(ctx->ctx);
+ /* zeroize the key material */
+ octet_string_set_to_zero(ctx, sizeof(srtp_aes_gcm_ctx_t));
+ srtp_crypto_free(ctx);
+ }
+
+ /* free memory */
+ srtp_crypto_free(c);
+
+ return (srtp_err_status_ok);
+}
+
+/*
+ * aes_gcm_openssl_context_init(...) initializes the aes_gcm_context
+ * using the value in key[].
+ *
+ * the key is the secret key
+ */
+static srtp_err_status_t srtp_aes_gcm_openssl_context_init(void *cv,
+ const uint8_t *key)
+{
+ srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv;
+ const EVP_CIPHER *evp;
+
+ c->dir = srtp_direction_any;
+
+ debug_print(srtp_mod_aes_gcm, "key: %s",
+ srtp_octet_string_hex_string(key, c->key_size));
+
+ switch (c->key_size) {
+ case SRTP_AES_256_KEY_LEN:
+ evp = EVP_aes_256_gcm();
+ break;
+ case SRTP_AES_128_KEY_LEN:
+ evp = EVP_aes_128_gcm();
+ break;
+ default:
+ return (srtp_err_status_bad_param);
+ break;
+ }
+
+ if (!EVP_CipherInit_ex(c->ctx, evp, NULL, key, NULL, 0)) {
+ return (srtp_err_status_init_fail);
+ }
+
+ return (srtp_err_status_ok);
+}
+
+/*
+ * aes_gcm_openssl_set_iv(c, iv) sets the counter value to the exor of iv with
+ * the offset
+ */
+static srtp_err_status_t srtp_aes_gcm_openssl_set_iv(
+ void *cv,
+ uint8_t *iv,
+ srtp_cipher_direction_t direction)
+{
+ srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv;
+
+ if (direction != srtp_direction_encrypt &&
+ direction != srtp_direction_decrypt) {
+ return (srtp_err_status_bad_param);
+ }
+ c->dir = direction;
+
+ debug_print(srtp_mod_aes_gcm, "setting iv: %s",
+ srtp_octet_string_hex_string(iv, 12));
+
+ if (!EVP_CIPHER_CTX_ctrl(c->ctx, EVP_CTRL_GCM_SET_IVLEN, 12, 0)) {
+ return (srtp_err_status_init_fail);
+ }
+
+ if (!EVP_CipherInit_ex(c->ctx, NULL, NULL, NULL, iv,
+ (c->dir == srtp_direction_encrypt ? 1 : 0))) {
+ return (srtp_err_status_init_fail);
+ }
+
+ return (srtp_err_status_ok);
+}
+
+/*
+ * This function processes the AAD
+ *
+ * Parameters:
+ * c Crypto context
+ * aad Additional data to process for AEAD cipher suites
+ * aad_len length of aad buffer
+ */
+static srtp_err_status_t srtp_aes_gcm_openssl_set_aad(void *cv,
+ const uint8_t *aad,
+ uint32_t aad_len)
+{
+ srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv;
+ int rv;
+
+ debug_print(srtp_mod_aes_gcm, "setting AAD: %s",
+ srtp_octet_string_hex_string(aad, aad_len));
+
+ /*
+ * Set dummy tag, OpenSSL requires the Tag to be set before
+ * processing AAD
+ */
+
+ /*
+ * OpenSSL never write to address pointed by the last parameter of
+ * EVP_CIPHER_CTX_ctrl while EVP_CTRL_GCM_SET_TAG (in reality,
+ * OpenSSL copy its content to the context), so we can make
+ * aad read-only in this function and all its wrappers.
+ */
+ unsigned char dummy_tag[GCM_AUTH_TAG_LEN];
+ memset(dummy_tag, 0x0, GCM_AUTH_TAG_LEN);
+ EVP_CIPHER_CTX_ctrl(c->ctx, EVP_CTRL_GCM_SET_TAG, c->tag_len, &dummy_tag);
+
+ rv = EVP_Cipher(c->ctx, NULL, aad, aad_len);
+ if (rv != aad_len) {
+ return (srtp_err_status_algo_fail);
+ } else {
+ return (srtp_err_status_ok);
+ }
+}
+
+/*
+ * This function encrypts a buffer using AES GCM mode
+ *
+ * Parameters:
+ * c Crypto context
+ * buf data to encrypt
+ * enc_len length of encrypt buffer
+ */
+static srtp_err_status_t srtp_aes_gcm_openssl_encrypt(void *cv,
+ unsigned char *buf,
+ unsigned int *enc_len)
+{
+ srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv;
+ if (c->dir != srtp_direction_encrypt && c->dir != srtp_direction_decrypt) {
+ return (srtp_err_status_bad_param);
+ }
+
+ /*
+ * Encrypt the data
+ */
+ EVP_Cipher(c->ctx, buf, buf, *enc_len);
+
+ return (srtp_err_status_ok);
+}
+
+/*
+ * This function calculates and returns the GCM tag for a given context.
+ * This should be called after encrypting the data. The *len value
+ * is increased by the tag size. The caller must ensure that *buf has
+ * enough room to accept the appended tag.
+ *
+ * Parameters:
+ * c Crypto context
+ * buf data to encrypt
+ * len length of encrypt buffer
+ */
+static srtp_err_status_t srtp_aes_gcm_openssl_get_tag(void *cv,
+ uint8_t *buf,
+ uint32_t *len)
+{
+ srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv;
+ /*
+ * Calculate the tag
+ */
+ EVP_Cipher(c->ctx, NULL, NULL, 0);
+
+ /*
+ * Retreive the tag
+ */
+ EVP_CIPHER_CTX_ctrl(c->ctx, EVP_CTRL_GCM_GET_TAG, c->tag_len, buf);
+
+ /*
+ * Increase encryption length by desired tag size
+ */
+ *len = c->tag_len;
+
+ return (srtp_err_status_ok);
+}
+
+/*
+ * This function decrypts a buffer using AES GCM mode
+ *
+ * Parameters:
+ * c Crypto context
+ * buf data to encrypt
+ * enc_len length of encrypt buffer
+ */
+static srtp_err_status_t srtp_aes_gcm_openssl_decrypt(void *cv,
+ unsigned char *buf,
+ unsigned int *enc_len)
+{
+ srtp_aes_gcm_ctx_t *c = (srtp_aes_gcm_ctx_t *)cv;
+ if (c->dir != srtp_direction_encrypt && c->dir != srtp_direction_decrypt) {
+ return (srtp_err_status_bad_param);
+ }
+
+ /*
+ * Set the tag before decrypting
+ */
+ EVP_CIPHER_CTX_ctrl(c->ctx, EVP_CTRL_GCM_SET_TAG, c->tag_len,
+ buf + (*enc_len - c->tag_len));
+ EVP_Cipher(c->ctx, buf, buf, *enc_len - c->tag_len);
+
+ /*
+ * Check the tag
+ */
+ if (EVP_Cipher(c->ctx, NULL, NULL, 0)) {
+ return (srtp_err_status_auth_fail);
+ }
+
+ /*
+ * Reduce the buffer size by the tag length since the tag
+ * is not part of the original payload
+ */
+ *enc_len -= c->tag_len;
+
+ return (srtp_err_status_ok);
+}
+
+/*
+ * Name of this crypto engine
+ */
+static const char srtp_aes_gcm_128_openssl_description[] =
+ "AES-128 GCM using openssl";
+static const char srtp_aes_gcm_256_openssl_description[] =
+ "AES-256 GCM using openssl";
+
+/*
+ * KAT values for AES self-test. These
+ * values we're derived from independent test code
+ * using OpenSSL.
+ */
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_0_key[SRTP_AES_GCM_128_KEY_LEN_WSALT] = {
+ 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+ 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c,
+};
+/* clang-format on */
+
+/* clang-format off */
+static uint8_t srtp_aes_gcm_test_case_0_iv[12] = {
+ 0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
+ 0xde, 0xca, 0xf8, 0x88
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_0_plaintext[60] = {
+ 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+ 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+ 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+ 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+ 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+ 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+ 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+ 0xba, 0x63, 0x7b, 0x39
+};
+
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_0_aad[20] = {
+ 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xab, 0xad, 0xda, 0xd2
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_0_ciphertext[76] = {
+ 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24,
+ 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c,
+ 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0,
+ 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e,
+ 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c,
+ 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05,
+ 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
+ 0x3d, 0x58, 0xe0, 0x91,
+ /* the last 16 bytes are the tag */
+ 0x5b, 0xc9, 0x4f, 0xbc, 0x32, 0x21, 0xa5, 0xdb,
+ 0x94, 0xfa, 0xe9, 0x5a, 0xe7, 0x12, 0x1a, 0x47,
+};
+/* clang-format on */
+
+static const srtp_cipher_test_case_t srtp_aes_gcm_test_case_0a = {
+ SRTP_AES_GCM_128_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_gcm_test_case_0_key, /* key */
+ srtp_aes_gcm_test_case_0_iv, /* packet index */
+ 60, /* octets in plaintext */
+ srtp_aes_gcm_test_case_0_plaintext, /* plaintext */
+ 68, /* octets in ciphertext */
+ srtp_aes_gcm_test_case_0_ciphertext, /* ciphertext + tag */
+ 20, /* octets in AAD */
+ srtp_aes_gcm_test_case_0_aad, /* AAD */
+ GCM_AUTH_TAG_LEN_8, /* */
+ NULL /* pointer to next testcase */
+};
+
+static const srtp_cipher_test_case_t srtp_aes_gcm_test_case_0 = {
+ SRTP_AES_GCM_128_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_gcm_test_case_0_key, /* key */
+ srtp_aes_gcm_test_case_0_iv, /* packet index */
+ 60, /* octets in plaintext */
+ srtp_aes_gcm_test_case_0_plaintext, /* plaintext */
+ 76, /* octets in ciphertext */
+ srtp_aes_gcm_test_case_0_ciphertext, /* ciphertext + tag */
+ 20, /* octets in AAD */
+ srtp_aes_gcm_test_case_0_aad, /* AAD */
+ GCM_AUTH_TAG_LEN, /* */
+ &srtp_aes_gcm_test_case_0a /* pointer to next testcase */
+};
+
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_1_key[SRTP_AES_GCM_256_KEY_LEN_WSALT] = {
+ 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+ 0xa5, 0x59, 0x09, 0xc5, 0x54, 0x66, 0x93, 0x1c,
+ 0xaf, 0xf5, 0x26, 0x9a, 0x21, 0xd5, 0x14, 0xb2,
+ 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c,
+};
+/* clang-format on */
+
+/* clang-format off */
+static uint8_t srtp_aes_gcm_test_case_1_iv[12] = {
+ 0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
+ 0xde, 0xca, 0xf8, 0x88
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_1_plaintext[60] = {
+ 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+ 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+ 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+ 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+ 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+ 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+ 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+ 0xba, 0x63, 0x7b, 0x39
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_1_aad[20] = {
+ 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xab, 0xad, 0xda, 0xd2
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_gcm_test_case_1_ciphertext[76] = {
+ 0x0b, 0x11, 0xcf, 0xaf, 0x68, 0x4d, 0xae, 0x46,
+ 0xc7, 0x90, 0xb8, 0x8e, 0xb7, 0x6a, 0x76, 0x2a,
+ 0x94, 0x82, 0xca, 0xab, 0x3e, 0x39, 0xd7, 0x86,
+ 0x1b, 0xc7, 0x93, 0xed, 0x75, 0x7f, 0x23, 0x5a,
+ 0xda, 0xfd, 0xd3, 0xe2, 0x0e, 0x80, 0x87, 0xa9,
+ 0x6d, 0xd7, 0xe2, 0x6a, 0x7d, 0x5f, 0xb4, 0x80,
+ 0xef, 0xef, 0xc5, 0x29, 0x12, 0xd1, 0xaa, 0x10,
+ 0x09, 0xc9, 0x86, 0xc1,
+ /* the last 16 bytes are the tag */
+ 0x45, 0xbc, 0x03, 0xe6, 0xe1, 0xac, 0x0a, 0x9f,
+ 0x81, 0xcb, 0x8e, 0x5b, 0x46, 0x65, 0x63, 0x1d,
+};
+/* clang-format on */
+
+static const srtp_cipher_test_case_t srtp_aes_gcm_test_case_1a = {
+ SRTP_AES_GCM_256_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_gcm_test_case_1_key, /* key */
+ srtp_aes_gcm_test_case_1_iv, /* packet index */
+ 60, /* octets in plaintext */
+ srtp_aes_gcm_test_case_1_plaintext, /* plaintext */
+ 68, /* octets in ciphertext */
+ srtp_aes_gcm_test_case_1_ciphertext, /* ciphertext + tag */
+ 20, /* octets in AAD */
+ srtp_aes_gcm_test_case_1_aad, /* AAD */
+ GCM_AUTH_TAG_LEN_8, /* */
+ NULL /* pointer to next testcase */
+};
+
+static const srtp_cipher_test_case_t srtp_aes_gcm_test_case_1 = {
+ SRTP_AES_GCM_256_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_gcm_test_case_1_key, /* key */
+ srtp_aes_gcm_test_case_1_iv, /* packet index */
+ 60, /* octets in plaintext */
+ srtp_aes_gcm_test_case_1_plaintext, /* plaintext */
+ 76, /* octets in ciphertext */
+ srtp_aes_gcm_test_case_1_ciphertext, /* ciphertext + tag */
+ 20, /* octets in AAD */
+ srtp_aes_gcm_test_case_1_aad, /* AAD */
+ GCM_AUTH_TAG_LEN, /* */
+ &srtp_aes_gcm_test_case_1a /* pointer to next testcase */
+};
+
+/*
+ * This is the vector function table for this crypto engine.
+ */
+const srtp_cipher_type_t srtp_aes_gcm_128 = {
+ srtp_aes_gcm_openssl_alloc,
+ srtp_aes_gcm_openssl_dealloc,
+ srtp_aes_gcm_openssl_context_init,
+ srtp_aes_gcm_openssl_set_aad,
+ srtp_aes_gcm_openssl_encrypt,
+ srtp_aes_gcm_openssl_decrypt,
+ srtp_aes_gcm_openssl_set_iv,
+ srtp_aes_gcm_openssl_get_tag,
+ srtp_aes_gcm_128_openssl_description,
+ &srtp_aes_gcm_test_case_0,
+ SRTP_AES_GCM_128
+};
+
+/*
+ * This is the vector function table for this crypto engine.
+ */
+const srtp_cipher_type_t srtp_aes_gcm_256 = {
+ srtp_aes_gcm_openssl_alloc,
+ srtp_aes_gcm_openssl_dealloc,
+ srtp_aes_gcm_openssl_context_init,
+ srtp_aes_gcm_openssl_set_aad,
+ srtp_aes_gcm_openssl_encrypt,
+ srtp_aes_gcm_openssl_decrypt,
+ srtp_aes_gcm_openssl_set_iv,
+ srtp_aes_gcm_openssl_get_tag,
+ srtp_aes_gcm_256_openssl_description,
+ &srtp_aes_gcm_test_case_1,
+ SRTP_AES_GCM_256
+};
diff --git a/netwerk/srtp/src/crypto/cipher/aes_icm.c b/netwerk/srtp/src/crypto/cipher/aes_icm.c
new file mode 100644
index 0000000000..7551c75148
--- /dev/null
+++ b/netwerk/srtp/src/crypto/cipher/aes_icm.c
@@ -0,0 +1,530 @@
+/*
+ * aes_icm.c
+ *
+ * AES Integer Counter Mode
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define ALIGN_32 0
+
+#include "aes_icm.h"
+#include "alloc.h"
+#include "cipher_types.h"
+
+srtp_debug_module_t srtp_mod_aes_icm = {
+ 0, /* debugging is off by default */
+ "aes icm" /* printable module name */
+};
+
+/*
+ * integer counter mode works as follows:
+ *
+ * 16 bits
+ * <----->
+ * +------+------+------+------+------+------+------+------+
+ * | nonce | pakcet index | ctr |---+
+ * +------+------+------+------+------+------+------+------+ |
+ * |
+ * +------+------+------+------+------+------+------+------+ v
+ * | salt |000000|->(+)
+ * +------+------+------+------+------+------+------+------+ |
+ * |
+ * +---------+
+ * | encrypt |
+ * +---------+
+ * |
+ * +------+------+------+------+------+------+------+------+ |
+ * | keystream block |<--+
+ * +------+------+------+------+------+------+------+------+
+ *
+ * All fields are big-endian
+ *
+ * ctr is the block counter, which increments from zero for
+ * each packet (16 bits wide)
+ *
+ * packet index is distinct for each packet (48 bits wide)
+ *
+ * nonce can be distinct across many uses of the same key, or
+ * can be a fixed value per key, or can be per-packet randomness
+ * (64 bits)
+ *
+ */
+
+static srtp_err_status_t srtp_aes_icm_alloc(srtp_cipher_t **c,
+ int key_len,
+ int tlen)
+{
+ srtp_aes_icm_ctx_t *icm;
+
+ debug_print(srtp_mod_aes_icm, "allocating cipher with key length %d",
+ key_len);
+
+ /*
+ * The check for key_len = 30/46 does not apply. Our usage
+ * of aes functions with key_len = values other than 30
+ * has not broken anything. Don't know what would be the
+ * effect of skipping this check for srtp in general.
+ */
+ if (key_len != SRTP_AES_ICM_128_KEY_LEN_WSALT &&
+ key_len != SRTP_AES_ICM_256_KEY_LEN_WSALT) {
+ return srtp_err_status_bad_param;
+ }
+
+ /* allocate memory a cipher of type aes_icm */
+ *c = (srtp_cipher_t *)srtp_crypto_alloc(sizeof(srtp_cipher_t));
+ if (*c == NULL) {
+ return srtp_err_status_alloc_fail;
+ }
+
+ icm = (srtp_aes_icm_ctx_t *)srtp_crypto_alloc(sizeof(srtp_aes_icm_ctx_t));
+ if (icm == NULL) {
+ srtp_crypto_free(*c);
+ *c = NULL;
+ return srtp_err_status_alloc_fail;
+ }
+
+ /* set pointers */
+ (*c)->state = icm;
+
+ switch (key_len) {
+ case SRTP_AES_ICM_256_KEY_LEN_WSALT:
+ (*c)->algorithm = SRTP_AES_ICM_256;
+ (*c)->type = &srtp_aes_icm_256;
+ break;
+ default:
+ (*c)->algorithm = SRTP_AES_ICM_128;
+ (*c)->type = &srtp_aes_icm_128;
+ break;
+ }
+
+ /* set key size */
+ icm->key_size = key_len;
+ (*c)->key_len = key_len;
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_aes_icm_dealloc(srtp_cipher_t *c)
+{
+ srtp_aes_icm_ctx_t *ctx;
+
+ if (c == NULL) {
+ return srtp_err_status_bad_param;
+ }
+
+ ctx = (srtp_aes_icm_ctx_t *)c->state;
+ if (ctx) {
+ /* zeroize the key material */
+ octet_string_set_to_zero(ctx, sizeof(srtp_aes_icm_ctx_t));
+ srtp_crypto_free(ctx);
+ }
+
+ /* free the cipher context */
+ srtp_crypto_free(c);
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * aes_icm_context_init(...) initializes the aes_icm_context
+ * using the value in key[].
+ *
+ * the key is the secret key
+ *
+ * the salt is unpredictable (but not necessarily secret) data which
+ * randomizes the starting point in the keystream
+ */
+
+static srtp_err_status_t srtp_aes_icm_context_init(void *cv, const uint8_t *key)
+{
+ srtp_aes_icm_ctx_t *c = (srtp_aes_icm_ctx_t *)cv;
+ srtp_err_status_t status;
+ int base_key_len, copy_len;
+
+ if (c->key_size == SRTP_AES_ICM_128_KEY_LEN_WSALT ||
+ c->key_size == SRTP_AES_ICM_256_KEY_LEN_WSALT) {
+ base_key_len = c->key_size - SRTP_SALT_LEN;
+ } else {
+ return srtp_err_status_bad_param;
+ }
+
+ /*
+ * set counter and initial values to 'offset' value, being careful not to
+ * go past the end of the key buffer
+ */
+ v128_set_to_zero(&c->counter);
+ v128_set_to_zero(&c->offset);
+
+ copy_len = c->key_size - base_key_len;
+ /* force last two octets of the offset to be left zero (for srtp
+ * compatibility) */
+ if (copy_len > SRTP_SALT_LEN) {
+ copy_len = SRTP_SALT_LEN;
+ }
+
+ memcpy(&c->counter, key + base_key_len, copy_len);
+ memcpy(&c->offset, key + base_key_len, copy_len);
+
+ debug_print(srtp_mod_aes_icm, "key: %s",
+ srtp_octet_string_hex_string(key, base_key_len));
+ debug_print(srtp_mod_aes_icm, "offset: %s", v128_hex_string(&c->offset));
+
+ /* expand key */
+ status =
+ srtp_aes_expand_encryption_key(key, base_key_len, &c->expanded_key);
+ if (status) {
+ v128_set_to_zero(&c->counter);
+ v128_set_to_zero(&c->offset);
+ return status;
+ }
+
+ /* indicate that the keystream_buffer is empty */
+ c->bytes_in_buffer = 0;
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * aes_icm_set_iv(c, iv) sets the counter value to the exor of iv with
+ * the offset
+ */
+
+static srtp_err_status_t srtp_aes_icm_set_iv(void *cv,
+ uint8_t *iv,
+ srtp_cipher_direction_t direction)
+{
+ srtp_aes_icm_ctx_t *c = (srtp_aes_icm_ctx_t *)cv;
+ v128_t nonce;
+
+ /* set nonce (for alignment) */
+ v128_copy_octet_string(&nonce, iv);
+
+ debug_print(srtp_mod_aes_icm, "setting iv: %s", v128_hex_string(&nonce));
+
+ v128_xor(&c->counter, &c->offset, &nonce);
+
+ debug_print(srtp_mod_aes_icm, "set_counter: %s",
+ v128_hex_string(&c->counter));
+
+ /* indicate that the keystream_buffer is empty */
+ c->bytes_in_buffer = 0;
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * aes_icm_advance(...) refills the keystream_buffer and
+ * advances the block index of the sicm_context forward by one
+ *
+ * this is an internal, hopefully inlined function
+ */
+static void srtp_aes_icm_advance(srtp_aes_icm_ctx_t *c)
+{
+ /* fill buffer with new keystream */
+ v128_copy(&c->keystream_buffer, &c->counter);
+ srtp_aes_encrypt(&c->keystream_buffer, &c->expanded_key);
+ c->bytes_in_buffer = sizeof(v128_t);
+
+ debug_print(srtp_mod_aes_icm, "counter: %s",
+ v128_hex_string(&c->counter));
+ debug_print(srtp_mod_aes_icm, "ciphertext: %s",
+ v128_hex_string(&c->keystream_buffer));
+
+ /* clock counter forward */
+ if (!++(c->counter.v8[15])) {
+ ++(c->counter.v8[14]);
+ }
+}
+
+/*
+ * icm_encrypt deals with the following cases:
+ *
+ * bytes_to_encr < bytes_in_buffer
+ * - add keystream into data
+ *
+ * bytes_to_encr > bytes_in_buffer
+ * - add keystream into data until keystream_buffer is depleted
+ * - loop over blocks, filling keystream_buffer and then
+ * adding keystream into data
+ * - fill buffer then add in remaining (< 16) bytes of keystream
+ */
+
+static srtp_err_status_t srtp_aes_icm_encrypt(void *cv,
+ unsigned char *buf,
+ unsigned int *enc_len)
+{
+ srtp_aes_icm_ctx_t *c = (srtp_aes_icm_ctx_t *)cv;
+ unsigned int bytes_to_encr = *enc_len;
+ unsigned int i;
+ uint32_t *b;
+
+ /* check that there's enough segment left*/
+ if ((bytes_to_encr + htons(c->counter.v16[7])) > 0xffff) {
+ return srtp_err_status_terminus;
+ }
+
+ debug_print(srtp_mod_aes_icm, "block index: %d", htons(c->counter.v16[7]));
+ if (bytes_to_encr <= (unsigned int)c->bytes_in_buffer) {
+ /* deal with odd case of small bytes_to_encr */
+ for (i = (sizeof(v128_t) - c->bytes_in_buffer);
+ i < (sizeof(v128_t) - c->bytes_in_buffer + bytes_to_encr); i++) {
+ *buf++ ^= c->keystream_buffer.v8[i];
+ }
+
+ c->bytes_in_buffer -= bytes_to_encr;
+
+ /* return now to avoid the main loop */
+ return srtp_err_status_ok;
+
+ } else {
+ /* encrypt bytes until the remaining data is 16-byte aligned */
+ for (i = (sizeof(v128_t) - c->bytes_in_buffer); i < sizeof(v128_t);
+ i++) {
+ *buf++ ^= c->keystream_buffer.v8[i];
+ }
+
+ bytes_to_encr -= c->bytes_in_buffer;
+ c->bytes_in_buffer = 0;
+ }
+
+ /* now loop over entire 16-byte blocks of keystream */
+ for (i = 0; i < (bytes_to_encr / sizeof(v128_t)); i++) {
+ /* fill buffer with new keystream */
+ srtp_aes_icm_advance(c);
+
+/*
+ * add keystream into the data buffer (this would be a lot faster
+ * if we could assume 32-bit alignment!)
+ */
+
+#if ALIGN_32
+ b = (uint32_t *)buf;
+ *b++ ^= c->keystream_buffer.v32[0];
+ *b++ ^= c->keystream_buffer.v32[1];
+ *b++ ^= c->keystream_buffer.v32[2];
+ *b++ ^= c->keystream_buffer.v32[3];
+ buf = (uint8_t *)b;
+#else
+ if ((((uintptr_t)buf) & 0x03) != 0) {
+ *buf++ ^= c->keystream_buffer.v8[0];
+ *buf++ ^= c->keystream_buffer.v8[1];
+ *buf++ ^= c->keystream_buffer.v8[2];
+ *buf++ ^= c->keystream_buffer.v8[3];
+ *buf++ ^= c->keystream_buffer.v8[4];
+ *buf++ ^= c->keystream_buffer.v8[5];
+ *buf++ ^= c->keystream_buffer.v8[6];
+ *buf++ ^= c->keystream_buffer.v8[7];
+ *buf++ ^= c->keystream_buffer.v8[8];
+ *buf++ ^= c->keystream_buffer.v8[9];
+ *buf++ ^= c->keystream_buffer.v8[10];
+ *buf++ ^= c->keystream_buffer.v8[11];
+ *buf++ ^= c->keystream_buffer.v8[12];
+ *buf++ ^= c->keystream_buffer.v8[13];
+ *buf++ ^= c->keystream_buffer.v8[14];
+ *buf++ ^= c->keystream_buffer.v8[15];
+ } else {
+ b = (uint32_t *)buf;
+ *b++ ^= c->keystream_buffer.v32[0];
+ *b++ ^= c->keystream_buffer.v32[1];
+ *b++ ^= c->keystream_buffer.v32[2];
+ *b++ ^= c->keystream_buffer.v32[3];
+ buf = (uint8_t *)b;
+ }
+#endif /* #if ALIGN_32 */
+ }
+
+ /* if there is a tail end of the data, process it */
+ if ((bytes_to_encr & 0xf) != 0) {
+ /* fill buffer with new keystream */
+ srtp_aes_icm_advance(c);
+
+ for (i = 0; i < (bytes_to_encr & 0xf); i++) {
+ *buf++ ^= c->keystream_buffer.v8[i];
+ }
+
+ /* reset the keystream buffer size to right value */
+ c->bytes_in_buffer = sizeof(v128_t) - i;
+ } else {
+ /* no tail, so just reset the keystream buffer size to zero */
+ c->bytes_in_buffer = 0;
+ }
+
+ return srtp_err_status_ok;
+}
+
+static const char srtp_aes_icm_128_description[] =
+ "AES-128 integer counter mode";
+static const char srtp_aes_icm_256_description[] =
+ "AES-256 integer counter mode";
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_128_test_case_0_key[SRTP_AES_ICM_128_KEY_LEN_WSALT] = {
+ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+ 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd
+};
+/* clang-format on */
+
+/* clang-format off */
+static uint8_t srtp_aes_icm_128_test_case_0_nonce[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_128_test_case_0_plaintext[32] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_128_test_case_0_ciphertext[32] = {
+ 0xe0, 0x3e, 0xad, 0x09, 0x35, 0xc9, 0x5e, 0x80,
+ 0xe1, 0x66, 0xb1, 0x6d, 0xd9, 0x2b, 0x4e, 0xb4,
+ 0xd2, 0x35, 0x13, 0x16, 0x2b, 0x02, 0xd0, 0xf7,
+ 0x2a, 0x43, 0xa2, 0xfe, 0x4a, 0x5f, 0x97, 0xab
+};
+/* clang-format on */
+
+static const srtp_cipher_test_case_t srtp_aes_icm_128_test_case_0 = {
+ SRTP_AES_ICM_128_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_icm_128_test_case_0_key, /* key */
+ srtp_aes_icm_128_test_case_0_nonce, /* packet index */
+ 32, /* octets in plaintext */
+ srtp_aes_icm_128_test_case_0_plaintext, /* plaintext */
+ 32, /* octets in ciphertext */
+ srtp_aes_icm_128_test_case_0_ciphertext, /* ciphertext */
+ 0, /* */
+ NULL, /* */
+ 0, /* */
+ NULL /* pointer to next testcase */
+};
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_256_test_case_0_key[SRTP_AES_ICM_256_KEY_LEN_WSALT] = {
+ 0x57, 0xf8, 0x2f, 0xe3, 0x61, 0x3f, 0xd1, 0x70,
+ 0xa8, 0x5e, 0xc9, 0x3c, 0x40, 0xb1, 0xf0, 0x92,
+ 0x2e, 0xc4, 0xcb, 0x0d, 0xc0, 0x25, 0xb5, 0x82,
+ 0x72, 0x14, 0x7c, 0xc4, 0x38, 0x94, 0x4a, 0x98,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd
+};
+/* clang-format on */
+
+/* clang-format off */
+static uint8_t srtp_aes_icm_256_test_case_0_nonce[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_256_test_case_0_plaintext[32] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_256_test_case_0_ciphertext[32] = {
+ 0x92, 0xbd, 0xd2, 0x8a, 0x93, 0xc3, 0xf5, 0x25,
+ 0x11, 0xc6, 0x77, 0xd0, 0x8b, 0x55, 0x15, 0xa4,
+ 0x9d, 0xa7, 0x1b, 0x23, 0x78, 0xa8, 0x54, 0xf6,
+ 0x70, 0x50, 0x75, 0x6d, 0xed, 0x16, 0x5b, 0xac
+};
+/* clang-format on */
+
+static const srtp_cipher_test_case_t srtp_aes_icm_256_test_case_0 = {
+ SRTP_AES_ICM_256_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_icm_256_test_case_0_key, /* key */
+ srtp_aes_icm_256_test_case_0_nonce, /* packet index */
+ 32, /* octets in plaintext */
+ srtp_aes_icm_256_test_case_0_plaintext, /* plaintext */
+ 32, /* octets in ciphertext */
+ srtp_aes_icm_256_test_case_0_ciphertext, /* ciphertext */
+ 0, /* */
+ NULL, /* */
+ 0, /* */
+ NULL, /* pointer to next testcase */
+};
+
+/*
+ * note: the encrypt function is identical to the decrypt function
+ */
+
+const srtp_cipher_type_t srtp_aes_icm_128 = {
+ srtp_aes_icm_alloc, /* */
+ srtp_aes_icm_dealloc, /* */
+ srtp_aes_icm_context_init, /* */
+ 0, /* set_aad */
+ srtp_aes_icm_encrypt, /* */
+ srtp_aes_icm_encrypt, /* */
+ srtp_aes_icm_set_iv, /* */
+ 0, /* get_tag */
+ srtp_aes_icm_128_description, /* */
+ &srtp_aes_icm_128_test_case_0, /* */
+ SRTP_AES_ICM_128 /* */
+};
+
+const srtp_cipher_type_t srtp_aes_icm_256 = {
+ srtp_aes_icm_alloc, /* */
+ srtp_aes_icm_dealloc, /* */
+ srtp_aes_icm_context_init, /* */
+ 0, /* set_aad */
+ srtp_aes_icm_encrypt, /* */
+ srtp_aes_icm_encrypt, /* */
+ srtp_aes_icm_set_iv, /* */
+ 0, /* get_tag */
+ srtp_aes_icm_256_description, /* */
+ &srtp_aes_icm_256_test_case_0, /* */
+ SRTP_AES_ICM_256 /* */
+};
diff --git a/netwerk/srtp/src/crypto/cipher/aes_icm_nss.c b/netwerk/srtp/src/crypto/cipher/aes_icm_nss.c
new file mode 100644
index 0000000000..c64335f416
--- /dev/null
+++ b/netwerk/srtp/src/crypto/cipher/aes_icm_nss.c
@@ -0,0 +1,550 @@
+/*
+ * aes_icm_nss.c
+ *
+ * AES Integer Counter Mode
+ *
+ * Richard L. Barnes
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2013-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "aes_icm_ext.h"
+#include "crypto_types.h"
+#include "err.h" /* for srtp_debug */
+#include "alloc.h"
+#include "cipher_types.h"
+#include <nss.h>
+
+srtp_debug_module_t srtp_mod_aes_icm = {
+ 0, /* debugging is off by default */
+ "aes icm nss" /* printable module name */
+};
+
+/*
+ * integer counter mode works as follows:
+ *
+ * 16 bits
+ * <----->
+ * +------+------+------+------+------+------+------+------+
+ * | nonce | packet index | ctr |---+
+ * +------+------+------+------+------+------+------+------+ |
+ * |
+ * +------+------+------+------+------+------+------+------+ v
+ * | salt |000000|->(+)
+ * +------+------+------+------+------+------+------+------+ |
+ * |
+ * +---------+
+ * | encrypt |
+ * +---------+
+ * |
+ * +------+------+------+------+------+------+------+------+ |
+ * | keystream block |<--+
+ * +------+------+------+------+------+------+------+------+
+ *
+ * All fields are big-endian
+ *
+ * ctr is the block counter, which increments from zero for
+ * each packet (16 bits wide)
+ *
+ * packet index is distinct for each packet (48 bits wide)
+ *
+ * nonce can be distinct across many uses of the same key, or
+ * can be a fixed value per key, or can be per-packet randomness
+ * (64 bits)
+ *
+ */
+
+/*
+ * This function allocates a new instance of this crypto engine.
+ * The key_len parameter should be one of 30, 38, or 46 for
+ * AES-128, AES-192, and AES-256 respectively. Note, this key_len
+ * value is inflated, as it also accounts for the 112 bit salt
+ * value. The tlen argument is for the AEAD tag length, which
+ * isn't used in counter mode.
+ */
+static srtp_err_status_t srtp_aes_icm_nss_alloc(srtp_cipher_t **c,
+ int key_len,
+ int tlen)
+{
+ srtp_aes_icm_ctx_t *icm;
+
+ debug_print(srtp_mod_aes_icm, "allocating cipher with key length %d",
+ key_len);
+
+ /*
+ * Verify the key_len is valid for one of: AES-128/192/256
+ */
+ if (key_len != SRTP_AES_ICM_128_KEY_LEN_WSALT &&
+ key_len != SRTP_AES_ICM_192_KEY_LEN_WSALT &&
+ key_len != SRTP_AES_ICM_256_KEY_LEN_WSALT) {
+ return srtp_err_status_bad_param;
+ }
+
+ /* Initialize NSS */
+ if (!NSS_IsInitialized() && NSS_NoDB_Init(NULL) != SECSuccess) {
+ return (srtp_err_status_cipher_fail);
+ }
+
+ /* allocate memory a cipher of type aes_icm */
+ *c = (srtp_cipher_t *)srtp_crypto_alloc(sizeof(srtp_cipher_t));
+ if (*c == NULL) {
+ return srtp_err_status_alloc_fail;
+ }
+
+ icm = (srtp_aes_icm_ctx_t *)srtp_crypto_alloc(sizeof(srtp_aes_icm_ctx_t));
+ if (icm == NULL) {
+ srtp_crypto_free(*c);
+ *c = NULL;
+ return srtp_err_status_alloc_fail;
+ }
+
+ icm->key = NULL;
+ icm->ctx = NULL;
+
+ /* set pointers */
+ (*c)->state = icm;
+
+ /* setup cipher parameters */
+ switch (key_len) {
+ case SRTP_AES_ICM_128_KEY_LEN_WSALT:
+ (*c)->algorithm = SRTP_AES_ICM_128;
+ (*c)->type = &srtp_aes_icm_128;
+ icm->key_size = SRTP_AES_128_KEY_LEN;
+ break;
+ case SRTP_AES_ICM_192_KEY_LEN_WSALT:
+ (*c)->algorithm = SRTP_AES_ICM_192;
+ (*c)->type = &srtp_aes_icm_192;
+ icm->key_size = SRTP_AES_192_KEY_LEN;
+ break;
+ case SRTP_AES_ICM_256_KEY_LEN_WSALT:
+ (*c)->algorithm = SRTP_AES_ICM_256;
+ (*c)->type = &srtp_aes_icm_256;
+ icm->key_size = SRTP_AES_256_KEY_LEN;
+ break;
+ }
+
+ /* set key size */
+ (*c)->key_len = key_len;
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * This function deallocates an instance of this engine
+ */
+static srtp_err_status_t srtp_aes_icm_nss_dealloc(srtp_cipher_t *c)
+{
+ srtp_aes_icm_ctx_t *ctx;
+
+ ctx = (srtp_aes_icm_ctx_t *)c->state;
+ if (ctx) {
+ /* free any PK11 values that have been created */
+ if (ctx->key) {
+ PK11_FreeSymKey(ctx->key);
+ ctx->key = NULL;
+ }
+
+ if (ctx->ctx) {
+ PK11_DestroyContext(ctx->ctx, PR_TRUE);
+ ctx->ctx = NULL;
+ }
+
+ /* zeroize everything */
+ octet_string_set_to_zero(ctx, sizeof(srtp_aes_icm_ctx_t));
+ srtp_crypto_free(ctx);
+ }
+
+ /* free memory */
+ srtp_crypto_free(c);
+
+ return (srtp_err_status_ok);
+}
+
+/*
+ * aes_icm_nss_context_init(...) initializes the aes_icm_context
+ * using the value in key[].
+ *
+ * the key is the secret key
+ *
+ * the salt is unpredictable (but not necessarily secret) data which
+ * randomizes the starting point in the keystream
+ */
+static srtp_err_status_t srtp_aes_icm_nss_context_init(void *cv,
+ const uint8_t *key)
+{
+ srtp_aes_icm_ctx_t *c = (srtp_aes_icm_ctx_t *)cv;
+
+ /*
+ * set counter and initial values to 'offset' value, being careful not to
+ * go past the end of the key buffer
+ */
+ v128_set_to_zero(&c->counter);
+ v128_set_to_zero(&c->offset);
+ memcpy(&c->counter, key + c->key_size, SRTP_SALT_LEN);
+ memcpy(&c->offset, key + c->key_size, SRTP_SALT_LEN);
+
+ /* force last two octets of the offset to zero (for srtp compatibility) */
+ c->offset.v8[SRTP_SALT_LEN] = c->offset.v8[SRTP_SALT_LEN + 1] = 0;
+ c->counter.v8[SRTP_SALT_LEN] = c->counter.v8[SRTP_SALT_LEN + 1] = 0;
+
+ debug_print(srtp_mod_aes_icm, "key: %s",
+ srtp_octet_string_hex_string(key, c->key_size));
+ debug_print(srtp_mod_aes_icm, "offset: %s", v128_hex_string(&c->offset));
+
+ if (c->key) {
+ PK11_FreeSymKey(c->key);
+ c->key = NULL;
+ }
+
+ PK11SlotInfo *slot = PK11_GetBestSlot(CKM_AES_CTR, NULL);
+ if (!slot) {
+ return srtp_err_status_bad_param;
+ }
+
+ SECItem keyItem = { siBuffer, (unsigned char *)key, c->key_size };
+ c->key = PK11_ImportSymKey(slot, CKM_AES_CTR, PK11_OriginUnwrap,
+ CKA_ENCRYPT, &keyItem, NULL);
+ PK11_FreeSlot(slot);
+
+ if (!c->key) {
+ return srtp_err_status_cipher_fail;
+ }
+
+ return (srtp_err_status_ok);
+}
+
+/*
+ * aes_icm_set_iv(c, iv) sets the counter value to the exor of iv with
+ * the offset
+ */
+static srtp_err_status_t srtp_aes_icm_nss_set_iv(void *cv,
+ uint8_t *iv,
+ srtp_cipher_direction_t dir)
+{
+ srtp_aes_icm_ctx_t *c = (srtp_aes_icm_ctx_t *)cv;
+ v128_t nonce;
+
+ /* set nonce (for alignment) */
+ v128_copy_octet_string(&nonce, iv);
+
+ debug_print(srtp_mod_aes_icm, "setting iv: %s", v128_hex_string(&nonce));
+
+ v128_xor(&c->counter, &c->offset, &nonce);
+
+ debug_print(srtp_mod_aes_icm, "set_counter: %s",
+ v128_hex_string(&c->counter));
+
+ /* set up the PK11 context now that we have all the info */
+ CK_AES_CTR_PARAMS param;
+ param.ulCounterBits = 16;
+ memcpy(param.cb, &c->counter, 16);
+
+ if (!c->key) {
+ return srtp_err_status_bad_param;
+ }
+
+ if (c->ctx) {
+ PK11_DestroyContext(c->ctx, PR_TRUE);
+ }
+
+ SECItem paramItem = { siBuffer, (unsigned char *)&param,
+ sizeof(CK_AES_CTR_PARAMS) };
+ c->ctx = PK11_CreateContextBySymKey(CKM_AES_CTR, CKA_ENCRYPT, c->key,
+ &paramItem);
+ if (!c->ctx) {
+ return srtp_err_status_cipher_fail;
+ }
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * This function encrypts a buffer using AES CTR mode
+ *
+ * Parameters:
+ * c Crypto context
+ * buf data to encrypt
+ * enc_len length of encrypt buffer
+ */
+static srtp_err_status_t srtp_aes_icm_nss_encrypt(void *cv,
+ unsigned char *buf,
+ unsigned int *enc_len)
+{
+ srtp_aes_icm_ctx_t *c = (srtp_aes_icm_ctx_t *)cv;
+
+ if (!c->ctx) {
+ return srtp_err_status_bad_param;
+ }
+
+ int rv =
+ PK11_CipherOp(c->ctx, buf, (int *)enc_len, *enc_len, buf, *enc_len);
+
+ srtp_err_status_t status = (srtp_err_status_ok);
+ if (rv != SECSuccess) {
+ status = (srtp_err_status_cipher_fail);
+ }
+
+ return status;
+}
+
+/*
+ * Name of this crypto engine
+ */
+static const char srtp_aes_icm_128_nss_description[] =
+ "AES-128 counter mode using NSS";
+static const char srtp_aes_icm_192_nss_description[] =
+ "AES-192 counter mode using NSS";
+static const char srtp_aes_icm_256_nss_description[] =
+ "AES-256 counter mode using NSS";
+
+/*
+ * KAT values for AES self-test. These
+ * values came from the legacy libsrtp code.
+ */
+/* clang-format off */
+static const uint8_t srtp_aes_icm_128_test_case_0_key[SRTP_AES_ICM_128_KEY_LEN_WSALT] = {
+ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+ 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd
+};
+/* clang-format on */
+
+/* clang-format off */
+static uint8_t srtp_aes_icm_128_test_case_0_nonce[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_128_test_case_0_plaintext[32] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_128_test_case_0_ciphertext[32] = {
+ 0xe0, 0x3e, 0xad, 0x09, 0x35, 0xc9, 0x5e, 0x80,
+ 0xe1, 0x66, 0xb1, 0x6d, 0xd9, 0x2b, 0x4e, 0xb4,
+ 0xd2, 0x35, 0x13, 0x16, 0x2b, 0x02, 0xd0, 0xf7,
+ 0x2a, 0x43, 0xa2, 0xfe, 0x4a, 0x5f, 0x97, 0xab
+};
+/* clang-format on */
+
+static const srtp_cipher_test_case_t srtp_aes_icm_128_test_case_0 = {
+ SRTP_AES_ICM_128_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_icm_128_test_case_0_key, /* key */
+ srtp_aes_icm_128_test_case_0_nonce, /* packet index */
+ 32, /* octets in plaintext */
+ srtp_aes_icm_128_test_case_0_plaintext, /* plaintext */
+ 32, /* octets in ciphertext */
+ srtp_aes_icm_128_test_case_0_ciphertext, /* ciphertext */
+ 0, /* */
+ NULL, /* */
+ 0, /* */
+ NULL /* pointer to next testcase */
+};
+
+/*
+ * KAT values for AES-192-CTR self-test. These
+ * values came from section 7 of RFC 6188.
+ */
+/* clang-format off */
+static const uint8_t srtp_aes_icm_192_test_case_0_key[SRTP_AES_ICM_192_KEY_LEN_WSALT] = {
+ 0xea, 0xb2, 0x34, 0x76, 0x4e, 0x51, 0x7b, 0x2d,
+ 0x3d, 0x16, 0x0d, 0x58, 0x7d, 0x8c, 0x86, 0x21,
+ 0x97, 0x40, 0xf6, 0x5f, 0x99, 0xb6, 0xbc, 0xf7,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd
+};
+/* clang-format on */
+
+/* clang-format off */
+static uint8_t srtp_aes_icm_192_test_case_0_nonce[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_192_test_case_0_plaintext[32] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_192_test_case_0_ciphertext[32] = {
+ 0x35, 0x09, 0x6c, 0xba, 0x46, 0x10, 0x02, 0x8d,
+ 0xc1, 0xb5, 0x75, 0x03, 0x80, 0x4c, 0xe3, 0x7c,
+ 0x5d, 0xe9, 0x86, 0x29, 0x1d, 0xcc, 0xe1, 0x61,
+ 0xd5, 0x16, 0x5e, 0xc4, 0x56, 0x8f, 0x5c, 0x9a
+};
+/* clang-format on */
+
+static const srtp_cipher_test_case_t srtp_aes_icm_192_test_case_0 = {
+ SRTP_AES_ICM_192_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_icm_192_test_case_0_key, /* key */
+ srtp_aes_icm_192_test_case_0_nonce, /* packet index */
+ 32, /* octets in plaintext */
+ srtp_aes_icm_192_test_case_0_plaintext, /* plaintext */
+ 32, /* octets in ciphertext */
+ srtp_aes_icm_192_test_case_0_ciphertext, /* ciphertext */
+ 0, /* */
+ NULL, /* */
+ 0, /* */
+ NULL /* pointer to next testcase */
+};
+
+/*
+ * KAT values for AES-256-CTR self-test. These
+ * values came from section 7 of RFC 6188.
+ */
+/* clang-format off */
+static const uint8_t srtp_aes_icm_256_test_case_0_key[SRTP_AES_ICM_256_KEY_LEN_WSALT] = {
+ 0x57, 0xf8, 0x2f, 0xe3, 0x61, 0x3f, 0xd1, 0x70,
+ 0xa8, 0x5e, 0xc9, 0x3c, 0x40, 0xb1, 0xf0, 0x92,
+ 0x2e, 0xc4, 0xcb, 0x0d, 0xc0, 0x25, 0xb5, 0x82,
+ 0x72, 0x14, 0x7c, 0xc4, 0x38, 0x94, 0x4a, 0x98,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd
+};
+/* clang-format on */
+
+/* clang-format off */
+static uint8_t srtp_aes_icm_256_test_case_0_nonce[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_256_test_case_0_plaintext[32] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_256_test_case_0_ciphertext[32] = {
+ 0x92, 0xbd, 0xd2, 0x8a, 0x93, 0xc3, 0xf5, 0x25,
+ 0x11, 0xc6, 0x77, 0xd0, 0x8b, 0x55, 0x15, 0xa4,
+ 0x9d, 0xa7, 0x1b, 0x23, 0x78, 0xa8, 0x54, 0xf6,
+ 0x70, 0x50, 0x75, 0x6d, 0xed, 0x16, 0x5b, 0xac
+};
+/* clang-format on */
+
+static const srtp_cipher_test_case_t srtp_aes_icm_256_test_case_0 = {
+ SRTP_AES_ICM_256_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_icm_256_test_case_0_key, /* key */
+ srtp_aes_icm_256_test_case_0_nonce, /* packet index */
+ 32, /* octets in plaintext */
+ srtp_aes_icm_256_test_case_0_plaintext, /* plaintext */
+ 32, /* octets in ciphertext */
+ srtp_aes_icm_256_test_case_0_ciphertext, /* ciphertext */
+ 0, /* */
+ NULL, /* */
+ 0, /* */
+ NULL /* pointer to next testcase */
+};
+
+/*
+ * This is the function table for this crypto engine.
+ * note: the encrypt function is identical to the decrypt function
+ */
+const srtp_cipher_type_t srtp_aes_icm_128 = {
+ srtp_aes_icm_nss_alloc, /* */
+ srtp_aes_icm_nss_dealloc, /* */
+ srtp_aes_icm_nss_context_init, /* */
+ 0, /* set_aad */
+ srtp_aes_icm_nss_encrypt, /* */
+ srtp_aes_icm_nss_encrypt, /* */
+ srtp_aes_icm_nss_set_iv, /* */
+ 0, /* get_tag */
+ srtp_aes_icm_128_nss_description, /* */
+ &srtp_aes_icm_128_test_case_0, /* */
+ SRTP_AES_ICM_128 /* */
+};
+
+/*
+ * This is the function table for this crypto engine.
+ * note: the encrypt function is identical to the decrypt function
+ */
+const srtp_cipher_type_t srtp_aes_icm_192 = {
+ srtp_aes_icm_nss_alloc, /* */
+ srtp_aes_icm_nss_dealloc, /* */
+ srtp_aes_icm_nss_context_init, /* */
+ 0, /* set_aad */
+ srtp_aes_icm_nss_encrypt, /* */
+ srtp_aes_icm_nss_encrypt, /* */
+ srtp_aes_icm_nss_set_iv, /* */
+ 0, /* get_tag */
+ srtp_aes_icm_192_nss_description, /* */
+ &srtp_aes_icm_192_test_case_0, /* */
+ SRTP_AES_ICM_192 /* */
+};
+
+/*
+ * This is the function table for this crypto engine.
+ * note: the encrypt function is identical to the decrypt function
+ */
+const srtp_cipher_type_t srtp_aes_icm_256 = {
+ srtp_aes_icm_nss_alloc, /* */
+ srtp_aes_icm_nss_dealloc, /* */
+ srtp_aes_icm_nss_context_init, /* */
+ 0, /* set_aad */
+ srtp_aes_icm_nss_encrypt, /* */
+ srtp_aes_icm_nss_encrypt, /* */
+ srtp_aes_icm_nss_set_iv, /* */
+ 0, /* get_tag */
+ srtp_aes_icm_256_nss_description, /* */
+ &srtp_aes_icm_256_test_case_0, /* */
+ SRTP_AES_ICM_256 /* */
+};
diff --git a/netwerk/srtp/src/crypto/cipher/aes_icm_ossl.c b/netwerk/srtp/src/crypto/cipher/aes_icm_ossl.c
new file mode 100644
index 0000000000..a9b14d3972
--- /dev/null
+++ b/netwerk/srtp/src/crypto/cipher/aes_icm_ossl.c
@@ -0,0 +1,540 @@
+/*
+ * aes_icm_ossl.c
+ *
+ * AES Integer Counter Mode
+ *
+ * John A. Foley
+ * Cisco Systems, Inc.
+ *
+ * 2/24/2012: This module was modified to use CiscoSSL for AES counter
+ * mode. Eddy Lem contributed the code to allow this.
+ *
+ * 12/20/2012: Added support for AES-192 and AES-256.
+ */
+
+/*
+ *
+ * Copyright (c) 2013-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <openssl/evp.h>
+#include "aes_icm_ext.h"
+#include "crypto_types.h"
+#include "err.h" /* for srtp_debug */
+#include "alloc.h"
+#include "cipher_types.h"
+
+srtp_debug_module_t srtp_mod_aes_icm = {
+ 0, /* debugging is off by default */
+ "aes icm ossl" /* printable module name */
+};
+
+/*
+ * integer counter mode works as follows:
+ *
+ * 16 bits
+ * <----->
+ * +------+------+------+------+------+------+------+------+
+ * | nonce | packet index | ctr |---+
+ * +------+------+------+------+------+------+------+------+ |
+ * |
+ * +------+------+------+------+------+------+------+------+ v
+ * | salt |000000|->(+)
+ * +------+------+------+------+------+------+------+------+ |
+ * |
+ * +---------+
+ * | encrypt |
+ * +---------+
+ * |
+ * +------+------+------+------+------+------+------+------+ |
+ * | keystream block |<--+
+ * +------+------+------+------+------+------+------+------+
+ *
+ * All fields are big-endian
+ *
+ * ctr is the block counter, which increments from zero for
+ * each packet (16 bits wide)
+ *
+ * packet index is distinct for each packet (48 bits wide)
+ *
+ * nonce can be distinct across many uses of the same key, or
+ * can be a fixed value per key, or can be per-packet randomness
+ * (64 bits)
+ *
+ */
+
+/*
+ * This function allocates a new instance of this crypto engine.
+ * The key_len parameter should be one of 30, 38, or 46 for
+ * AES-128, AES-192, and AES-256 respectively. Note, this key_len
+ * value is inflated, as it also accounts for the 112 bit salt
+ * value. The tlen argument is for the AEAD tag length, which
+ * isn't used in counter mode.
+ */
+static srtp_err_status_t srtp_aes_icm_openssl_alloc(srtp_cipher_t **c,
+ int key_len,
+ int tlen)
+{
+ srtp_aes_icm_ctx_t *icm;
+
+ debug_print(srtp_mod_aes_icm, "allocating cipher with key length %d",
+ key_len);
+
+ /*
+ * Verify the key_len is valid for one of: AES-128/192/256
+ */
+ if (key_len != SRTP_AES_ICM_128_KEY_LEN_WSALT &&
+ key_len != SRTP_AES_ICM_192_KEY_LEN_WSALT &&
+ key_len != SRTP_AES_ICM_256_KEY_LEN_WSALT) {
+ return srtp_err_status_bad_param;
+ }
+
+ /* allocate memory a cipher of type aes_icm */
+ *c = (srtp_cipher_t *)srtp_crypto_alloc(sizeof(srtp_cipher_t));
+ if (*c == NULL) {
+ return srtp_err_status_alloc_fail;
+ }
+
+ icm = (srtp_aes_icm_ctx_t *)srtp_crypto_alloc(sizeof(srtp_aes_icm_ctx_t));
+ if (icm == NULL) {
+ srtp_crypto_free(*c);
+ *c = NULL;
+ return srtp_err_status_alloc_fail;
+ }
+
+ icm->ctx = EVP_CIPHER_CTX_new();
+ if (icm->ctx == NULL) {
+ srtp_crypto_free(icm);
+ srtp_crypto_free(*c);
+ *c = NULL;
+ return srtp_err_status_alloc_fail;
+ }
+
+ /* set pointers */
+ (*c)->state = icm;
+
+ /* setup cipher parameters */
+ switch (key_len) {
+ case SRTP_AES_ICM_128_KEY_LEN_WSALT:
+ (*c)->algorithm = SRTP_AES_ICM_128;
+ (*c)->type = &srtp_aes_icm_128;
+ icm->key_size = SRTP_AES_128_KEY_LEN;
+ break;
+ case SRTP_AES_ICM_192_KEY_LEN_WSALT:
+ (*c)->algorithm = SRTP_AES_ICM_192;
+ (*c)->type = &srtp_aes_icm_192;
+ icm->key_size = SRTP_AES_192_KEY_LEN;
+ break;
+ case SRTP_AES_ICM_256_KEY_LEN_WSALT:
+ (*c)->algorithm = SRTP_AES_ICM_256;
+ (*c)->type = &srtp_aes_icm_256;
+ icm->key_size = SRTP_AES_256_KEY_LEN;
+ break;
+ }
+
+ /* set key size */
+ (*c)->key_len = key_len;
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * This function deallocates an instance of this engine
+ */
+static srtp_err_status_t srtp_aes_icm_openssl_dealloc(srtp_cipher_t *c)
+{
+ srtp_aes_icm_ctx_t *ctx;
+
+ if (c == NULL) {
+ return srtp_err_status_bad_param;
+ }
+
+ /*
+ * Free the EVP context
+ */
+ ctx = (srtp_aes_icm_ctx_t *)c->state;
+ if (ctx != NULL) {
+ EVP_CIPHER_CTX_free(ctx->ctx);
+ /* zeroize the key material */
+ octet_string_set_to_zero(ctx, sizeof(srtp_aes_icm_ctx_t));
+ srtp_crypto_free(ctx);
+ }
+
+ /* free memory */
+ srtp_crypto_free(c);
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * aes_icm_openssl_context_init(...) initializes the aes_icm_context
+ * using the value in key[].
+ *
+ * the key is the secret key
+ *
+ * the salt is unpredictable (but not necessarily secret) data which
+ * randomizes the starting point in the keystream
+ */
+static srtp_err_status_t srtp_aes_icm_openssl_context_init(void *cv,
+ const uint8_t *key)
+{
+ srtp_aes_icm_ctx_t *c = (srtp_aes_icm_ctx_t *)cv;
+ const EVP_CIPHER *evp;
+
+ /*
+ * set counter and initial values to 'offset' value, being careful not to
+ * go past the end of the key buffer
+ */
+ v128_set_to_zero(&c->counter);
+ v128_set_to_zero(&c->offset);
+ memcpy(&c->counter, key + c->key_size, SRTP_SALT_LEN);
+ memcpy(&c->offset, key + c->key_size, SRTP_SALT_LEN);
+
+ /* force last two octets of the offset to zero (for srtp compatibility) */
+ c->offset.v8[SRTP_SALT_LEN] = c->offset.v8[SRTP_SALT_LEN + 1] = 0;
+ c->counter.v8[SRTP_SALT_LEN] = c->counter.v8[SRTP_SALT_LEN + 1] = 0;
+
+ debug_print(srtp_mod_aes_icm, "key: %s",
+ srtp_octet_string_hex_string(key, c->key_size));
+ debug_print(srtp_mod_aes_icm, "offset: %s", v128_hex_string(&c->offset));
+
+ switch (c->key_size) {
+ case SRTP_AES_256_KEY_LEN:
+ evp = EVP_aes_256_ctr();
+ break;
+ case SRTP_AES_192_KEY_LEN:
+ evp = EVP_aes_192_ctr();
+ break;
+ case SRTP_AES_128_KEY_LEN:
+ evp = EVP_aes_128_ctr();
+ break;
+ default:
+ return srtp_err_status_bad_param;
+ break;
+ }
+
+ if (!EVP_EncryptInit_ex(c->ctx, evp, NULL, key, NULL)) {
+ return srtp_err_status_fail;
+ } else {
+ return srtp_err_status_ok;
+ }
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * aes_icm_set_iv(c, iv) sets the counter value to the exor of iv with
+ * the offset
+ */
+static srtp_err_status_t srtp_aes_icm_openssl_set_iv(
+ void *cv,
+ uint8_t *iv,
+ srtp_cipher_direction_t dir)
+{
+ srtp_aes_icm_ctx_t *c = (srtp_aes_icm_ctx_t *)cv;
+ v128_t nonce;
+
+ /* set nonce (for alignment) */
+ v128_copy_octet_string(&nonce, iv);
+
+ debug_print(srtp_mod_aes_icm, "setting iv: %s", v128_hex_string(&nonce));
+
+ v128_xor(&c->counter, &c->offset, &nonce);
+
+ debug_print(srtp_mod_aes_icm, "set_counter: %s",
+ v128_hex_string(&c->counter));
+
+ if (!EVP_EncryptInit_ex(c->ctx, NULL, NULL, NULL, c->counter.v8)) {
+ return srtp_err_status_fail;
+ } else {
+ return srtp_err_status_ok;
+ }
+}
+
+/*
+ * This function encrypts a buffer using AES CTR mode
+ *
+ * Parameters:
+ * c Crypto context
+ * buf data to encrypt
+ * enc_len length of encrypt buffer
+ */
+static srtp_err_status_t srtp_aes_icm_openssl_encrypt(void *cv,
+ unsigned char *buf,
+ unsigned int *enc_len)
+{
+ srtp_aes_icm_ctx_t *c = (srtp_aes_icm_ctx_t *)cv;
+ int len = 0;
+
+ debug_print(srtp_mod_aes_icm, "rs0: %s", v128_hex_string(&c->counter));
+
+ if (!EVP_EncryptUpdate(c->ctx, buf, &len, buf, *enc_len)) {
+ return srtp_err_status_cipher_fail;
+ }
+ *enc_len = len;
+
+ if (!EVP_EncryptFinal_ex(c->ctx, buf, &len)) {
+ return srtp_err_status_cipher_fail;
+ }
+ *enc_len += len;
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * Name of this crypto engine
+ */
+static const char srtp_aes_icm_128_openssl_description[] =
+ "AES-128 counter mode using openssl";
+static const char srtp_aes_icm_192_openssl_description[] =
+ "AES-192 counter mode using openssl";
+static const char srtp_aes_icm_256_openssl_description[] =
+ "AES-256 counter mode using openssl";
+
+/*
+ * KAT values for AES self-test. These
+ * values came from the legacy libsrtp code.
+ */
+/* clang-format off */
+static const uint8_t srtp_aes_icm_128_test_case_0_key[SRTP_AES_ICM_128_KEY_LEN_WSALT] = {
+ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+ 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd
+};
+/* clang-format on */
+
+/* clang-format off */
+static uint8_t srtp_aes_icm_128_test_case_0_nonce[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_128_test_case_0_plaintext[32] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_128_test_case_0_ciphertext[32] = {
+ 0xe0, 0x3e, 0xad, 0x09, 0x35, 0xc9, 0x5e, 0x80,
+ 0xe1, 0x66, 0xb1, 0x6d, 0xd9, 0x2b, 0x4e, 0xb4,
+ 0xd2, 0x35, 0x13, 0x16, 0x2b, 0x02, 0xd0, 0xf7,
+ 0x2a, 0x43, 0xa2, 0xfe, 0x4a, 0x5f, 0x97, 0xab
+};
+/* clang-format on */
+
+static const srtp_cipher_test_case_t srtp_aes_icm_128_test_case_0 = {
+ SRTP_AES_ICM_128_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_icm_128_test_case_0_key, /* key */
+ srtp_aes_icm_128_test_case_0_nonce, /* packet index */
+ 32, /* octets in plaintext */
+ srtp_aes_icm_128_test_case_0_plaintext, /* plaintext */
+ 32, /* octets in ciphertext */
+ srtp_aes_icm_128_test_case_0_ciphertext, /* ciphertext */
+ 0, /* */
+ NULL, /* */
+ 0, /* */
+ NULL /* pointer to next testcase */
+};
+
+/*
+ * KAT values for AES-192-CTR self-test. These
+ * values came from section 7 of RFC 6188.
+ */
+/* clang-format off */
+static const uint8_t srtp_aes_icm_192_test_case_0_key[SRTP_AES_ICM_192_KEY_LEN_WSALT] = {
+ 0xea, 0xb2, 0x34, 0x76, 0x4e, 0x51, 0x7b, 0x2d,
+ 0x3d, 0x16, 0x0d, 0x58, 0x7d, 0x8c, 0x86, 0x21,
+ 0x97, 0x40, 0xf6, 0x5f, 0x99, 0xb6, 0xbc, 0xf7,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd
+};
+/* clang-format on */
+
+/* clang-format off */
+static uint8_t srtp_aes_icm_192_test_case_0_nonce[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_192_test_case_0_plaintext[32] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_192_test_case_0_ciphertext[32] = {
+ 0x35, 0x09, 0x6c, 0xba, 0x46, 0x10, 0x02, 0x8d,
+ 0xc1, 0xb5, 0x75, 0x03, 0x80, 0x4c, 0xe3, 0x7c,
+ 0x5d, 0xe9, 0x86, 0x29, 0x1d, 0xcc, 0xe1, 0x61,
+ 0xd5, 0x16, 0x5e, 0xc4, 0x56, 0x8f, 0x5c, 0x9a
+};
+/* clang-format on */
+
+static const srtp_cipher_test_case_t srtp_aes_icm_192_test_case_0 = {
+ SRTP_AES_ICM_192_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_icm_192_test_case_0_key, /* key */
+ srtp_aes_icm_192_test_case_0_nonce, /* packet index */
+ 32, /* octets in plaintext */
+ srtp_aes_icm_192_test_case_0_plaintext, /* plaintext */
+ 32, /* octets in ciphertext */
+ srtp_aes_icm_192_test_case_0_ciphertext, /* ciphertext */
+ 0, /* */
+ NULL, /* */
+ 0, /* */
+ NULL /* pointer to next testcase */
+};
+
+/*
+ * KAT values for AES-256-CTR self-test. These
+ * values came from section 7 of RFC 6188.
+ */
+/* clang-format off */
+static const uint8_t srtp_aes_icm_256_test_case_0_key[SRTP_AES_ICM_256_KEY_LEN_WSALT] = {
+ 0x57, 0xf8, 0x2f, 0xe3, 0x61, 0x3f, 0xd1, 0x70,
+ 0xa8, 0x5e, 0xc9, 0x3c, 0x40, 0xb1, 0xf0, 0x92,
+ 0x2e, 0xc4, 0xcb, 0x0d, 0xc0, 0x25, 0xb5, 0x82,
+ 0x72, 0x14, 0x7c, 0xc4, 0x38, 0x94, 0x4a, 0x98,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd
+};
+/* clang-format on */
+
+/* clang-format off */
+static uint8_t srtp_aes_icm_256_test_case_0_nonce[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_256_test_case_0_plaintext[32] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_aes_icm_256_test_case_0_ciphertext[32] = {
+ 0x92, 0xbd, 0xd2, 0x8a, 0x93, 0xc3, 0xf5, 0x25,
+ 0x11, 0xc6, 0x77, 0xd0, 0x8b, 0x55, 0x15, 0xa4,
+ 0x9d, 0xa7, 0x1b, 0x23, 0x78, 0xa8, 0x54, 0xf6,
+ 0x70, 0x50, 0x75, 0x6d, 0xed, 0x16, 0x5b, 0xac
+};
+/* clang-format on */
+
+static const srtp_cipher_test_case_t srtp_aes_icm_256_test_case_0 = {
+ SRTP_AES_ICM_256_KEY_LEN_WSALT, /* octets in key */
+ srtp_aes_icm_256_test_case_0_key, /* key */
+ srtp_aes_icm_256_test_case_0_nonce, /* packet index */
+ 32, /* octets in plaintext */
+ srtp_aes_icm_256_test_case_0_plaintext, /* plaintext */
+ 32, /* octets in ciphertext */
+ srtp_aes_icm_256_test_case_0_ciphertext, /* ciphertext */
+ 0, /* */
+ NULL, /* */
+ 0, /* */
+ NULL /* pointer to next testcase */
+};
+
+/*
+ * This is the function table for this crypto engine.
+ * note: the encrypt function is identical to the decrypt function
+ */
+const srtp_cipher_type_t srtp_aes_icm_128 = {
+ srtp_aes_icm_openssl_alloc, /* */
+ srtp_aes_icm_openssl_dealloc, /* */
+ srtp_aes_icm_openssl_context_init, /* */
+ 0, /* set_aad */
+ srtp_aes_icm_openssl_encrypt, /* */
+ srtp_aes_icm_openssl_encrypt, /* */
+ srtp_aes_icm_openssl_set_iv, /* */
+ 0, /* get_tag */
+ srtp_aes_icm_128_openssl_description, /* */
+ &srtp_aes_icm_128_test_case_0, /* */
+ SRTP_AES_ICM_128 /* */
+};
+
+/*
+ * This is the function table for this crypto engine.
+ * note: the encrypt function is identical to the decrypt function
+ */
+const srtp_cipher_type_t srtp_aes_icm_192 = {
+ srtp_aes_icm_openssl_alloc, /* */
+ srtp_aes_icm_openssl_dealloc, /* */
+ srtp_aes_icm_openssl_context_init, /* */
+ 0, /* set_aad */
+ srtp_aes_icm_openssl_encrypt, /* */
+ srtp_aes_icm_openssl_encrypt, /* */
+ srtp_aes_icm_openssl_set_iv, /* */
+ 0, /* get_tag */
+ srtp_aes_icm_192_openssl_description, /* */
+ &srtp_aes_icm_192_test_case_0, /* */
+ SRTP_AES_ICM_192 /* */
+};
+
+/*
+ * This is the function table for this crypto engine.
+ * note: the encrypt function is identical to the decrypt function
+ */
+const srtp_cipher_type_t srtp_aes_icm_256 = {
+ srtp_aes_icm_openssl_alloc, /* */
+ srtp_aes_icm_openssl_dealloc, /* */
+ srtp_aes_icm_openssl_context_init, /* */
+ 0, /* set_aad */
+ srtp_aes_icm_openssl_encrypt, /* */
+ srtp_aes_icm_openssl_encrypt, /* */
+ srtp_aes_icm_openssl_set_iv, /* */
+ 0, /* get_tag */
+ srtp_aes_icm_256_openssl_description, /* */
+ &srtp_aes_icm_256_test_case_0, /* */
+ SRTP_AES_ICM_256 /* */
+};
diff --git a/netwerk/srtp/src/crypto/cipher/cipher.c b/netwerk/srtp/src/crypto/cipher/cipher.c
new file mode 100644
index 0000000000..19f00abc55
--- /dev/null
+++ b/netwerk/srtp/src/crypto/cipher/cipher.c
@@ -0,0 +1,679 @@
+/*
+ * cipher.c
+ *
+ * cipher meta-functions
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "cipher.h"
+#include "crypto_types.h"
+#include "err.h" /* for srtp_debug */
+#include "alloc.h" /* for crypto_alloc(), crypto_free() */
+
+srtp_debug_module_t srtp_mod_cipher = {
+ 0, /* debugging is off by default */
+ "cipher" /* printable module name */
+};
+
+srtp_err_status_t srtp_cipher_type_alloc(const srtp_cipher_type_t *ct,
+ srtp_cipher_t **c,
+ int key_len,
+ int tlen)
+{
+ if (!ct || !ct->alloc) {
+ return (srtp_err_status_bad_param);
+ }
+ return ((ct)->alloc((c), (key_len), (tlen)));
+}
+
+srtp_err_status_t srtp_cipher_dealloc(srtp_cipher_t *c)
+{
+ if (!c || !c->type) {
+ return (srtp_err_status_bad_param);
+ }
+ return (((c)->type)->dealloc(c));
+}
+
+srtp_err_status_t srtp_cipher_init(srtp_cipher_t *c, const uint8_t *key)
+{
+ if (!c || !c->type || !c->state) {
+ return (srtp_err_status_bad_param);
+ }
+ return (((c)->type)->init(((c)->state), (key)));
+}
+
+srtp_err_status_t srtp_cipher_set_iv(srtp_cipher_t *c,
+ uint8_t *iv,
+ int direction)
+{
+ if (!c || !c->type || !c->state) {
+ return (srtp_err_status_bad_param);
+ }
+
+ return (((c)->type)->set_iv(((c)->state), iv, direction));
+}
+
+srtp_err_status_t srtp_cipher_output(srtp_cipher_t *c,
+ uint8_t *buffer,
+ uint32_t *num_octets_to_output)
+{
+ /* zeroize the buffer */
+ octet_string_set_to_zero(buffer, *num_octets_to_output);
+
+ /* exor keystream into buffer */
+ return (((c)->type)->encrypt(((c)->state), buffer, num_octets_to_output));
+}
+
+srtp_err_status_t srtp_cipher_encrypt(srtp_cipher_t *c,
+ uint8_t *buffer,
+ uint32_t *num_octets_to_output)
+{
+ if (!c || !c->type || !c->state) {
+ return (srtp_err_status_bad_param);
+ }
+
+ return (((c)->type)->encrypt(((c)->state), buffer, num_octets_to_output));
+}
+
+srtp_err_status_t srtp_cipher_decrypt(srtp_cipher_t *c,
+ uint8_t *buffer,
+ uint32_t *num_octets_to_output)
+{
+ if (!c || !c->type || !c->state) {
+ return (srtp_err_status_bad_param);
+ }
+
+ return (((c)->type)->decrypt(((c)->state), buffer, num_octets_to_output));
+}
+
+srtp_err_status_t srtp_cipher_get_tag(srtp_cipher_t *c,
+ uint8_t *buffer,
+ uint32_t *tag_len)
+{
+ if (!c || !c->type || !c->state) {
+ return (srtp_err_status_bad_param);
+ }
+ if (!((c)->type)->get_tag) {
+ return (srtp_err_status_no_such_op);
+ }
+
+ return (((c)->type)->get_tag(((c)->state), buffer, tag_len));
+}
+
+srtp_err_status_t srtp_cipher_set_aad(srtp_cipher_t *c,
+ const uint8_t *aad,
+ uint32_t aad_len)
+{
+ if (!c || !c->type || !c->state) {
+ return (srtp_err_status_bad_param);
+ }
+ if (!((c)->type)->set_aad) {
+ return (srtp_err_status_no_such_op);
+ }
+
+ return (((c)->type)->set_aad(((c)->state), aad, aad_len));
+}
+
+/* some bookkeeping functions */
+
+int srtp_cipher_get_key_length(const srtp_cipher_t *c)
+{
+ return c->key_len;
+}
+
+/*
+ * A trivial platform independent random source. The random
+ * data is used for some of the cipher self-tests.
+ */
+static srtp_err_status_t srtp_cipher_rand(void *dest, uint32_t len)
+{
+#if defined(HAVE_RAND_S)
+ uint8_t *dst = (uint8_t *)dest;
+ while (len) {
+ unsigned int val;
+ errno_t err = rand_s(&val);
+
+ if (err != 0)
+ return srtp_err_status_fail;
+
+ *dst++ = val & 0xff;
+ len--;
+ }
+#else
+ /* Generic C-library (rand()) version */
+ /* This is a random source of last resort */
+ uint8_t *dst = (uint8_t *)dest;
+ while (len) {
+ int val = rand();
+ /* rand() returns 0-32767 (ugh) */
+ /* Is this a good enough way to get random bytes?
+ It is if it passes FIPS-140... */
+ *dst++ = val & 0xff;
+ len--;
+ }
+#endif
+ return srtp_err_status_ok;
+}
+
+#define SELF_TEST_BUF_OCTETS 128
+#define NUM_RAND_TESTS 128
+#define MAX_KEY_LEN 64
+/*
+ * srtp_cipher_type_test(ct, test_data) tests a cipher of type ct against
+ * test cases provided in a list test_data of values of key, salt, iv,
+ * plaintext, and ciphertext that is known to be good
+ */
+srtp_err_status_t srtp_cipher_type_test(
+ const srtp_cipher_type_t *ct,
+ const srtp_cipher_test_case_t *test_data)
+{
+ const srtp_cipher_test_case_t *test_case = test_data;
+ srtp_cipher_t *c;
+ srtp_err_status_t status;
+ uint8_t buffer[SELF_TEST_BUF_OCTETS];
+ uint8_t buffer2[SELF_TEST_BUF_OCTETS];
+ uint32_t tag_len;
+ unsigned int len;
+ int i, j, case_num = 0;
+ unsigned k = 0;
+
+ debug_print(srtp_mod_cipher, "running self-test for cipher %s",
+ ct->description);
+
+ /*
+ * check to make sure that we have at least one test case, and
+ * return an error if we don't - we need to be paranoid here
+ */
+ if (test_case == NULL) {
+ return srtp_err_status_cant_check;
+ }
+
+ /*
+ * loop over all test cases, perform known-answer tests of both the
+ * encryption and decryption functions
+ */
+ while (test_case != NULL) {
+ /* allocate cipher */
+ status = srtp_cipher_type_alloc(ct, &c, test_case->key_length_octets,
+ test_case->tag_length_octets);
+ if (status) {
+ return status;
+ }
+
+ /*
+ * test the encrypt function
+ */
+ debug_print(srtp_mod_cipher, "testing encryption", NULL);
+
+ /* initialize cipher */
+ status = srtp_cipher_init(c, test_case->key);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+
+ /* copy plaintext into test buffer */
+ if (test_case->ciphertext_length_octets > SELF_TEST_BUF_OCTETS) {
+ srtp_cipher_dealloc(c);
+ return srtp_err_status_bad_param;
+ }
+ for (k = 0; k < test_case->plaintext_length_octets; k++) {
+ buffer[k] = test_case->plaintext[k];
+ }
+
+ debug_print(srtp_mod_cipher, "plaintext: %s",
+ srtp_octet_string_hex_string(
+ buffer, test_case->plaintext_length_octets));
+
+ /* set the initialization vector */
+ status = srtp_cipher_set_iv(c, (uint8_t *)test_case->idx,
+ srtp_direction_encrypt);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+
+ if (c->algorithm == SRTP_AES_GCM_128 ||
+ c->algorithm == SRTP_AES_GCM_256) {
+ debug_print(srtp_mod_cipher, "IV: %s",
+ srtp_octet_string_hex_string(test_case->idx, 12));
+
+ /*
+ * Set the AAD
+ */
+ status = srtp_cipher_set_aad(c, test_case->aad,
+ test_case->aad_length_octets);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+ debug_print(srtp_mod_cipher, "AAD: %s",
+ srtp_octet_string_hex_string(
+ test_case->aad, test_case->aad_length_octets));
+ }
+
+ /* encrypt */
+ len = test_case->plaintext_length_octets;
+ status = srtp_cipher_encrypt(c, buffer, &len);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+
+ if (c->algorithm == SRTP_AES_GCM_128 ||
+ c->algorithm == SRTP_AES_GCM_256) {
+ /*
+ * Get the GCM tag
+ */
+ status = srtp_cipher_get_tag(c, buffer + len, &tag_len);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+ len += tag_len;
+ }
+
+ debug_print(srtp_mod_cipher, "ciphertext: %s",
+ srtp_octet_string_hex_string(
+ buffer, test_case->ciphertext_length_octets));
+
+ /* compare the resulting ciphertext with that in the test case */
+ if (len != test_case->ciphertext_length_octets) {
+ srtp_cipher_dealloc(c);
+ return srtp_err_status_algo_fail;
+ }
+ status = srtp_err_status_ok;
+ for (k = 0; k < test_case->ciphertext_length_octets; k++) {
+ if (buffer[k] != test_case->ciphertext[k]) {
+ status = srtp_err_status_algo_fail;
+ debug_print(srtp_mod_cipher, "test case %d failed", case_num);
+ debug_print(srtp_mod_cipher, "(failure at byte %u)", k);
+ break;
+ }
+ }
+ if (status) {
+ debug_print(srtp_mod_cipher, "c computed: %s",
+ srtp_octet_string_hex_string(
+ buffer, 2 * test_case->plaintext_length_octets));
+ debug_print(srtp_mod_cipher, "c expected: %s",
+ srtp_octet_string_hex_string(
+ test_case->ciphertext,
+ 2 * test_case->plaintext_length_octets));
+
+ srtp_cipher_dealloc(c);
+ return srtp_err_status_algo_fail;
+ }
+
+ /*
+ * test the decrypt function
+ */
+ debug_print(srtp_mod_cipher, "testing decryption", NULL);
+
+ /* re-initialize cipher for decryption */
+ status = srtp_cipher_init(c, test_case->key);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+
+ /* copy ciphertext into test buffer */
+ if (test_case->ciphertext_length_octets > SELF_TEST_BUF_OCTETS) {
+ srtp_cipher_dealloc(c);
+ return srtp_err_status_bad_param;
+ }
+ for (k = 0; k < test_case->ciphertext_length_octets; k++) {
+ buffer[k] = test_case->ciphertext[k];
+ }
+
+ debug_print(srtp_mod_cipher, "ciphertext: %s",
+ srtp_octet_string_hex_string(
+ buffer, test_case->plaintext_length_octets));
+
+ /* set the initialization vector */
+ status = srtp_cipher_set_iv(c, (uint8_t *)test_case->idx,
+ srtp_direction_decrypt);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+
+ if (c->algorithm == SRTP_AES_GCM_128 ||
+ c->algorithm == SRTP_AES_GCM_256) {
+ /*
+ * Set the AAD
+ */
+ status = srtp_cipher_set_aad(c, test_case->aad,
+ test_case->aad_length_octets);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+ debug_print(srtp_mod_cipher, "AAD: %s",
+ srtp_octet_string_hex_string(
+ test_case->aad, test_case->aad_length_octets));
+ }
+
+ /* decrypt */
+ len = test_case->ciphertext_length_octets;
+ status = srtp_cipher_decrypt(c, buffer, &len);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+
+ debug_print(srtp_mod_cipher, "plaintext: %s",
+ srtp_octet_string_hex_string(
+ buffer, test_case->plaintext_length_octets));
+
+ /* compare the resulting plaintext with that in the test case */
+ if (len != test_case->plaintext_length_octets) {
+ srtp_cipher_dealloc(c);
+ return srtp_err_status_algo_fail;
+ }
+ status = srtp_err_status_ok;
+ for (k = 0; k < test_case->plaintext_length_octets; k++) {
+ if (buffer[k] != test_case->plaintext[k]) {
+ status = srtp_err_status_algo_fail;
+ debug_print(srtp_mod_cipher, "test case %d failed", case_num);
+ debug_print(srtp_mod_cipher, "(failure at byte %u)", k);
+ }
+ }
+ if (status) {
+ debug_print(srtp_mod_cipher, "p computed: %s",
+ srtp_octet_string_hex_string(
+ buffer, 2 * test_case->plaintext_length_octets));
+ debug_print(srtp_mod_cipher, "p expected: %s",
+ srtp_octet_string_hex_string(
+ test_case->plaintext,
+ 2 * test_case->plaintext_length_octets));
+
+ srtp_cipher_dealloc(c);
+ return srtp_err_status_algo_fail;
+ }
+
+ /* deallocate the cipher */
+ status = srtp_cipher_dealloc(c);
+ if (status) {
+ return status;
+ }
+
+ /*
+ * the cipher passed the test case, so move on to the next test
+ * case in the list; if NULL, we'l proceed to the next test
+ */
+ test_case = test_case->next_test_case;
+ ++case_num;
+ }
+
+ /* now run some random invertibility tests */
+
+ /* allocate cipher, using paramaters from the first test case */
+ test_case = test_data;
+ status = srtp_cipher_type_alloc(ct, &c, test_case->key_length_octets,
+ test_case->tag_length_octets);
+ if (status) {
+ return status;
+ }
+
+ for (j = 0; j < NUM_RAND_TESTS; j++) {
+ unsigned int length;
+ unsigned int plaintext_len;
+ uint8_t key[MAX_KEY_LEN];
+ uint8_t iv[MAX_KEY_LEN];
+
+ /* choose a length at random (leaving room for IV and padding) */
+ length = rand() % (SELF_TEST_BUF_OCTETS - 64);
+ debug_print(srtp_mod_cipher, "random plaintext length %d\n", length);
+ status = srtp_cipher_rand(buffer, length);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+
+ debug_print(srtp_mod_cipher, "plaintext: %s",
+ srtp_octet_string_hex_string(buffer, length));
+
+ /* copy plaintext into second buffer */
+ for (i = 0; (unsigned int)i < length; i++) {
+ buffer2[i] = buffer[i];
+ }
+
+ /* choose a key at random */
+ if (test_case->key_length_octets > MAX_KEY_LEN) {
+ srtp_cipher_dealloc(c);
+ return srtp_err_status_cant_check;
+ }
+ status = srtp_cipher_rand(key, test_case->key_length_octets);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+
+ /* chose a random initialization vector */
+ status = srtp_cipher_rand(iv, MAX_KEY_LEN);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+
+ /* initialize cipher */
+ status = srtp_cipher_init(c, key);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+
+ /* set initialization vector */
+ status = srtp_cipher_set_iv(c, (uint8_t *)test_case->idx,
+ srtp_direction_encrypt);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+
+ if (c->algorithm == SRTP_AES_GCM_128 ||
+ c->algorithm == SRTP_AES_GCM_256) {
+ /*
+ * Set the AAD
+ */
+ status = srtp_cipher_set_aad(c, test_case->aad,
+ test_case->aad_length_octets);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+ debug_print(srtp_mod_cipher, "AAD: %s",
+ srtp_octet_string_hex_string(
+ test_case->aad, test_case->aad_length_octets));
+ }
+
+ /* encrypt buffer with cipher */
+ plaintext_len = length;
+ status = srtp_cipher_encrypt(c, buffer, &length);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+ if (c->algorithm == SRTP_AES_GCM_128 ||
+ c->algorithm == SRTP_AES_GCM_256) {
+ /*
+ * Get the GCM tag
+ */
+ status = srtp_cipher_get_tag(c, buffer + length, &tag_len);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+ length += tag_len;
+ }
+ debug_print(srtp_mod_cipher, "ciphertext: %s",
+ srtp_octet_string_hex_string(buffer, length));
+
+ /*
+ * re-initialize cipher for decryption, re-set the iv, then
+ * decrypt the ciphertext
+ */
+ status = srtp_cipher_init(c, key);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+ status = srtp_cipher_set_iv(c, (uint8_t *)test_case->idx,
+ srtp_direction_decrypt);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+ if (c->algorithm == SRTP_AES_GCM_128 ||
+ c->algorithm == SRTP_AES_GCM_256) {
+ /*
+ * Set the AAD
+ */
+ status = srtp_cipher_set_aad(c, test_case->aad,
+ test_case->aad_length_octets);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+ debug_print(srtp_mod_cipher, "AAD: %s",
+ srtp_octet_string_hex_string(
+ test_case->aad, test_case->aad_length_octets));
+ }
+ status = srtp_cipher_decrypt(c, buffer, &length);
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return status;
+ }
+
+ debug_print(srtp_mod_cipher, "plaintext[2]: %s",
+ srtp_octet_string_hex_string(buffer, length));
+
+ /* compare the resulting plaintext with the original one */
+ if (length != plaintext_len) {
+ srtp_cipher_dealloc(c);
+ return srtp_err_status_algo_fail;
+ }
+ status = srtp_err_status_ok;
+ for (k = 0; k < plaintext_len; k++) {
+ if (buffer[k] != buffer2[k]) {
+ status = srtp_err_status_algo_fail;
+ debug_print(srtp_mod_cipher, "random test case %d failed",
+ case_num);
+ debug_print(srtp_mod_cipher, "(failure at byte %u)", k);
+ }
+ }
+ if (status) {
+ srtp_cipher_dealloc(c);
+ return srtp_err_status_algo_fail;
+ }
+ }
+
+ status = srtp_cipher_dealloc(c);
+ if (status) {
+ return status;
+ }
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp_cipher_type_self_test(ct) performs srtp_cipher_type_test on ct's
+ * internal list of test data.
+ */
+srtp_err_status_t srtp_cipher_type_self_test(const srtp_cipher_type_t *ct)
+{
+ return srtp_cipher_type_test(ct, ct->test_data);
+}
+
+/*
+ * cipher_bits_per_second(c, l, t) computes (an estimate of) the
+ * number of bits that a cipher implementation can encrypt in a second
+ *
+ * c is a cipher (which MUST be allocated and initialized already), l
+ * is the length in octets of the test data to be encrypted, and t is
+ * the number of trials
+ *
+ * if an error is encountered, the value 0 is returned
+ */
+uint64_t srtp_cipher_bits_per_second(srtp_cipher_t *c,
+ int octets_in_buffer,
+ int num_trials)
+{
+ int i;
+ v128_t nonce;
+ clock_t timer;
+ unsigned char *enc_buf;
+ unsigned int len = octets_in_buffer;
+
+ enc_buf = (unsigned char *)srtp_crypto_alloc(octets_in_buffer);
+ if (enc_buf == NULL) {
+ return 0; /* indicate bad parameters by returning null */
+ }
+ /* time repeated trials */
+ v128_set_to_zero(&nonce);
+ timer = clock();
+ for (i = 0; i < num_trials; i++, nonce.v32[3] = i) {
+ if (srtp_cipher_set_iv(c, (uint8_t *)&nonce, srtp_direction_encrypt) !=
+ srtp_err_status_ok) {
+ srtp_crypto_free(enc_buf);
+ return 0;
+ }
+ if (srtp_cipher_encrypt(c, enc_buf, &len) != srtp_err_status_ok) {
+ srtp_crypto_free(enc_buf);
+ return 0;
+ }
+ }
+ timer = clock() - timer;
+
+ srtp_crypto_free(enc_buf);
+
+ if (timer == 0) {
+ /* Too fast! */
+ return 0;
+ }
+
+ return (uint64_t)CLOCKS_PER_SEC * num_trials * 8 * octets_in_buffer / timer;
+}
diff --git a/netwerk/srtp/src/crypto/cipher/null_cipher.c b/netwerk/srtp/src/crypto/cipher/null_cipher.c
new file mode 100644
index 0000000000..659add0d47
--- /dev/null
+++ b/netwerk/srtp/src/crypto/cipher/null_cipher.c
@@ -0,0 +1,153 @@
+/*
+ * null_cipher.c
+ *
+ * A null cipher implementation. This cipher leaves the plaintext
+ * unchanged.
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "datatypes.h"
+#include "null_cipher.h"
+#include "err.h" /* for srtp_debug */
+#include "alloc.h"
+#include "cipher_types.h"
+
+static srtp_err_status_t srtp_null_cipher_alloc(srtp_cipher_t **c,
+ int key_len,
+ int tlen)
+{
+ extern const srtp_cipher_type_t srtp_null_cipher;
+
+ debug_print(srtp_mod_cipher, "allocating cipher with key length %d",
+ key_len);
+
+ /* allocate memory a cipher of type null_cipher */
+ *c = (srtp_cipher_t *)srtp_crypto_alloc(sizeof(srtp_cipher_t));
+ if (*c == NULL) {
+ return srtp_err_status_alloc_fail;
+ }
+
+ /* set pointers */
+ (*c)->algorithm = SRTP_NULL_CIPHER;
+ (*c)->type = &srtp_null_cipher;
+ (*c)->state = (void *)0x1; /* The null cipher does not maintain state */
+
+ /* set key size */
+ (*c)->key_len = key_len;
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_null_cipher_dealloc(srtp_cipher_t *c)
+{
+ extern const srtp_cipher_type_t srtp_null_cipher;
+
+ /* zeroize entire state*/
+ octet_string_set_to_zero(c, sizeof(srtp_cipher_t));
+
+ /* free memory of type null_cipher */
+ srtp_crypto_free(c);
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_null_cipher_init(void *cv, const uint8_t *key)
+{
+ /* srtp_null_cipher_ctx_t *c = (srtp_null_cipher_ctx_t *)cv; */
+
+ debug_print(srtp_mod_cipher, "initializing null cipher", NULL);
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_null_cipher_set_iv(void *cv,
+ uint8_t *iv,
+ srtp_cipher_direction_t dir)
+{
+ /* srtp_null_cipher_ctx_t *c = (srtp_null_cipher_ctx_t *)cv; */
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_null_cipher_encrypt(void *cv,
+ unsigned char *buf,
+ unsigned int *bytes_to_encr)
+{
+ /* srtp_null_cipher_ctx_t *c = (srtp_null_cipher_ctx_t *)cv; */
+ return srtp_err_status_ok;
+}
+
+static const char srtp_null_cipher_description[] = "null cipher";
+
+static const srtp_cipher_test_case_t srtp_null_cipher_test_0 = {
+ 0, /* octets in key */
+ NULL, /* key */
+ 0, /* packet index */
+ 0, /* octets in plaintext */
+ NULL, /* plaintext */
+ 0, /* octets in plaintext */
+ NULL, /* ciphertext */
+ 0, /* */
+ NULL, /* */
+ 0, /* */
+ NULL /* pointer to next testcase */
+};
+
+/*
+ * note: the decrypt function is idential to the encrypt function
+ */
+
+const srtp_cipher_type_t srtp_null_cipher = {
+ srtp_null_cipher_alloc, /* */
+ srtp_null_cipher_dealloc, /* */
+ srtp_null_cipher_init, /* */
+ 0, /* set_aad */
+ srtp_null_cipher_encrypt, /* */
+ srtp_null_cipher_encrypt, /* */
+ srtp_null_cipher_set_iv, /* */
+ 0, /* get_tag */
+ srtp_null_cipher_description, /* */
+ &srtp_null_cipher_test_0, /* */
+ SRTP_NULL_CIPHER /* */
+};
diff --git a/netwerk/srtp/src/crypto/hash/auth.c b/netwerk/srtp/src/crypto/hash/auth.c
new file mode 100644
index 0000000000..f19327dc8e
--- /dev/null
+++ b/netwerk/srtp/src/crypto/hash/auth.c
@@ -0,0 +1,187 @@
+/*
+ * auth.c
+ *
+ * some bookkeeping functions for authentication functions
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "auth.h"
+#include "err.h" /* for srtp_debug */
+#include "datatypes.h" /* for octet_string */
+
+/* the debug module for authentiation */
+
+srtp_debug_module_t srtp_mod_auth = {
+ 0, /* debugging is off by default */
+ "auth func" /* printable name for module */
+};
+
+int srtp_auth_get_key_length(const srtp_auth_t *a)
+{
+ return a->key_len;
+}
+
+int srtp_auth_get_tag_length(const srtp_auth_t *a)
+{
+ return a->out_len;
+}
+
+int srtp_auth_get_prefix_length(const srtp_auth_t *a)
+{
+ return a->prefix_len;
+}
+
+/*
+ * srtp_auth_type_test() tests an auth function of type ct against
+ * test cases provided in a list test_data of values of key, data, and tag
+ * that is known to be good
+ */
+
+/* should be big enough for most occasions */
+#define SELF_TEST_TAG_BUF_OCTETS 32
+
+srtp_err_status_t srtp_auth_type_test(const srtp_auth_type_t *at,
+ const srtp_auth_test_case_t *test_data)
+{
+ const srtp_auth_test_case_t *test_case = test_data;
+ srtp_auth_t *a;
+ srtp_err_status_t status;
+ uint8_t tag[SELF_TEST_TAG_BUF_OCTETS];
+ int i, case_num = 0;
+
+ debug_print(srtp_mod_auth, "running self-test for auth function %s",
+ at->description);
+
+ /*
+ * check to make sure that we have at least one test case, and
+ * return an error if we don't - we need to be paranoid here
+ */
+ if (test_case == NULL) {
+ return srtp_err_status_cant_check;
+ }
+
+ /* loop over all test cases */
+ while (test_case != NULL) {
+ /* check test case parameters */
+ if (test_case->tag_length_octets > SELF_TEST_TAG_BUF_OCTETS) {
+ return srtp_err_status_bad_param;
+ }
+
+ /* allocate auth */
+ status = srtp_auth_type_alloc(at, &a, test_case->key_length_octets,
+ test_case->tag_length_octets);
+ if (status) {
+ return status;
+ }
+
+ /* initialize auth */
+ status = srtp_auth_init(a, test_case->key);
+ if (status) {
+ srtp_auth_dealloc(a);
+ return status;
+ }
+
+ /* zeroize tag then compute */
+ octet_string_set_to_zero(tag, test_case->tag_length_octets);
+ status = srtp_auth_compute(a, test_case->data,
+ test_case->data_length_octets, tag);
+ if (status) {
+ srtp_auth_dealloc(a);
+ return status;
+ }
+
+ debug_print(srtp_mod_auth, "key: %s",
+ srtp_octet_string_hex_string(test_case->key,
+ test_case->key_length_octets));
+ debug_print(srtp_mod_auth, "data: %s",
+ srtp_octet_string_hex_string(
+ test_case->data, test_case->data_length_octets));
+ debug_print(
+ srtp_mod_auth, "tag computed: %s",
+ srtp_octet_string_hex_string(tag, test_case->tag_length_octets));
+ debug_print(srtp_mod_auth, "tag expected: %s",
+ srtp_octet_string_hex_string(test_case->tag,
+ test_case->tag_length_octets));
+
+ /* check the result */
+ status = srtp_err_status_ok;
+ for (i = 0; i < test_case->tag_length_octets; i++) {
+ if (tag[i] != test_case->tag[i]) {
+ status = srtp_err_status_algo_fail;
+ debug_print(srtp_mod_auth, "test case %d failed", case_num);
+ debug_print(srtp_mod_auth, " (mismatch at octet %d)", i);
+ }
+ }
+ if (status) {
+ srtp_auth_dealloc(a);
+ return srtp_err_status_algo_fail;
+ }
+
+ /* deallocate the auth function */
+ status = srtp_auth_dealloc(a);
+ if (status) {
+ return status;
+ }
+
+ /*
+ * the auth function passed the test case, so move on to the next test
+ * case in the list; if NULL, we'll quit and return an OK
+ */
+ test_case = test_case->next_test_case;
+ ++case_num;
+ }
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp_auth_type_self_test(at) performs srtp_auth_type_test on at's internal
+ * list of test data.
+ */
+
+srtp_err_status_t srtp_auth_type_self_test(const srtp_auth_type_t *at)
+{
+ return srtp_auth_type_test(at, at->test_data);
+}
diff --git a/netwerk/srtp/src/crypto/hash/hmac.c b/netwerk/srtp/src/crypto/hash/hmac.c
new file mode 100644
index 0000000000..6e91468f36
--- /dev/null
+++ b/netwerk/srtp/src/crypto/hash/hmac.c
@@ -0,0 +1,283 @@
+/*
+ * hmac.c
+ *
+ * implementation of hmac srtp_auth_type_t
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "hmac.h"
+#include "alloc.h"
+#include "cipher_types.h"
+
+/* the debug module for authentiation */
+
+srtp_debug_module_t srtp_mod_hmac = {
+ 0, /* debugging is off by default */
+ "hmac sha-1" /* printable name for module */
+};
+
+static srtp_err_status_t srtp_hmac_alloc(srtp_auth_t **a,
+ int key_len,
+ int out_len)
+{
+ extern const srtp_auth_type_t srtp_hmac;
+ uint8_t *pointer;
+
+ debug_print(srtp_mod_hmac, "allocating auth func with key length %d",
+ key_len);
+ debug_print(srtp_mod_hmac, " tag length %d",
+ out_len);
+
+ /*
+ * check key length - note that we don't support keys larger
+ * than 20 bytes yet
+ */
+ if (key_len > 20) {
+ return srtp_err_status_bad_param;
+ }
+
+ /* check output length - should be less than 20 bytes */
+ if (out_len > 20) {
+ return srtp_err_status_bad_param;
+ }
+
+ /* allocate memory for auth and srtp_hmac_ctx_t structures */
+ pointer = (uint8_t *)srtp_crypto_alloc(sizeof(srtp_hmac_ctx_t) +
+ sizeof(srtp_auth_t));
+ if (pointer == NULL) {
+ return srtp_err_status_alloc_fail;
+ }
+
+ /* set pointers */
+ *a = (srtp_auth_t *)pointer;
+ (*a)->type = &srtp_hmac;
+ (*a)->state = pointer + sizeof(srtp_auth_t);
+ (*a)->out_len = out_len;
+ (*a)->key_len = key_len;
+ (*a)->prefix_len = 0;
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_hmac_dealloc(srtp_auth_t *a)
+{
+ /* zeroize entire state*/
+ octet_string_set_to_zero(a, sizeof(srtp_hmac_ctx_t) + sizeof(srtp_auth_t));
+
+ /* free memory */
+ srtp_crypto_free(a);
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_hmac_init(void *statev,
+ const uint8_t *key,
+ int key_len)
+{
+ srtp_hmac_ctx_t *state = (srtp_hmac_ctx_t *)statev;
+ int i;
+ uint8_t ipad[64];
+
+ /*
+ * check key length - note that we don't support keys larger
+ * than 20 bytes yet
+ */
+ if (key_len > 20) {
+ return srtp_err_status_bad_param;
+ }
+
+ /*
+ * set values of ipad and opad by exoring the key into the
+ * appropriate constant values
+ */
+ for (i = 0; i < key_len; i++) {
+ ipad[i] = key[i] ^ 0x36;
+ state->opad[i] = key[i] ^ 0x5c;
+ }
+ /* set the rest of ipad, opad to constant values */
+ for (; i < 64; i++) {
+ ipad[i] = 0x36;
+ ((uint8_t *)state->opad)[i] = 0x5c;
+ }
+
+ debug_print(srtp_mod_hmac, "ipad: %s",
+ srtp_octet_string_hex_string(ipad, 64));
+
+ /* initialize sha1 context */
+ srtp_sha1_init(&state->init_ctx);
+
+ /* hash ipad ^ key */
+ srtp_sha1_update(&state->init_ctx, ipad, 64);
+ memcpy(&state->ctx, &state->init_ctx, sizeof(srtp_sha1_ctx_t));
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_hmac_start(void *statev)
+{
+ srtp_hmac_ctx_t *state = (srtp_hmac_ctx_t *)statev;
+
+ memcpy(&state->ctx, &state->init_ctx, sizeof(srtp_sha1_ctx_t));
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_hmac_update(void *statev,
+ const uint8_t *message,
+ int msg_octets)
+{
+ srtp_hmac_ctx_t *state = (srtp_hmac_ctx_t *)statev;
+
+ debug_print(srtp_mod_hmac, "input: %s",
+ srtp_octet_string_hex_string(message, msg_octets));
+
+ /* hash message into sha1 context */
+ srtp_sha1_update(&state->ctx, message, msg_octets);
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_hmac_compute(void *statev,
+ const uint8_t *message,
+ int msg_octets,
+ int tag_len,
+ uint8_t *result)
+{
+ srtp_hmac_ctx_t *state = (srtp_hmac_ctx_t *)statev;
+ uint32_t hash_value[5];
+ uint32_t H[5];
+ int i;
+
+ /* check tag length, return error if we can't provide the value expected */
+ if (tag_len > 20) {
+ return srtp_err_status_bad_param;
+ }
+
+ /* hash message, copy output into H */
+ srtp_hmac_update(state, message, msg_octets);
+ srtp_sha1_final(&state->ctx, H);
+
+ /*
+ * note that we don't need to debug_print() the input, since the
+ * function hmac_update() already did that for us
+ */
+ debug_print(srtp_mod_hmac, "intermediate state: %s",
+ srtp_octet_string_hex_string((uint8_t *)H, 20));
+
+ /* re-initialize hash context */
+ srtp_sha1_init(&state->ctx);
+
+ /* hash opad ^ key */
+ srtp_sha1_update(&state->ctx, (uint8_t *)state->opad, 64);
+
+ /* hash the result of the inner hash */
+ srtp_sha1_update(&state->ctx, (uint8_t *)H, 20);
+
+ /* the result is returned in the array hash_value[] */
+ srtp_sha1_final(&state->ctx, hash_value);
+
+ /* copy hash_value to *result */
+ for (i = 0; i < tag_len; i++) {
+ result[i] = ((uint8_t *)hash_value)[i];
+ }
+
+ debug_print(srtp_mod_hmac, "output: %s",
+ srtp_octet_string_hex_string((uint8_t *)hash_value, tag_len));
+
+ return srtp_err_status_ok;
+}
+
+/* begin test case 0 */
+/* clang-format off */
+static const uint8_t srtp_hmac_test_case_0_key[20] = {
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_hmac_test_case_0_data[8] = {
+ 0x48, 0x69, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65 /* "Hi There" */
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_hmac_test_case_0_tag[20] = {
+ 0xb6, 0x17, 0x31, 0x86, 0x55, 0x05, 0x72, 0x64,
+ 0xe2, 0x8b, 0xc0, 0xb6, 0xfb, 0x37, 0x8c, 0x8e,
+ 0xf1, 0x46, 0xbe, 0x00
+};
+/* clang-format on */
+
+static const srtp_auth_test_case_t srtp_hmac_test_case_0 = {
+ 20, /* octets in key */
+ srtp_hmac_test_case_0_key, /* key */
+ 8, /* octets in data */
+ srtp_hmac_test_case_0_data, /* data */
+ 20, /* octets in tag */
+ srtp_hmac_test_case_0_tag, /* tag */
+ NULL /* pointer to next testcase */
+};
+
+/* end test case 0 */
+
+static const char srtp_hmac_description[] =
+ "hmac sha-1 authentication function";
+
+/*
+ * srtp_auth_type_t hmac is the hmac metaobject
+ */
+
+const srtp_auth_type_t srtp_hmac = {
+ srtp_hmac_alloc, /* */
+ srtp_hmac_dealloc, /* */
+ srtp_hmac_init, /* */
+ srtp_hmac_compute, /* */
+ srtp_hmac_update, /* */
+ srtp_hmac_start, /* */
+ srtp_hmac_description, /* */
+ &srtp_hmac_test_case_0, /* */
+ SRTP_HMAC_SHA1 /* */
+};
diff --git a/netwerk/srtp/src/crypto/hash/hmac_ossl.c b/netwerk/srtp/src/crypto/hash/hmac_ossl.c
new file mode 100644
index 0000000000..8146438b05
--- /dev/null
+++ b/netwerk/srtp/src/crypto/hash/hmac_ossl.c
@@ -0,0 +1,273 @@
+/*
+ * hmac_ossl.c
+ *
+ * Implementation of hmac srtp_auth_type_t that leverages OpenSSL
+ *
+ * John A. Foley
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2013-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "auth.h"
+#include "alloc.h"
+#include "err.h" /* for srtp_debug */
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+
+#define SHA1_DIGEST_SIZE 20
+
+/* the debug module for authentiation */
+
+srtp_debug_module_t srtp_mod_hmac = {
+ 0, /* debugging is off by default */
+ "hmac sha-1 openssl" /* printable name for module */
+};
+
+static srtp_err_status_t srtp_hmac_alloc(srtp_auth_t **a,
+ int key_len,
+ int out_len)
+{
+ extern const srtp_auth_type_t srtp_hmac;
+
+ debug_print(srtp_mod_hmac, "allocating auth func with key length %d",
+ key_len);
+ debug_print(srtp_mod_hmac, " tag length %d",
+ out_len);
+
+ /* check output length - should be less than 20 bytes */
+ if (out_len > SHA1_DIGEST_SIZE) {
+ return srtp_err_status_bad_param;
+ }
+
+/* OpenSSL 1.1.0 made HMAC_CTX an opaque structure, which must be allocated
+ using HMAC_CTX_new. But this function doesn't exist in OpenSSL 1.0.x. */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || LIBRESSL_VERSION_NUMBER
+ {
+ /* allocate memory for auth and HMAC_CTX structures */
+ uint8_t *pointer;
+ HMAC_CTX *new_hmac_ctx;
+ pointer = (uint8_t *)srtp_crypto_alloc(sizeof(HMAC_CTX) +
+ sizeof(srtp_auth_t));
+ if (pointer == NULL) {
+ return srtp_err_status_alloc_fail;
+ }
+ *a = (srtp_auth_t *)pointer;
+ (*a)->state = pointer + sizeof(srtp_auth_t);
+ new_hmac_ctx = (HMAC_CTX *)((*a)->state);
+
+ HMAC_CTX_init(new_hmac_ctx);
+ }
+
+#else
+ *a = (srtp_auth_t *)srtp_crypto_alloc(sizeof(srtp_auth_t));
+ if (*a == NULL) {
+ return srtp_err_status_alloc_fail;
+ }
+
+ (*a)->state = HMAC_CTX_new();
+ if ((*a)->state == NULL) {
+ srtp_crypto_free(*a);
+ *a = NULL;
+ return srtp_err_status_alloc_fail;
+ }
+#endif
+
+ /* set pointers */
+ (*a)->type = &srtp_hmac;
+ (*a)->out_len = out_len;
+ (*a)->key_len = key_len;
+ (*a)->prefix_len = 0;
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_hmac_dealloc(srtp_auth_t *a)
+{
+ HMAC_CTX *hmac_ctx;
+
+ hmac_ctx = (HMAC_CTX *)a->state;
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || LIBRESSL_VERSION_NUMBER
+ HMAC_CTX_cleanup(hmac_ctx);
+
+ /* zeroize entire state*/
+ octet_string_set_to_zero(a, sizeof(HMAC_CTX) + sizeof(srtp_auth_t));
+
+#else
+ HMAC_CTX_free(hmac_ctx);
+
+ /* zeroize entire state*/
+ octet_string_set_to_zero(a, sizeof(srtp_auth_t));
+#endif
+
+ /* free memory */
+ srtp_crypto_free(a);
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_hmac_start(void *statev)
+{
+ HMAC_CTX *state = (HMAC_CTX *)statev;
+
+ if (HMAC_Init_ex(state, NULL, 0, NULL, NULL) == 0)
+ return srtp_err_status_auth_fail;
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_hmac_init(void *statev,
+ const uint8_t *key,
+ int key_len)
+{
+ HMAC_CTX *state = (HMAC_CTX *)statev;
+
+ if (HMAC_Init_ex(state, key, key_len, EVP_sha1(), NULL) == 0)
+ return srtp_err_status_auth_fail;
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_hmac_update(void *statev,
+ const uint8_t *message,
+ int msg_octets)
+{
+ HMAC_CTX *state = (HMAC_CTX *)statev;
+
+ debug_print(srtp_mod_hmac, "input: %s",
+ srtp_octet_string_hex_string(message, msg_octets));
+
+ if (HMAC_Update(state, message, msg_octets) == 0)
+ return srtp_err_status_auth_fail;
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_hmac_compute(void *statev,
+ const uint8_t *message,
+ int msg_octets,
+ int tag_len,
+ uint8_t *result)
+{
+ HMAC_CTX *state = (HMAC_CTX *)statev;
+ uint8_t hash_value[SHA1_DIGEST_SIZE];
+ int i;
+ unsigned int len;
+
+ /* check tag length, return error if we can't provide the value expected */
+ if (tag_len > SHA1_DIGEST_SIZE) {
+ return srtp_err_status_bad_param;
+ }
+
+ /* hash message, copy output into H */
+ if (HMAC_Update(state, message, msg_octets) == 0)
+ return srtp_err_status_auth_fail;
+
+ if (HMAC_Final(state, hash_value, &len) == 0)
+ return srtp_err_status_auth_fail;
+
+ if (len < tag_len)
+ return srtp_err_status_auth_fail;
+
+ /* copy hash_value to *result */
+ for (i = 0; i < tag_len; i++) {
+ result[i] = hash_value[i];
+ }
+
+ debug_print(srtp_mod_hmac, "output: %s",
+ srtp_octet_string_hex_string(hash_value, tag_len));
+
+ return srtp_err_status_ok;
+}
+
+/* begin test case 0 */
+/* clang-format off */
+static const uint8_t srtp_hmac_test_case_0_key[SHA1_DIGEST_SIZE] = {
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_hmac_test_case_0_data[8] = {
+ 0x48, 0x69, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65 /* "Hi There" */
+};
+/* clang-format on */
+
+/* clang-format off */
+static const uint8_t srtp_hmac_test_case_0_tag[SHA1_DIGEST_SIZE] = {
+ 0xb6, 0x17, 0x31, 0x86, 0x55, 0x05, 0x72, 0x64,
+ 0xe2, 0x8b, 0xc0, 0xb6, 0xfb, 0x37, 0x8c, 0x8e,
+ 0xf1, 0x46, 0xbe, 0x00
+};
+/* clang-format on */
+
+static const srtp_auth_test_case_t srtp_hmac_test_case_0 = {
+ sizeof(srtp_hmac_test_case_0_key), /* octets in key */
+ srtp_hmac_test_case_0_key, /* key */
+ sizeof(srtp_hmac_test_case_0_data), /* octets in data */
+ srtp_hmac_test_case_0_data, /* data */
+ sizeof(srtp_hmac_test_case_0_tag), /* octets in tag */
+ srtp_hmac_test_case_0_tag, /* tag */
+ NULL /* pointer to next testcase */
+};
+
+/* end test case 0 */
+
+static const char srtp_hmac_description[] =
+ "hmac sha-1 authentication function";
+
+/*
+ * srtp_auth_type_t hmac is the hmac metaobject
+ */
+
+const srtp_auth_type_t srtp_hmac = {
+ srtp_hmac_alloc, /* */
+ srtp_hmac_dealloc, /* */
+ srtp_hmac_init, /* */
+ srtp_hmac_compute, /* */
+ srtp_hmac_update, /* */
+ srtp_hmac_start, /* */
+ srtp_hmac_description, /* */
+ &srtp_hmac_test_case_0, /* */
+ SRTP_HMAC_SHA1 /* */
+};
diff --git a/netwerk/srtp/src/crypto/hash/null_auth.c b/netwerk/srtp/src/crypto/hash/null_auth.c
new file mode 100644
index 0000000000..5194417caf
--- /dev/null
+++ b/netwerk/srtp/src/crypto/hash/null_auth.c
@@ -0,0 +1,168 @@
+/*
+ * null_auth.c
+ *
+ * implements the do-nothing auth algorithm
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "null_auth.h"
+#include "err.h" /* for srtp_debug */
+#include "alloc.h"
+#include "cipher_types.h"
+
+static srtp_err_status_t srtp_null_auth_alloc(srtp_auth_t **a,
+ int key_len,
+ int out_len)
+{
+ extern const srtp_auth_type_t srtp_null_auth;
+ uint8_t *pointer;
+
+ debug_print(srtp_mod_auth, "allocating auth func with key length %d",
+ key_len);
+ debug_print(srtp_mod_auth, " tag length %d",
+ out_len);
+
+ /* allocate memory for auth and srtp_null_auth_ctx_t structures */
+ pointer = (uint8_t *)srtp_crypto_alloc(sizeof(srtp_null_auth_ctx_t) +
+ sizeof(srtp_auth_t));
+ if (pointer == NULL) {
+ return srtp_err_status_alloc_fail;
+ }
+
+ /* set pointers */
+ *a = (srtp_auth_t *)pointer;
+ (*a)->type = &srtp_null_auth;
+ (*a)->state = pointer + sizeof(srtp_auth_t);
+ (*a)->out_len = out_len;
+ (*a)->prefix_len = out_len;
+ (*a)->key_len = key_len;
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_null_auth_dealloc(srtp_auth_t *a)
+{
+ extern const srtp_auth_type_t srtp_null_auth;
+
+ /* zeroize entire state*/
+ octet_string_set_to_zero(a, sizeof(srtp_null_auth_ctx_t) +
+ sizeof(srtp_auth_t));
+
+ /* free memory */
+ srtp_crypto_free(a);
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_null_auth_init(void *statev,
+ const uint8_t *key,
+ int key_len)
+{
+ /* srtp_null_auth_ctx_t *state = (srtp_null_auth_ctx_t *)statev; */
+ /* accept any length of key, and do nothing */
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_null_auth_compute(void *statev,
+ const uint8_t *message,
+ int msg_octets,
+ int tag_len,
+ uint8_t *result)
+{
+ /* srtp_null_auth_ctx_t *state = (srtp_null_auth_ctx_t *)statev; */
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_null_auth_update(void *statev,
+ const uint8_t *message,
+ int msg_octets)
+{
+ /* srtp_null_auth_ctx_t *state = (srtp_null_auth_ctx_t *)statev; */
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_null_auth_start(void *statev)
+{
+ /* srtp_null_auth_ctx_t *state = (srtp_null_auth_ctx_t *)statev; */
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp_auth_type_t - defines description, test case, and null_auth
+ * metaobject
+ */
+
+/* begin test case 0 */
+
+static const srtp_auth_test_case_t srtp_null_auth_test_case_0 = {
+ 0, /* octets in key */
+ NULL, /* key */
+ 0, /* octets in data */
+ NULL, /* data */
+ 0, /* octets in tag */
+ NULL, /* tag */
+ NULL /* pointer to next testcase */
+};
+
+/* end test case 0 */
+
+static const char srtp_null_auth_description[] = "null authentication function";
+
+const srtp_auth_type_t srtp_null_auth = {
+ srtp_null_auth_alloc, /* */
+ srtp_null_auth_dealloc, /* */
+ srtp_null_auth_init, /* */
+ srtp_null_auth_compute, /* */
+ srtp_null_auth_update, /* */
+ srtp_null_auth_start, /* */
+ srtp_null_auth_description, /* */
+ &srtp_null_auth_test_case_0, /* */
+ SRTP_NULL_AUTH /* */
+};
diff --git a/netwerk/srtp/src/crypto/hash/sha1.c b/netwerk/srtp/src/crypto/hash/sha1.c
new file mode 100644
index 0000000000..91ebd1e396
--- /dev/null
+++ b/netwerk/srtp/src/crypto/hash/sha1.c
@@ -0,0 +1,474 @@
+/*
+ * sha1.c
+ *
+ * an implementation of the Secure Hash Algorithm v.1 (SHA-1),
+ * specified in FIPS 180-1
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "sha1.h"
+
+srtp_debug_module_t srtp_mod_sha1 = {
+ 0, /* debugging is off by default */
+ "sha-1" /* printable module name */
+};
+
+/* SN == Rotate left N bits */
+#define S1(X) ((X << 1) | (X >> 31))
+#define S5(X) ((X << 5) | (X >> 27))
+#define S30(X) ((X << 30) | (X >> 2))
+
+#define f0(B, C, D) ((B & C) | (~B & D))
+#define f1(B, C, D) (B ^ C ^ D)
+#define f2(B, C, D) ((B & C) | (B & D) | (C & D))
+#define f3(B, C, D) (B ^ C ^ D)
+
+/*
+ * nota bene: the variable K0 appears in the curses library, so we
+ * give longer names to these variables to avoid spurious warnings
+ * on systems that uses curses
+ */
+
+uint32_t SHA_K0 = 0x5A827999; /* Kt for 0 <= t <= 19 */
+uint32_t SHA_K1 = 0x6ED9EBA1; /* Kt for 20 <= t <= 39 */
+uint32_t SHA_K2 = 0x8F1BBCDC; /* Kt for 40 <= t <= 59 */
+uint32_t SHA_K3 = 0xCA62C1D6; /* Kt for 60 <= t <= 79 */
+
+void srtp_sha1(const uint8_t *msg, int octets_in_msg, uint32_t hash_value[5])
+{
+ srtp_sha1_ctx_t ctx;
+
+ srtp_sha1_init(&ctx);
+ srtp_sha1_update(&ctx, msg, octets_in_msg);
+ srtp_sha1_final(&ctx, hash_value);
+}
+
+/*
+ * srtp_sha1_core(M, H) computes the core compression function, where M is
+ * the next part of the message (in network byte order) and H is the
+ * intermediate state { H0, H1, ...} (in host byte order)
+ *
+ * this function does not do any of the padding required in the
+ * complete SHA1 function
+ *
+ * this function is used in the SEAL 3.0 key setup routines
+ * (crypto/cipher/seal.c)
+ */
+
+void srtp_sha1_core(const uint32_t M[16], uint32_t hash_value[5])
+{
+ uint32_t H0;
+ uint32_t H1;
+ uint32_t H2;
+ uint32_t H3;
+ uint32_t H4;
+ uint32_t W[80];
+ uint32_t A, B, C, D, E, TEMP;
+ int t;
+
+ /* copy hash_value into H0, H1, H2, H3, H4 */
+ H0 = hash_value[0];
+ H1 = hash_value[1];
+ H2 = hash_value[2];
+ H3 = hash_value[3];
+ H4 = hash_value[4];
+
+ /* copy/xor message into array */
+
+ W[0] = be32_to_cpu(M[0]);
+ W[1] = be32_to_cpu(M[1]);
+ W[2] = be32_to_cpu(M[2]);
+ W[3] = be32_to_cpu(M[3]);
+ W[4] = be32_to_cpu(M[4]);
+ W[5] = be32_to_cpu(M[5]);
+ W[6] = be32_to_cpu(M[6]);
+ W[7] = be32_to_cpu(M[7]);
+ W[8] = be32_to_cpu(M[8]);
+ W[9] = be32_to_cpu(M[9]);
+ W[10] = be32_to_cpu(M[10]);
+ W[11] = be32_to_cpu(M[11]);
+ W[12] = be32_to_cpu(M[12]);
+ W[13] = be32_to_cpu(M[13]);
+ W[14] = be32_to_cpu(M[14]);
+ W[15] = be32_to_cpu(M[15]);
+ TEMP = W[13] ^ W[8] ^ W[2] ^ W[0];
+ W[16] = S1(TEMP);
+ TEMP = W[14] ^ W[9] ^ W[3] ^ W[1];
+ W[17] = S1(TEMP);
+ TEMP = W[15] ^ W[10] ^ W[4] ^ W[2];
+ W[18] = S1(TEMP);
+ TEMP = W[16] ^ W[11] ^ W[5] ^ W[3];
+ W[19] = S1(TEMP);
+ TEMP = W[17] ^ W[12] ^ W[6] ^ W[4];
+ W[20] = S1(TEMP);
+ TEMP = W[18] ^ W[13] ^ W[7] ^ W[5];
+ W[21] = S1(TEMP);
+ TEMP = W[19] ^ W[14] ^ W[8] ^ W[6];
+ W[22] = S1(TEMP);
+ TEMP = W[20] ^ W[15] ^ W[9] ^ W[7];
+ W[23] = S1(TEMP);
+ TEMP = W[21] ^ W[16] ^ W[10] ^ W[8];
+ W[24] = S1(TEMP);
+ TEMP = W[22] ^ W[17] ^ W[11] ^ W[9];
+ W[25] = S1(TEMP);
+ TEMP = W[23] ^ W[18] ^ W[12] ^ W[10];
+ W[26] = S1(TEMP);
+ TEMP = W[24] ^ W[19] ^ W[13] ^ W[11];
+ W[27] = S1(TEMP);
+ TEMP = W[25] ^ W[20] ^ W[14] ^ W[12];
+ W[28] = S1(TEMP);
+ TEMP = W[26] ^ W[21] ^ W[15] ^ W[13];
+ W[29] = S1(TEMP);
+ TEMP = W[27] ^ W[22] ^ W[16] ^ W[14];
+ W[30] = S1(TEMP);
+ TEMP = W[28] ^ W[23] ^ W[17] ^ W[15];
+ W[31] = S1(TEMP);
+
+ /* process the remainder of the array */
+ for (t = 32; t < 80; t++) {
+ TEMP = W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16];
+ W[t] = S1(TEMP);
+ }
+
+ A = H0;
+ B = H1;
+ C = H2;
+ D = H3;
+ E = H4;
+
+ for (t = 0; t < 20; t++) {
+ TEMP = S5(A) + f0(B, C, D) + E + W[t] + SHA_K0;
+ E = D;
+ D = C;
+ C = S30(B);
+ B = A;
+ A = TEMP;
+ }
+ for (; t < 40; t++) {
+ TEMP = S5(A) + f1(B, C, D) + E + W[t] + SHA_K1;
+ E = D;
+ D = C;
+ C = S30(B);
+ B = A;
+ A = TEMP;
+ }
+ for (; t < 60; t++) {
+ TEMP = S5(A) + f2(B, C, D) + E + W[t] + SHA_K2;
+ E = D;
+ D = C;
+ C = S30(B);
+ B = A;
+ A = TEMP;
+ }
+ for (; t < 80; t++) {
+ TEMP = S5(A) + f3(B, C, D) + E + W[t] + SHA_K3;
+ E = D;
+ D = C;
+ C = S30(B);
+ B = A;
+ A = TEMP;
+ }
+
+ hash_value[0] = H0 + A;
+ hash_value[1] = H1 + B;
+ hash_value[2] = H2 + C;
+ hash_value[3] = H3 + D;
+ hash_value[4] = H4 + E;
+
+ return;
+}
+
+void srtp_sha1_init(srtp_sha1_ctx_t *ctx)
+{
+ /* initialize state vector */
+ ctx->H[0] = 0x67452301;
+ ctx->H[1] = 0xefcdab89;
+ ctx->H[2] = 0x98badcfe;
+ ctx->H[3] = 0x10325476;
+ ctx->H[4] = 0xc3d2e1f0;
+
+ /* indicate that message buffer is empty */
+ ctx->octets_in_buffer = 0;
+
+ /* reset message bit-count to zero */
+ ctx->num_bits_in_msg = 0;
+}
+
+void srtp_sha1_update(srtp_sha1_ctx_t *ctx,
+ const uint8_t *msg,
+ int octets_in_msg)
+{
+ int i;
+ uint8_t *buf = (uint8_t *)ctx->M;
+
+ /* update message bit-count */
+ ctx->num_bits_in_msg += octets_in_msg * 8;
+
+ /* loop over 16-word blocks of M */
+ while (octets_in_msg > 0) {
+ if (octets_in_msg + ctx->octets_in_buffer >= 64) {
+ /*
+ * copy words of M into msg buffer until that buffer is full,
+ * converting them into host byte order as needed
+ */
+ octets_in_msg -= (64 - ctx->octets_in_buffer);
+ for (i = ctx->octets_in_buffer; i < 64; i++) {
+ buf[i] = *msg++;
+ }
+ ctx->octets_in_buffer = 0;
+
+ /* process a whole block */
+
+ debug_print(srtp_mod_sha1, "(update) running srtp_sha1_core()",
+ NULL);
+
+ srtp_sha1_core(ctx->M, ctx->H);
+
+ } else {
+ debug_print(srtp_mod_sha1, "(update) not running srtp_sha1_core()",
+ NULL);
+
+ for (i = ctx->octets_in_buffer;
+ i < (ctx->octets_in_buffer + octets_in_msg); i++) {
+ buf[i] = *msg++;
+ }
+ ctx->octets_in_buffer += octets_in_msg;
+ octets_in_msg = 0;
+ }
+ }
+}
+
+/*
+ * srtp_sha1_final(ctx, output) computes the result for ctx and copies it
+ * into the twenty octets located at *output
+ */
+
+void srtp_sha1_final(srtp_sha1_ctx_t *ctx, uint32_t output[5])
+{
+ uint32_t A, B, C, D, E, TEMP;
+ uint32_t W[80];
+ int i, t;
+
+ /*
+ * process the remaining octets_in_buffer, padding and terminating as
+ * necessary
+ */
+ {
+ int tail = ctx->octets_in_buffer % 4;
+
+ /* copy/xor message into array */
+ for (i = 0; i < (ctx->octets_in_buffer + 3) / 4; i++) {
+ W[i] = be32_to_cpu(ctx->M[i]);
+ }
+
+ /* set the high bit of the octet immediately following the message */
+ switch (tail) {
+ case (3):
+ W[i - 1] = (be32_to_cpu(ctx->M[i - 1]) & 0xffffff00) | 0x80;
+ W[i] = 0x0;
+ break;
+ case (2):
+ W[i - 1] = (be32_to_cpu(ctx->M[i - 1]) & 0xffff0000) | 0x8000;
+ W[i] = 0x0;
+ break;
+ case (1):
+ W[i - 1] = (be32_to_cpu(ctx->M[i - 1]) & 0xff000000) | 0x800000;
+ W[i] = 0x0;
+ break;
+ case (0):
+ W[i] = 0x80000000;
+ break;
+ }
+
+ /* zeroize remaining words */
+ for (i++; i < 15; i++) {
+ W[i] = 0x0;
+ }
+
+ /*
+ * if there is room at the end of the word array, then set the
+ * last word to the bit-length of the message; otherwise, set that
+ * word to zero and then we need to do one more run of the
+ * compression algo.
+ */
+ if (ctx->octets_in_buffer < 56) {
+ W[15] = ctx->num_bits_in_msg;
+ } else if (ctx->octets_in_buffer < 60) {
+ W[15] = 0x0;
+ }
+
+ /* process the word array */
+ for (t = 16; t < 80; t++) {
+ TEMP = W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16];
+ W[t] = S1(TEMP);
+ }
+
+ A = ctx->H[0];
+ B = ctx->H[1];
+ C = ctx->H[2];
+ D = ctx->H[3];
+ E = ctx->H[4];
+
+ for (t = 0; t < 20; t++) {
+ TEMP = S5(A) + f0(B, C, D) + E + W[t] + SHA_K0;
+ E = D;
+ D = C;
+ C = S30(B);
+ B = A;
+ A = TEMP;
+ }
+ for (; t < 40; t++) {
+ TEMP = S5(A) + f1(B, C, D) + E + W[t] + SHA_K1;
+ E = D;
+ D = C;
+ C = S30(B);
+ B = A;
+ A = TEMP;
+ }
+ for (; t < 60; t++) {
+ TEMP = S5(A) + f2(B, C, D) + E + W[t] + SHA_K2;
+ E = D;
+ D = C;
+ C = S30(B);
+ B = A;
+ A = TEMP;
+ }
+ for (; t < 80; t++) {
+ TEMP = S5(A) + f3(B, C, D) + E + W[t] + SHA_K3;
+ E = D;
+ D = C;
+ C = S30(B);
+ B = A;
+ A = TEMP;
+ }
+
+ ctx->H[0] += A;
+ ctx->H[1] += B;
+ ctx->H[2] += C;
+ ctx->H[3] += D;
+ ctx->H[4] += E;
+ }
+
+ debug_print(srtp_mod_sha1, "(final) running srtp_sha1_core()", NULL);
+
+ if (ctx->octets_in_buffer >= 56) {
+ debug_print(srtp_mod_sha1, "(final) running srtp_sha1_core() again",
+ NULL);
+
+ /* we need to do one final run of the compression algo */
+
+ /*
+ * set initial part of word array to zeros, and set the
+ * final part to the number of bits in the message
+ */
+ for (i = 0; i < 15; i++) {
+ W[i] = 0x0;
+ }
+ W[15] = ctx->num_bits_in_msg;
+
+ /* process the word array */
+ for (t = 16; t < 80; t++) {
+ TEMP = W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16];
+ W[t] = S1(TEMP);
+ }
+
+ A = ctx->H[0];
+ B = ctx->H[1];
+ C = ctx->H[2];
+ D = ctx->H[3];
+ E = ctx->H[4];
+
+ for (t = 0; t < 20; t++) {
+ TEMP = S5(A) + f0(B, C, D) + E + W[t] + SHA_K0;
+ E = D;
+ D = C;
+ C = S30(B);
+ B = A;
+ A = TEMP;
+ }
+ for (; t < 40; t++) {
+ TEMP = S5(A) + f1(B, C, D) + E + W[t] + SHA_K1;
+ E = D;
+ D = C;
+ C = S30(B);
+ B = A;
+ A = TEMP;
+ }
+ for (; t < 60; t++) {
+ TEMP = S5(A) + f2(B, C, D) + E + W[t] + SHA_K2;
+ E = D;
+ D = C;
+ C = S30(B);
+ B = A;
+ A = TEMP;
+ }
+ for (; t < 80; t++) {
+ TEMP = S5(A) + f3(B, C, D) + E + W[t] + SHA_K3;
+ E = D;
+ D = C;
+ C = S30(B);
+ B = A;
+ A = TEMP;
+ }
+
+ ctx->H[0] += A;
+ ctx->H[1] += B;
+ ctx->H[2] += C;
+ ctx->H[3] += D;
+ ctx->H[4] += E;
+ }
+
+ /* copy result into output buffer */
+ output[0] = be32_to_cpu(ctx->H[0]);
+ output[1] = be32_to_cpu(ctx->H[1]);
+ output[2] = be32_to_cpu(ctx->H[2]);
+ output[3] = be32_to_cpu(ctx->H[3]);
+ output[4] = be32_to_cpu(ctx->H[4]);
+
+ /* indicate that message buffer in context is empty */
+ ctx->octets_in_buffer = 0;
+
+ return;
+}
diff --git a/netwerk/srtp/src/crypto/include/aes.h b/netwerk/srtp/src/crypto/include/aes.h
new file mode 100644
index 0000000000..779c3ac744
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/aes.h
@@ -0,0 +1,83 @@
+/*
+ * aes.h
+ *
+ * header file for the AES block cipher
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 AES_H
+#define AES_H
+
+#include "datatypes.h"
+#include "err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* aes internals */
+
+typedef struct {
+ v128_t round[15];
+ int num_rounds;
+} srtp_aes_expanded_key_t;
+
+srtp_err_status_t srtp_aes_expand_encryption_key(
+ const uint8_t *key,
+ int key_len,
+ srtp_aes_expanded_key_t *expanded_key);
+
+srtp_err_status_t srtp_aes_expand_decryption_key(
+ const uint8_t *key,
+ int key_len,
+ srtp_aes_expanded_key_t *expanded_key);
+
+void srtp_aes_encrypt(v128_t *plaintext,
+ const srtp_aes_expanded_key_t *exp_key);
+
+void srtp_aes_decrypt(v128_t *plaintext,
+ const srtp_aes_expanded_key_t *exp_key);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AES_H */
diff --git a/netwerk/srtp/src/crypto/include/aes_gcm.h b/netwerk/srtp/src/crypto/include/aes_gcm.h
new file mode 100644
index 0000000000..f59d04dbca
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/aes_gcm.h
@@ -0,0 +1,87 @@
+/*
+ * aes_gcm.h
+ *
+ * Header for AES Galois Counter Mode.
+ *
+ * John A. Foley
+ * Cisco Systems, Inc.
+ *
+ */
+/*
+ *
+ * Copyright (c) 2013-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 AES_GCM_H
+#define AES_GCM_H
+
+#include "cipher.h"
+#include "srtp.h"
+#include "datatypes.h"
+
+#ifdef OPENSSL
+
+#include <openssl/evp.h>
+#include <openssl/aes.h>
+
+typedef struct {
+ int key_size;
+ int tag_len;
+ EVP_CIPHER_CTX *ctx;
+ srtp_cipher_direction_t dir;
+} srtp_aes_gcm_ctx_t;
+
+#endif /* OPENSSL */
+
+#ifdef NSS
+
+#include <pk11pub.h>
+
+#define MAX_AD_SIZE 2048
+
+typedef struct {
+ int key_size;
+ int tag_size;
+ srtp_cipher_direction_t dir;
+ PK11SymKey *key;
+ uint8_t iv[12];
+ uint8_t aad[MAX_AD_SIZE];
+ int aad_size;
+ CK_GCM_PARAMS params;
+ uint8_t tag[16];
+} srtp_aes_gcm_ctx_t;
+
+#endif /* NSS */
+
+#endif /* AES_GCM_H */
diff --git a/netwerk/srtp/src/crypto/include/aes_icm.h b/netwerk/srtp/src/crypto/include/aes_icm.h
new file mode 100644
index 0000000000..8ded156a29
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/aes_icm.h
@@ -0,0 +1,62 @@
+/*
+ * aes_icm.h
+ *
+ * Header for AES Integer Counter Mode.
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 AES_ICM_H
+#define AES_ICM_H
+
+#include "aes.h"
+#include "cipher.h"
+
+typedef struct {
+ v128_t counter; /* holds the counter value */
+ v128_t offset; /* initial offset value */
+ v128_t keystream_buffer; /* buffers bytes of keystream */
+ srtp_aes_expanded_key_t expanded_key; /* the cipher key */
+ int bytes_in_buffer; /* number of unused bytes in buffer */
+ int key_size; /* AES key size + 14 byte SALT */
+} srtp_aes_icm_ctx_t;
+
+#endif /* AES_ICM_H */
diff --git a/netwerk/srtp/src/crypto/include/aes_icm_ext.h b/netwerk/srtp/src/crypto/include/aes_icm_ext.h
new file mode 100644
index 0000000000..6518d40b06
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/aes_icm_ext.h
@@ -0,0 +1,81 @@
+/*
+ * aes_icm.h
+ *
+ * Header for AES Integer Counter Mode.
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ */
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 AES_ICM_H
+#define AES_ICM_H
+
+#include "cipher.h"
+#include "datatypes.h"
+
+#ifdef OPENSSL
+
+#include <openssl/evp.h>
+#include <openssl/aes.h>
+
+typedef struct {
+ v128_t counter; /* holds the counter value */
+ v128_t offset; /* initial offset value */
+ int key_size;
+ EVP_CIPHER_CTX *ctx;
+} srtp_aes_icm_ctx_t;
+
+#endif /* OPENSSL */
+
+#ifdef NSS
+
+#include <pk11pub.h>
+
+typedef struct {
+ v128_t counter;
+ v128_t offset;
+ int key_size;
+ uint8_t iv[16];
+ PK11SymKey *key;
+ PK11Context *ctx;
+} srtp_aes_icm_ctx_t;
+
+#endif /* NSS */
+
+#endif /* AES_ICM_H */
diff --git a/netwerk/srtp/src/crypto/include/alloc.h b/netwerk/srtp/src/crypto/include/alloc.h
new file mode 100644
index 0000000000..1fc041014d
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/alloc.h
@@ -0,0 +1,76 @@
+/*
+ * alloc.h
+ *
+ * interface to memory allocation and deallocation, with optional debugging
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 CRYPTO_ALLOC_H
+#define CRYPTO_ALLOC_H
+
+#include "datatypes.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * srtp_crypto_alloc
+ *
+ * Allocates a block of memory of given size. The memory will be
+ * initialized to zero's. Free the memory with a call to srtp_crypto_free.
+ *
+ * returns pointer to memory on success or else NULL
+ */
+void *srtp_crypto_alloc(size_t size);
+
+/*
+ * srtp_crypto_free
+ *
+ * Frees the block of memory ptr previously allocated with
+ * srtp_crypto_alloc
+ */
+void srtp_crypto_free(void *ptr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CRYPTO_ALLOC_H */
diff --git a/netwerk/srtp/src/crypto/include/auth.h b/netwerk/srtp/src/crypto/include/auth.h
new file mode 100644
index 0000000000..774ea1687b
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/auth.h
@@ -0,0 +1,173 @@
+/*
+ * auth.h
+ *
+ * common interface to authentication functions
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 SRTP_AUTH_H
+#define SRTP_AUTH_H
+
+#include "srtp.h"
+#include "crypto_types.h" /* for values of auth_type_id_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef const struct srtp_auth_type_t *srtp_auth_type_pointer;
+typedef struct srtp_auth_t *srtp_auth_pointer_t;
+
+typedef srtp_err_status_t (*srtp_auth_alloc_func)(srtp_auth_pointer_t *ap,
+ int key_len,
+ int out_len);
+
+typedef srtp_err_status_t (*srtp_auth_init_func)(void *state,
+ const uint8_t *key,
+ int key_len);
+
+typedef srtp_err_status_t (*srtp_auth_dealloc_func)(srtp_auth_pointer_t ap);
+
+typedef srtp_err_status_t (*srtp_auth_compute_func)(void *state,
+ const uint8_t *buffer,
+ int octets_to_auth,
+ int tag_len,
+ uint8_t *tag);
+
+typedef srtp_err_status_t (*srtp_auth_update_func)(void *state,
+ const uint8_t *buffer,
+ int octets_to_auth);
+
+typedef srtp_err_status_t (*srtp_auth_start_func)(void *state);
+
+/* some syntactic sugar on these function types */
+#define srtp_auth_type_alloc(at, a, klen, outlen) \
+ ((at)->alloc((a), (klen), (outlen)))
+
+#define srtp_auth_init(a, key) \
+ (((a)->type)->init((a)->state, (key), ((a)->key_len)))
+
+#define srtp_auth_compute(a, buf, len, res) \
+ (((a)->type)->compute((a)->state, (buf), (len), (a)->out_len, (res)))
+
+#define srtp_auth_update(a, buf, len) \
+ (((a)->type)->update((a)->state, (buf), (len)))
+
+#define srtp_auth_start(a) (((a)->type)->start((a)->state))
+
+#define srtp_auth_dealloc(c) (((c)->type)->dealloc(c))
+
+/* functions to get information about a particular auth_t */
+int srtp_auth_get_key_length(const struct srtp_auth_t *a);
+
+int srtp_auth_get_tag_length(const struct srtp_auth_t *a);
+
+int srtp_auth_get_prefix_length(const struct srtp_auth_t *a);
+
+/*
+ * srtp_auth_test_case_t is a (list of) key/message/tag values that are
+ * known to be correct for a particular cipher. this data can be used
+ * to test an implementation in an on-the-fly self test of the
+ * correctness of the implementation. (see the srtp_auth_type_self_test()
+ * function below)
+ */
+typedef struct srtp_auth_test_case_t {
+ int key_length_octets; /* octets in key */
+ const uint8_t *key; /* key */
+ int data_length_octets; /* octets in data */
+ const uint8_t *data; /* data */
+ int tag_length_octets; /* octets in tag */
+ const uint8_t *tag; /* tag */
+ const struct srtp_auth_test_case_t
+ *next_test_case; /* pointer to next testcase */
+} srtp_auth_test_case_t;
+
+/* srtp_auth_type_t */
+typedef struct srtp_auth_type_t {
+ srtp_auth_alloc_func alloc;
+ srtp_auth_dealloc_func dealloc;
+ srtp_auth_init_func init;
+ srtp_auth_compute_func compute;
+ srtp_auth_update_func update;
+ srtp_auth_start_func start;
+ const char *description;
+ const srtp_auth_test_case_t *test_data;
+ srtp_auth_type_id_t id;
+} srtp_auth_type_t;
+
+typedef struct srtp_auth_t {
+ const srtp_auth_type_t *type;
+ void *state;
+ int out_len; /* length of output tag in octets */
+ int key_len; /* length of key in octets */
+ int prefix_len; /* length of keystream prefix */
+} srtp_auth_t;
+
+/*
+ * srtp_auth_type_self_test() tests an auth_type against test cases
+ * provided in an array of values of key/message/tag that is known to
+ * be good
+ */
+srtp_err_status_t srtp_auth_type_self_test(const srtp_auth_type_t *at);
+
+/*
+ * srtp_auth_type_test() tests an auth_type against external test cases
+ * provided in an array of values of key/message/tag that is known to
+ * be good
+ */
+srtp_err_status_t srtp_auth_type_test(const srtp_auth_type_t *at,
+ const srtp_auth_test_case_t *test_data);
+
+/*
+ * srtp_replace_auth_type(ct, id)
+ *
+ * replaces srtp's kernel's auth type implementation for the auth_type id
+ * with a new one passed in externally. The new auth type must pass all the
+ * existing auth_type's self tests as well as its own.
+ */
+srtp_err_status_t srtp_replace_auth_type(const srtp_auth_type_t *ct,
+ srtp_auth_type_id_t id);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SRTP_AUTH_H */
diff --git a/netwerk/srtp/src/crypto/include/cipher.h b/netwerk/srtp/src/crypto/include/cipher.h
new file mode 100644
index 0000000000..4f14e3560f
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/cipher.h
@@ -0,0 +1,248 @@
+/*
+ * cipher.h
+ *
+ * common interface to ciphers
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 SRTP_CIPHER_H
+#define SRTP_CIPHER_H
+
+#include "srtp.h"
+#include "crypto_types.h" /* for values of cipher_type_id_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * srtp_cipher_direction_t defines a particular cipher operation.
+ *
+ * A srtp_cipher_direction_t is an enum that describes a particular cipher
+ * operation, i.e. encryption or decryption. For some ciphers, this
+ * distinction does not matter, but for others, it is essential.
+ */
+typedef enum {
+ srtp_direction_encrypt, /**< encryption (convert plaintext to ciphertext) */
+ srtp_direction_decrypt, /**< decryption (convert ciphertext to plaintext) */
+ srtp_direction_any /**< encryption or decryption */
+} srtp_cipher_direction_t;
+
+/*
+ * the srtp_cipher_pointer_t definition is needed
+ * as srtp_cipher_t is not yet defined
+ */
+typedef struct srtp_cipher_t *srtp_cipher_pointer_t;
+
+/*
+ * a srtp_cipher_alloc_func_t allocates (but does not initialize) a
+ * srtp_cipher_t
+ */
+typedef srtp_err_status_t (*srtp_cipher_alloc_func_t)(srtp_cipher_pointer_t *cp,
+ int key_len,
+ int tag_len);
+
+/*
+ * a srtp_cipher_init_func_t [re-]initializes a cipher_t with a given key
+ */
+typedef srtp_err_status_t (*srtp_cipher_init_func_t)(void *state,
+ const uint8_t *key);
+
+/* a srtp_cipher_dealloc_func_t de-allocates a cipher_t */
+typedef srtp_err_status_t (*srtp_cipher_dealloc_func_t)(
+ srtp_cipher_pointer_t cp);
+
+/*
+ * a srtp_cipher_set_aad_func_t processes the AAD data for AEAD ciphers
+ */
+typedef srtp_err_status_t (*srtp_cipher_set_aad_func_t)(void *state,
+ const uint8_t *aad,
+ uint32_t aad_len);
+
+/* a srtp_cipher_encrypt_func_t encrypts data in-place */
+typedef srtp_err_status_t (*srtp_cipher_encrypt_func_t)(
+ void *state,
+ uint8_t *buffer,
+ unsigned int *octets_to_encrypt);
+
+/* a srtp_cipher_decrypt_func_t decrypts data in-place */
+typedef srtp_err_status_t (*srtp_cipher_decrypt_func_t)(
+ void *state,
+ uint8_t *buffer,
+ unsigned int *octets_to_decrypt);
+
+/*
+ * a srtp_cipher_set_iv_func_t function sets the current initialization vector
+ */
+typedef srtp_err_status_t (*srtp_cipher_set_iv_func_t)(
+ void *state,
+ uint8_t *iv,
+ srtp_cipher_direction_t direction);
+
+/*
+ * a cipher_get_tag_func_t function is used to get the authentication
+ * tag that was calculated by an AEAD cipher.
+ */
+typedef srtp_err_status_t (*srtp_cipher_get_tag_func_t)(void *state,
+ uint8_t *tag,
+ uint32_t *len);
+
+/*
+ * srtp_cipher_test_case_t is a (list of) key, salt, plaintext, ciphertext,
+ * and aad values that are known to be correct for a
+ * particular cipher. this data can be used to test an implementation
+ * in an on-the-fly self test of the correctness of the implementation.
+ * (see the srtp_cipher_type_self_test() function below)
+ */
+typedef struct srtp_cipher_test_case_t {
+ int key_length_octets; /* octets in key */
+ const uint8_t *key; /* key */
+ uint8_t *idx; /* packet index */
+ unsigned int plaintext_length_octets; /* octets in plaintext */
+ const uint8_t *plaintext; /* plaintext */
+ unsigned int ciphertext_length_octets; /* octets in plaintext */
+ const uint8_t *ciphertext; /* ciphertext */
+ int aad_length_octets; /* octets in AAD */
+ const uint8_t *aad; /* AAD */
+ int tag_length_octets; /* Length of AEAD tag */
+ const struct srtp_cipher_test_case_t
+ *next_test_case; /* pointer to next testcase */
+} srtp_cipher_test_case_t;
+
+/* srtp_cipher_type_t defines the 'metadata' for a particular cipher type */
+typedef struct srtp_cipher_type_t {
+ srtp_cipher_alloc_func_t alloc;
+ srtp_cipher_dealloc_func_t dealloc;
+ srtp_cipher_init_func_t init;
+ srtp_cipher_set_aad_func_t set_aad;
+ srtp_cipher_encrypt_func_t encrypt;
+ srtp_cipher_encrypt_func_t decrypt;
+ srtp_cipher_set_iv_func_t set_iv;
+ srtp_cipher_get_tag_func_t get_tag;
+ const char *description;
+ const srtp_cipher_test_case_t *test_data;
+ srtp_cipher_type_id_t id;
+} srtp_cipher_type_t;
+
+/*
+ * srtp_cipher_t defines an instantiation of a particular cipher, with fixed
+ * key length, key and salt values
+ */
+typedef struct srtp_cipher_t {
+ const srtp_cipher_type_t *type;
+ void *state;
+ int key_len;
+ int algorithm;
+} srtp_cipher_t;
+
+/* some bookkeeping functions */
+int srtp_cipher_get_key_length(const srtp_cipher_t *c);
+
+/*
+ * srtp_cipher_type_self_test() tests a cipher against test cases provided in
+ * an array of values of key/srtp_xtd_seq_num_t/plaintext/ciphertext
+ * that is known to be good
+ */
+srtp_err_status_t srtp_cipher_type_self_test(const srtp_cipher_type_t *ct);
+
+/*
+ * srtp_cipher_type_test() tests a cipher against external test cases provided
+ * in
+ * an array of values of key/srtp_xtd_seq_num_t/plaintext/ciphertext
+ * that is known to be good
+ */
+srtp_err_status_t srtp_cipher_type_test(
+ const srtp_cipher_type_t *ct,
+ const srtp_cipher_test_case_t *test_data);
+
+/*
+ * srtp_cipher_bits_per_second(c, l, t) computes (an estimate of) the
+ * number of bits that a cipher implementation can encrypt in a second
+ *
+ * c is a cipher (which MUST be allocated and initialized already), l
+ * is the length in octets of the test data to be encrypted, and t is
+ * the number of trials
+ *
+ * if an error is encountered, then the value 0 is returned
+ */
+uint64_t srtp_cipher_bits_per_second(srtp_cipher_t *c,
+ int octets_in_buffer,
+ int num_trials);
+
+srtp_err_status_t srtp_cipher_type_alloc(const srtp_cipher_type_t *ct,
+ srtp_cipher_t **c,
+ int key_len,
+ int tlen);
+srtp_err_status_t srtp_cipher_dealloc(srtp_cipher_t *c);
+srtp_err_status_t srtp_cipher_init(srtp_cipher_t *c, const uint8_t *key);
+srtp_err_status_t srtp_cipher_set_iv(srtp_cipher_t *c,
+ uint8_t *iv,
+ int direction);
+srtp_err_status_t srtp_cipher_output(srtp_cipher_t *c,
+ uint8_t *buffer,
+ uint32_t *num_octets_to_output);
+srtp_err_status_t srtp_cipher_encrypt(srtp_cipher_t *c,
+ uint8_t *buffer,
+ uint32_t *num_octets_to_output);
+srtp_err_status_t srtp_cipher_decrypt(srtp_cipher_t *c,
+ uint8_t *buffer,
+ uint32_t *num_octets_to_output);
+srtp_err_status_t srtp_cipher_get_tag(srtp_cipher_t *c,
+ uint8_t *buffer,
+ uint32_t *tag_len);
+srtp_err_status_t srtp_cipher_set_aad(srtp_cipher_t *c,
+ const uint8_t *aad,
+ uint32_t aad_len);
+
+/*
+ * srtp_replace_cipher_type(ct, id)
+ *
+ * replaces srtp's existing cipher implementation for the cipher_type id
+ * with a new one passed in externally. The new cipher must pass all the
+ * existing cipher_type's self tests as well as its own.
+ */
+srtp_err_status_t srtp_replace_cipher_type(const srtp_cipher_type_t *ct,
+ srtp_cipher_type_id_t id);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SRTP_CIPHER_H */
diff --git a/netwerk/srtp/src/crypto/include/cipher_types.h b/netwerk/srtp/src/crypto/include/cipher_types.h
new file mode 100644
index 0000000000..18f0328fbb
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/cipher_types.h
@@ -0,0 +1,84 @@
+/*
+ *
+ * Copyright(c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 CIHPER_TYPES_H
+#define CIHPER_TYPES_H
+
+#include "cipher.h"
+#include "auth.h"
+
+/*
+ * cipher types that can be included in the kernel
+ */
+
+extern const srtp_cipher_type_t srtp_null_cipher;
+extern const srtp_cipher_type_t srtp_aes_icm_128;
+extern const srtp_cipher_type_t srtp_aes_icm_256;
+#ifdef GCM
+extern const srtp_cipher_type_t srtp_aes_icm_192;
+extern const srtp_cipher_type_t srtp_aes_gcm_128;
+extern const srtp_cipher_type_t srtp_aes_gcm_256;
+#endif
+
+/*
+ * auth func types that can be included in the kernel
+ */
+
+extern const srtp_auth_type_t srtp_null_auth;
+extern const srtp_auth_type_t srtp_hmac;
+
+/*
+ * other generic debug modules that can be included in the kernel
+ */
+
+extern srtp_debug_module_t srtp_mod_auth;
+extern srtp_debug_module_t srtp_mod_cipher;
+extern srtp_debug_module_t srtp_mod_stat;
+extern srtp_debug_module_t srtp_mod_alloc;
+
+/* debug modules for cipher types */
+extern srtp_debug_module_t srtp_mod_aes_icm;
+#ifdef OPENSSL
+extern srtp_debug_module_t srtp_mod_aes_gcm;
+#endif
+#ifdef NSS
+extern srtp_debug_module_t srtp_mod_aes_gcm;
+#endif
+
+/* debug modules for auth types */
+extern srtp_debug_module_t srtp_mod_hmac;
+
+#endif
diff --git a/netwerk/srtp/src/crypto/include/crypto_kernel.h b/netwerk/srtp/src/crypto/include/crypto_kernel.h
new file mode 100644
index 0000000000..1f8dfa7712
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/crypto_kernel.h
@@ -0,0 +1,215 @@
+/*
+ * crypto_kernel.h
+ *
+ * header for the cryptographic kernel
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 CRYPTO_KERNEL
+#define CRYPTO_KERNEL
+
+#include "cipher.h"
+#include "auth.h"
+#include "err.h"
+#include "crypto_types.h"
+#include "key.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * crypto_kernel_state_t defines the possible states:
+ *
+ * insecure - not yet initialized
+ * secure - initialized and passed self-tests
+ */
+typedef enum {
+ srtp_crypto_kernel_state_insecure,
+ srtp_crypto_kernel_state_secure
+} srtp_crypto_kernel_state_t;
+
+/*
+ * linked list of cipher types
+ */
+typedef struct srtp_kernel_cipher_type {
+ srtp_cipher_type_id_t id;
+ const srtp_cipher_type_t *cipher_type;
+ struct srtp_kernel_cipher_type *next;
+} srtp_kernel_cipher_type_t;
+
+/*
+ * linked list of auth types
+ */
+typedef struct srtp_kernel_auth_type {
+ srtp_auth_type_id_t id;
+ const srtp_auth_type_t *auth_type;
+ struct srtp_kernel_auth_type *next;
+} srtp_kernel_auth_type_t;
+
+/*
+ * linked list of debug modules
+ */
+typedef struct srtp_kernel_debug_module {
+ srtp_debug_module_t *mod;
+ struct srtp_kernel_debug_module *next;
+} srtp_kernel_debug_module_t;
+
+/*
+ * crypto_kernel_t is the data structure for the crypto kernel
+ *
+ * note that there is *exactly one* instance of this data type,
+ * a global variable defined in crypto_kernel.c
+ */
+typedef struct {
+ srtp_crypto_kernel_state_t state; /* current state of kernel */
+ srtp_kernel_cipher_type_t *cipher_type_list; /* list of all cipher types */
+ srtp_kernel_auth_type_t *auth_type_list; /* list of all auth func types */
+ srtp_kernel_debug_module_t
+ *debug_module_list; /* list of all debug modules */
+} srtp_crypto_kernel_t;
+
+/*
+ * srtp_crypto_kernel_t external api
+ */
+
+/*
+ * The function srtp_crypto_kernel_init() initialized the crypto kernel and
+ * runs the self-test operations on the random number generators and
+ * crypto algorithms. Possible return values are:
+ *
+ * srtp_err_status_ok initialization successful
+ * <other> init failure
+ *
+ * If any value other than srtp_err_status_ok is returned, the
+ * crypto_kernel MUST NOT be used.
+ */
+srtp_err_status_t srtp_crypto_kernel_init(void);
+
+/*
+ * The function srtp_crypto_kernel_shutdown() de-initializes the
+ * crypto_kernel, zeroizes keys and other cryptographic material, and
+ * deallocates any dynamically allocated memory. Possible return
+ * values are:
+ *
+ * srtp_err_status_ok shutdown successful
+ * <other> shutdown failure
+ *
+ */
+srtp_err_status_t srtp_crypto_kernel_shutdown(void);
+
+/*
+ * The function srtp_crypto_kernel_stats() checks the the crypto_kernel,
+ * running tests on the ciphers, auth funcs, and rng, and prints out a
+ * status report. Possible return values are:
+ *
+ * srtp_err_status_ok all tests were passed
+ * <other> a test failed
+ *
+ */
+srtp_err_status_t srtp_crypto_kernel_status(void);
+
+/*
+ * srtp_crypto_kernel_list_debug_modules() outputs a list of debugging modules
+ *
+ */
+srtp_err_status_t srtp_crypto_kernel_list_debug_modules(void);
+
+/*
+ * srtp_crypto_kernel_load_cipher_type()
+ *
+ */
+srtp_err_status_t srtp_crypto_kernel_load_cipher_type(
+ const srtp_cipher_type_t *ct,
+ srtp_cipher_type_id_t id);
+
+srtp_err_status_t srtp_crypto_kernel_load_auth_type(const srtp_auth_type_t *ct,
+ srtp_auth_type_id_t id);
+
+srtp_err_status_t srtp_crypto_kernel_load_debug_module(
+ srtp_debug_module_t *new_dm);
+
+/*
+ * srtp_crypto_kernel_alloc_cipher(id, cp, key_len);
+ *
+ * allocates a cipher of type id at location *cp, with key length
+ * key_len octets. Return values are:
+ *
+ * srtp_err_status_ok no problems
+ * srtp_err_status_alloc_fail an allocation failure occured
+ * srtp_err_status_fail couldn't find cipher with identifier 'id'
+ */
+srtp_err_status_t srtp_crypto_kernel_alloc_cipher(srtp_cipher_type_id_t id,
+ srtp_cipher_pointer_t *cp,
+ int key_len,
+ int tag_len);
+
+/*
+ * srtp_crypto_kernel_alloc_auth(id, ap, key_len, tag_len);
+ *
+ * allocates an auth function of type id at location *ap, with key
+ * length key_len octets and output tag length of tag_len. Return
+ * values are:
+ *
+ * srtp_err_status_ok no problems
+ * srtp_err_status_alloc_fail an allocation failure occured
+ * srtp_err_status_fail couldn't find auth with identifier 'id'
+ */
+srtp_err_status_t srtp_crypto_kernel_alloc_auth(srtp_auth_type_id_t id,
+ srtp_auth_pointer_t *ap,
+ int key_len,
+ int tag_len);
+
+/*
+ * srtp_crypto_kernel_set_debug_module(mod_name, v)
+ *
+ * sets dynamic debugging to the value v (0 for off, 1 for on) for the
+ * debug module with the name mod_name
+ *
+ * returns srtp_err_status_ok on success, srtp_err_status_fail otherwise
+ */
+srtp_err_status_t srtp_crypto_kernel_set_debug_module(const char *mod_name,
+ int v);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CRYPTO_KERNEL */
diff --git a/netwerk/srtp/src/crypto/include/crypto_types.h b/netwerk/srtp/src/crypto/include/crypto_types.h
new file mode 100644
index 0000000000..7fd3178b0b
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/crypto_types.h
@@ -0,0 +1,116 @@
+/*
+ * crypto_types.h
+ *
+ * constants for cipher types and auth func types
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 SRTP_CRYPTO_TYPES_H
+#define SRTP_CRYPTO_TYPES_H
+
+/*
+ * The null cipher performs no encryption.
+ *
+ * The SRTP_NULL_CIPHER leaves its inputs unaltered, during both the
+ * encryption and decryption operations. This cipher can be chosen
+ * to indicate that no encryption is to be performed.
+ */
+#define SRTP_NULL_CIPHER 0
+
+/*
+ * AES-128 Integer Counter Mode (AES ICM)
+ *
+ * AES-128 ICM is the variant of counter mode that is used by
+ * Secure RTP. This cipher uses a 16-octet key concatenated with a
+ * 14-octet offset (or salt) value.
+ */
+#define SRTP_AES_ICM_128 1
+
+/*
+ * AES-192 Integer Counter Mode (AES ICM)
+ *
+ * AES-128 ICM is the variant of counter mode that is used by
+ * Secure RTP. This cipher uses a 24-octet key concatenated with a
+ * 14-octet offset (or salt) value.
+ */
+#define SRTP_AES_ICM_192 4
+
+/*
+ * AES-256 Integer Counter Mode (AES ICM)
+ *
+ * AES-128 ICM is the variant of counter mode that is used by
+ * Secure RTP. This cipher uses a 32-octet key concatenated with a
+ * 14-octet offset (or salt) value.
+ */
+#define SRTP_AES_ICM_256 5
+
+/*
+ * AES-128_GCM Galois Counter Mode (AES GCM)
+ *
+ * AES-128 GCM is the variant of galois counter mode that is used by
+ * Secure RTP. This cipher uses a 16-octet key.
+ */
+#define SRTP_AES_GCM_128 6
+
+/*
+ * AES-256_GCM Galois Counter Mode (AES GCM)
+ *
+ * AES-256 GCM is the variant of galois counter mode that is used by
+ * Secure RTP. This cipher uses a 32-octet key.
+ */
+#define SRTP_AES_GCM_256 7
+
+/*
+ * The null authentication function performs no authentication.
+ *
+ * The NULL_AUTH function does nothing, and can be selected to indicate
+ * that authentication should not be performed.
+ */
+#define SRTP_NULL_AUTH 0
+
+/*
+ * HMAC-SHA1
+ *
+ * SRTP_HMAC_SHA1 implements the Hash-based MAC using the NIST Secure
+ * Hash Algorithm version 1 (SHA1).
+ */
+#define SRTP_HMAC_SHA1 3
+
+#endif /* SRTP_CRYPTO_TYPES_H */
diff --git a/netwerk/srtp/src/crypto/include/datatypes.h b/netwerk/srtp/src/crypto/include/datatypes.h
new file mode 100644
index 0000000000..182cca25b9
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/datatypes.h
@@ -0,0 +1,378 @@
+/*
+ * datatypes.h
+ *
+ * data types for bit vectors and finite fields
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 DATATYPES_H
+#define DATATYPES_H
+
+#include "integers.h" /* definitions of uint32_t, et cetera */
+#include "alloc.h"
+
+#include <stdarg.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#elif defined HAVE_WINSOCK2_H
+#include <winsock2.h>
+#else
+#error "Platform not recognized"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* if DATATYPES_USE_MACROS is defined, then little functions are macros */
+#define DATATYPES_USE_MACROS
+
+typedef union {
+ uint8_t v8[2];
+ uint16_t value;
+} v16_t;
+
+typedef union {
+ uint8_t v8[4];
+ uint16_t v16[2];
+ uint32_t value;
+} v32_t;
+
+typedef union {
+ uint8_t v8[8];
+ uint16_t v16[4];
+ uint32_t v32[2];
+ uint64_t value;
+} v64_t;
+
+typedef union {
+ uint8_t v8[16];
+ uint16_t v16[8];
+ uint32_t v32[4];
+ uint64_t v64[2];
+} v128_t;
+
+typedef union {
+ uint8_t v8[32];
+ uint16_t v16[16];
+ uint32_t v32[8];
+ uint64_t v64[4];
+} v256_t;
+
+/* some useful and simple math functions */
+
+#define pow_2(X) ((unsigned int)1 << (X)) /* 2^X */
+
+#define pow_minus_one(X) ((X) ? -1 : 1) /* (-1)^X */
+
+/*
+ * octet_get_weight(x) returns the hamming weight (number of bits equal to
+ * one) in the octet x
+ */
+
+int octet_get_weight(uint8_t octet);
+
+#define MAX_PRINT_STRING_LEN 1024
+
+char *srtp_octet_string_hex_string(const void *str, int length);
+
+char *v128_bit_string(v128_t *x);
+
+char *v128_hex_string(v128_t *x);
+
+void v128_copy_octet_string(v128_t *x, const uint8_t s[16]);
+
+void v128_left_shift(v128_t *x, int shift_index);
+
+void v128_right_shift(v128_t *x, int shift_index);
+
+/*
+ * the following macros define the data manipulation functions
+ *
+ * If DATATYPES_USE_MACROS is defined, then these macros are used
+ * directly (and function call overhead is avoided). Otherwise,
+ * the macros are used through the functions defined in datatypes.c
+ * (and the compiler provides better warnings).
+ */
+
+#define _v128_set_to_zero(x) \
+ ((x)->v32[0] = 0, (x)->v32[1] = 0, (x)->v32[2] = 0, (x)->v32[3] = 0)
+
+#define _v128_copy(x, y) \
+ ((x)->v32[0] = (y)->v32[0], (x)->v32[1] = (y)->v32[1], \
+ (x)->v32[2] = (y)->v32[2], (x)->v32[3] = (y)->v32[3])
+
+#define _v128_xor(z, x, y) \
+ ((z)->v32[0] = (x)->v32[0] ^ (y)->v32[0], \
+ (z)->v32[1] = (x)->v32[1] ^ (y)->v32[1], \
+ (z)->v32[2] = (x)->v32[2] ^ (y)->v32[2], \
+ (z)->v32[3] = (x)->v32[3] ^ (y)->v32[3])
+
+#define _v128_and(z, x, y) \
+ ((z)->v32[0] = (x)->v32[0] & (y)->v32[0], \
+ (z)->v32[1] = (x)->v32[1] & (y)->v32[1], \
+ (z)->v32[2] = (x)->v32[2] & (y)->v32[2], \
+ (z)->v32[3] = (x)->v32[3] & (y)->v32[3])
+
+#define _v128_or(z, x, y) \
+ ((z)->v32[0] = (x)->v32[0] | (y)->v32[0], \
+ (z)->v32[1] = (x)->v32[1] | (y)->v32[1], \
+ (z)->v32[2] = (x)->v32[2] | (y)->v32[2], \
+ (z)->v32[3] = (x)->v32[3] | (y)->v32[3])
+
+#define _v128_complement(x) \
+ ((x)->v32[0] = ~(x)->v32[0], (x)->v32[1] = ~(x)->v32[1], \
+ (x)->v32[2] = ~(x)->v32[2], (x)->v32[3] = ~(x)->v32[3])
+
+/* ok for NO_64BIT_MATH if it can compare uint64_t's (even as structures) */
+#define _v128_is_eq(x, y) \
+ (((x)->v64[0] == (y)->v64[0]) && ((x)->v64[1] == (y)->v64[1]))
+
+#ifdef NO_64BIT_MATH
+#define _v128_xor_eq(z, x) \
+ ((z)->v32[0] ^= (x)->v32[0], (z)->v32[1] ^= (x)->v32[1], \
+ (z)->v32[2] ^= (x)->v32[2], (z)->v32[3] ^= (x)->v32[3])
+#else
+#define _v128_xor_eq(z, x) \
+ ((z)->v64[0] ^= (x)->v64[0], (z)->v64[1] ^= (x)->v64[1])
+#endif
+
+/* NOTE! This assumes an odd ordering! */
+/* This will not be compatible directly with math on some processors */
+/* bit 0 is first 32-bit word, low order bit. in little-endian, that's
+ the first byte of the first 32-bit word. In big-endian, that's
+ the 3rd byte of the first 32-bit word */
+/* The get/set bit code is used by the replay code ONLY, and it doesn't
+ really care which bit is which. AES does care which bit is which, but
+ doesn't use the 128-bit get/set or 128-bit shifts */
+
+#define _v128_get_bit(x, bit) (((((x)->v32[(bit) >> 5]) >> ((bit)&31)) & 1))
+
+#define _v128_set_bit(x, bit) \
+ ((((x)->v32[(bit) >> 5]) |= ((uint32_t)1 << ((bit)&31))))
+
+#define _v128_clear_bit(x, bit) \
+ ((((x)->v32[(bit) >> 5]) &= ~((uint32_t)1 << ((bit)&31))))
+
+#define _v128_set_bit_to(x, bit, value) \
+ ((value) ? _v128_set_bit(x, bit) : _v128_clear_bit(x, bit))
+
+#ifdef DATATYPES_USE_MACROS /* little functions are really macros */
+
+#define v128_set_to_zero(z) _v128_set_to_zero(z)
+#define v128_copy(z, x) _v128_copy(z, x)
+#define v128_xor(z, x, y) _v128_xor(z, x, y)
+#define v128_and(z, x, y) _v128_and(z, x, y)
+#define v128_or(z, x, y) _v128_or(z, x, y)
+#define v128_complement(x) _v128_complement(x)
+#define v128_is_eq(x, y) _v128_is_eq(x, y)
+#define v128_xor_eq(x, y) _v128_xor_eq(x, y)
+#define v128_get_bit(x, i) _v128_get_bit(x, i)
+#define v128_set_bit(x, i) _v128_set_bit(x, i)
+#define v128_clear_bit(x, i) _v128_clear_bit(x, i)
+#define v128_set_bit_to(x, i, y) _v128_set_bit_to(x, i, y)
+
+#else
+
+void v128_set_to_zero(v128_t *x);
+
+int v128_is_eq(const v128_t *x, const v128_t *y);
+
+void v128_copy(v128_t *x, const v128_t *y);
+
+void v128_xor(v128_t *z, v128_t *x, v128_t *y);
+
+void v128_and(v128_t *z, v128_t *x, v128_t *y);
+
+void v128_or(v128_t *z, v128_t *x, v128_t *y);
+
+void v128_complement(v128_t *x);
+
+int v128_get_bit(const v128_t *x, int i);
+
+void v128_set_bit(v128_t *x, int i);
+
+void v128_clear_bit(v128_t *x, int i);
+
+void v128_set_bit_to(v128_t *x, int i, int y);
+
+#endif /* DATATYPES_USE_MACROS */
+
+/*
+ * octet_string_is_eq(a, b, len) returns 1 if the length len strings a
+ * and b are not equal. It returns 0 otherwise. The running time of the
+ * comparison depends only on len, making this safe to use for (e.g.)
+ * verifying authentication tags.
+ */
+
+int octet_string_is_eq(uint8_t *a, uint8_t *b, int len);
+
+/*
+ * A portable way to zero out memory as recommended by
+ * https://cryptocoding.net/index.php/Coding_rules#Clean_memory_of_secret_data
+ * This is used to zero memory when OPENSSL_cleanse() is not available.
+ */
+void srtp_cleanse(void *s, size_t len);
+
+/*
+ * Functions as a wrapper that delegates to either srtp_cleanse() or
+ * OPENSSL_cleanse() if available to zero memory.
+ */
+void octet_string_set_to_zero(void *s, size_t len);
+
+#if defined(HAVE_CONFIG_H)
+
+/*
+ * Convert big endian integers to CPU byte order.
+ */
+#ifdef WORDS_BIGENDIAN
+/* Nothing to do. */
+#define be32_to_cpu(x) (x)
+#define be64_to_cpu(x) (x)
+#elif defined(HAVE_BYTESWAP_H)
+/* We have (hopefully) optimized versions in byteswap.h */
+#include <byteswap.h>
+#define be32_to_cpu(x) bswap_32((x))
+#define be64_to_cpu(x) bswap_64((x))
+#else /* WORDS_BIGENDIAN */
+
+#if defined(__GNUC__) && defined(HAVE_X86)
+/* Fall back. */
+static inline uint32_t be32_to_cpu(uint32_t v)
+{
+ /* optimized for x86. */
+ asm("bswap %0" : "=r"(v) : "0"(v));
+ return v;
+}
+#else /* HAVE_X86 */
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#elif defined HAVE_WINSOCK2_H
+#include <winsock2.h>
+#endif /* HAVE_NETINET_IN_H */
+#define be32_to_cpu(x) ntohl((x))
+#endif /* HAVE_X86 */
+
+static inline uint64_t be64_to_cpu(uint64_t v)
+{
+#ifdef NO_64BIT_MATH
+ /* use the make64 functions to do 64-bit math */
+ v = make64(htonl(low32(v)), htonl(high32(v)));
+#else /* NO_64BIT_MATH */
+ /* use the native 64-bit math */
+ v = (uint64_t)((be32_to_cpu((uint32_t)(v >> 32))) |
+ (((uint64_t)be32_to_cpu((uint32_t)v)) << 32));
+#endif /* NO_64BIT_MATH */
+ return v;
+}
+
+#endif /* WORDS_BIGENDIAN */
+
+#endif /* HAVE_CONFIG_H */
+
+/*
+ * functions manipulating bitvector_t
+ *
+ * A bitvector_t consists of an array of words and an integer
+ * representing the number of significant bits stored in the array.
+ * The bits are packed as follows: the least significant bit is that
+ * of word[0], while the most significant bit is the nth most
+ * significant bit of word[m], where length = bits_per_word * m + n.
+ *
+ */
+
+#define bits_per_word 32
+#define bytes_per_word 4
+
+typedef struct {
+ uint32_t length;
+ uint32_t *word;
+} bitvector_t;
+
+#define _bitvector_get_bit(v, bit_index) \
+ (((((v)->word[((bit_index) >> 5)]) >> ((bit_index)&31)) & 1))
+
+#define _bitvector_set_bit(v, bit_index) \
+ ((((v)->word[((bit_index) >> 5)] |= ((uint32_t)1 << ((bit_index)&31)))))
+
+#define _bitvector_clear_bit(v, bit_index) \
+ ((((v)->word[((bit_index) >> 5)] &= ~((uint32_t)1 << ((bit_index)&31)))))
+
+#define _bitvector_get_length(v) (((v)->length))
+
+#ifdef DATATYPES_USE_MACROS /* little functions are really macros */
+
+#define bitvector_get_bit(v, bit_index) _bitvector_get_bit(v, bit_index)
+#define bitvector_set_bit(v, bit_index) _bitvector_set_bit(v, bit_index)
+#define bitvector_clear_bit(v, bit_index) _bitvector_clear_bit(v, bit_index)
+#define bitvector_get_length(v) _bitvector_get_length(v)
+
+#else
+
+int bitvector_get_bit(const bitvector_t *v, int bit_index);
+
+void bitvector_set_bit(bitvector_t *v, int bit_index);
+
+void bitvector_clear_bit(bitvector_t *v, int bit_index);
+
+unsigned long bitvector_get_length(const bitvector_t *v);
+
+#endif
+
+int bitvector_alloc(bitvector_t *v, unsigned long length);
+
+void bitvector_dealloc(bitvector_t *v);
+
+void bitvector_set_to_zero(bitvector_t *x);
+
+void bitvector_left_shift(bitvector_t *x, int index);
+
+char *bitvector_bit_string(bitvector_t *x, char *buf, int len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DATATYPES_H */
diff --git a/netwerk/srtp/src/crypto/include/err.h b/netwerk/srtp/src/crypto/include/err.h
new file mode 100644
index 0000000000..66a1023ec8
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/err.h
@@ -0,0 +1,134 @@
+/*
+ * err.h
+ *
+ * error status codes
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 ERR_H
+#define ERR_H
+
+#include <stdio.h>
+#include <stdarg.h>
+#include "srtp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @defgroup Error Error Codes
+ *
+ * Error status codes are represented by the enumeration srtp_err_status_t.
+ *
+ * @{
+ */
+
+/**
+ * @}
+ */
+
+typedef enum {
+ srtp_err_level_error,
+ srtp_err_level_warning,
+ srtp_err_level_info,
+ srtp_err_level_debug
+} srtp_err_reporting_level_t;
+
+/*
+ * err_reporting_init prepares the error system. If
+ * ERR_REPORTING_STDOUT is defined, it will log to stdout.
+ *
+ */
+
+srtp_err_status_t srtp_err_reporting_init(void);
+
+typedef void(srtp_err_report_handler_func_t)(srtp_err_reporting_level_t level,
+ const char *msg);
+
+srtp_err_status_t srtp_install_err_report_handler(
+ srtp_err_report_handler_func_t func);
+
+/*
+ * srtp_err_report reports a 'printf' formatted error
+ * string, followed by a an arg list. The level argument
+ * is one of srtp_err_reporting_level_t.
+ *
+ * Errors will be reported to stdout, if ERR_REPORTING_STDOUT
+ * is defined.
+ *
+ */
+
+void srtp_err_report(srtp_err_reporting_level_t level, const char *format, ...);
+
+/*
+ * debug_module_t defines a debug module
+ */
+
+typedef struct {
+ int on; /* 1 if debugging is on, 0 if it is off */
+ const char *name; /* printable name for debug module */
+} srtp_debug_module_t;
+
+#ifdef ENABLE_DEBUG_LOGGING
+
+#define debug_print(mod, format, arg) \
+ srtp_err_report(srtp_err_level_debug, ("%s: " format "\n"), mod.name, arg)
+#define debug_print2(mod, format, arg1, arg2) \
+ srtp_err_report(srtp_err_level_debug, ("%s: " format "\n"), mod.name, \
+ arg1, arg2)
+
+#else
+
+#define debug_print(mod, format, arg) \
+ if (mod.on) \
+ srtp_err_report(srtp_err_level_debug, ("%s: " format "\n"), mod.name, arg)
+#define debug_print2(mod, format, arg1, arg2) \
+ if (mod.on) \
+ srtp_err_report(srtp_err_level_debug, ("%s: " format "\n"), mod.name, \
+ arg1, arg2)
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ERR_H */
diff --git a/netwerk/srtp/src/crypto/include/hmac.h b/netwerk/srtp/src/crypto/include/hmac.h
new file mode 100644
index 0000000000..148818122c
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/hmac.h
@@ -0,0 +1,58 @@
+/*
+ * hmac.h
+ *
+ * interface to hmac srtp_auth_type_t
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ */
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HMAC_H
+#define HMAC_H
+
+#include "auth.h"
+#include "sha1.h"
+
+typedef struct {
+ uint8_t opad[64];
+ srtp_sha1_ctx_t ctx;
+ srtp_sha1_ctx_t init_ctx;
+} srtp_hmac_ctx_t;
+
+#endif /* HMAC_H */
diff --git a/netwerk/srtp/src/crypto/include/integers.h b/netwerk/srtp/src/crypto/include/integers.h
new file mode 100644
index 0000000000..f2cd7c064d
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/integers.h
@@ -0,0 +1,146 @@
+/*
+ * integers.h
+ *
+ * defines integer types (or refers to their definitions)
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 INTEGERS_H
+#define INTEGERS_H
+
+/* use standard integer definitions, if they're available */
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_INT_TYPES_H
+#include <sys/int_types.h> /* this exists on Sun OS */
+#endif
+#ifdef HAVE_MACHINE_TYPES_H
+#include <machine/types.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Can we do 64 bit integers? */
+#if !defined(HAVE_UINT64_T)
+#if SIZEOF_UNSIGNED_LONG == 8
+typedef unsigned long uint64_t;
+#elif SIZEOF_UNSIGNED_LONG_LONG == 8
+typedef unsigned long long uint64_t;
+#else
+#define NO_64BIT_MATH 1
+#endif
+#endif
+
+/* Reasonable defaults for 32 bit machines - you may need to
+ * edit these definitions for your own machine. */
+#ifndef HAVE_UINT8_T
+typedef unsigned char uint8_t;
+#endif
+#ifndef HAVE_UINT16_T
+typedef unsigned short int uint16_t;
+#endif
+#ifndef HAVE_UINT32_T
+typedef unsigned int uint32_t;
+#endif
+#ifndef HAVE_INT32_T
+typedef int int32_t;
+#endif
+
+#if defined(NO_64BIT_MATH) && defined(HAVE_CONFIG_H)
+typedef double uint64_t;
+/* assert that sizeof(double) == 8 */
+extern uint64_t make64(uint32_t high, uint32_t low);
+extern uint32_t high32(uint64_t value);
+extern uint32_t low32(uint64_t value);
+#endif
+
+/* These macros are to load and store 32-bit values from un-aligned
+ addresses. This is required for processors that do not allow unaligned
+ loads. */
+#ifdef ALIGNMENT_32BIT_REQUIRED
+/* Note that if it's in a variable, you can memcpy it */
+#ifdef WORDS_BIGENDIAN
+#define PUT_32(addr, value) \
+ { \
+ ((unsigned char *)(addr))[0] = (value >> 24); \
+ ((unsigned char *)(addr))[1] = (value >> 16) & 0xff; \
+ ((unsigned char *)(addr))[2] = (value >> 8) & 0xff; \
+ ((unsigned char *)(addr))[3] = (value)&0xff; \
+ }
+#define GET_32(addr) \
+ ((((unsigned char *)(addr))[0] << 24) | \
+ (((unsigned char *)(addr))[1] << 16) | \
+ (((unsigned char *)(addr))[2] << 8) | (((unsigned char *)(addr))[3]))
+#else
+#define PUT_32(addr, value) \
+ { \
+ ((unsigned char *)(addr))[3] = (value >> 24); \
+ ((unsigned char *)(addr))[2] = (value >> 16) & 0xff; \
+ ((unsigned char *)(addr))[1] = (value >> 8) & 0xff; \
+ ((unsigned char *)(addr))[0] = (value)&0xff; \
+ }
+#define GET_32(addr) \
+ ((((unsigned char *)(addr))[3] << 24) | \
+ (((unsigned char *)(addr))[2] << 16) | \
+ (((unsigned char *)(addr))[1] << 8) | (((unsigned char *)(addr))[0]))
+#endif // WORDS_BIGENDIAN
+#else
+#define PUT_32(addr, value) *(((uint32_t *) (addr)) = (value)
+#define GET_32(addr) (*(((uint32_t *) (addr)))
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* INTEGERS_H */
diff --git a/netwerk/srtp/src/crypto/include/key.h b/netwerk/srtp/src/crypto/include/key.h
new file mode 100644
index 0000000000..3498114b05
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/key.h
@@ -0,0 +1,88 @@
+/*
+ * key.h
+ *
+ * key usage limits enforcement
+ *
+ * David A. Mcgrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 KEY_H
+#define KEY_H
+
+#include "rdbx.h" /* for srtp_xtd_seq_num_t */
+#include "err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct srtp_key_limit_ctx_t *srtp_key_limit_t;
+
+typedef enum {
+ srtp_key_event_normal,
+ srtp_key_event_soft_limit,
+ srtp_key_event_hard_limit
+} srtp_key_event_t;
+
+srtp_err_status_t srtp_key_limit_set(srtp_key_limit_t key,
+ const srtp_xtd_seq_num_t s);
+
+srtp_err_status_t srtp_key_limit_clone(srtp_key_limit_t original,
+ srtp_key_limit_t *new_key);
+
+srtp_err_status_t srtp_key_limit_check(const srtp_key_limit_t key);
+
+srtp_key_event_t srtp_key_limit_update(srtp_key_limit_t key);
+
+typedef enum {
+ srtp_key_state_normal,
+ srtp_key_state_past_soft_limit,
+ srtp_key_state_expired
+} srtp_key_state_t;
+
+typedef struct srtp_key_limit_ctx_t {
+ srtp_xtd_seq_num_t num_left;
+ srtp_key_state_t state;
+} srtp_key_limit_ctx_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* KEY_H */
diff --git a/netwerk/srtp/src/crypto/include/null_auth.h b/netwerk/srtp/src/crypto/include/null_auth.h
new file mode 100644
index 0000000000..490dd7bc0f
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/null_auth.h
@@ -0,0 +1,73 @@
+/*
+ * null-auth.h
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 NULL_AUTH_H
+#define NULL_AUTH_H
+
+#include "auth.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+ char foo;
+} srtp_null_auth_ctx_t;
+
+#if 0
+srtp_err_status_t srtp_null_auth_alloc(srtp_auth_t **a, int key_len, int out_len);
+
+srtp_err_status_t srtp_null_auth_dealloc(srtp_auth_t *a);
+
+srtp_err_status_t srtp_null_auth_init(srtp_null_auth_ctx_t *state, const uint8_t *key, int key_len);
+
+srtp_err_status_t srtp_null_auth_compute(srtp_null_auth_ctx_t *state, uint8_t *message, int msg_octets, int tag_len, uint8_t *result);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NULL_AUTH_H */
diff --git a/netwerk/srtp/src/crypto/include/null_cipher.h b/netwerk/srtp/src/crypto/include/null_cipher.h
new file mode 100644
index 0000000000..5e8c91c134
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/null_cipher.h
@@ -0,0 +1,57 @@
+/*
+ * null-cipher.h
+ *
+ * header file for the null cipher
+ *
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 NULL_CIPHER_H
+#define NULL_CIPHER_H
+
+#include "datatypes.h"
+#include "cipher.h"
+
+typedef struct {
+ char foo; /* empty, for now */
+} srtp_null_cipher_ctx_t;
+
+#endif /* NULL_CIPHER_H */
diff --git a/netwerk/srtp/src/crypto/include/rdb.h b/netwerk/srtp/src/crypto/include/rdb.h
new file mode 100644
index 0000000000..98314c1f3e
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/rdb.h
@@ -0,0 +1,125 @@
+/*
+ * replay-database.h
+ *
+ * interface for a replay database for packet security
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 REPLAY_DB_H
+#define REPLAY_DB_H
+
+#include "integers.h" /* for uint32_t */
+#include "datatypes.h" /* for v128_t */
+#include "err.h" /* for srtp_err_status_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * if the ith least significant bit is one, then the packet index
+ * window_end-i is in the database
+ */
+
+typedef struct {
+ uint32_t window_start; /* packet index of the first bit in bitmask */
+ v128_t bitmask;
+} srtp_rdb_t;
+
+#define rdb_bits_in_bitmask (8 * sizeof(v128_t))
+
+/*
+ * srtp_rdb_init
+ *
+ * initalizes rdb
+ *
+ * returns srtp_err_status_ok on success, srtp_err_status_t_fail otherwise
+ */
+srtp_err_status_t srtp_rdb_init(srtp_rdb_t *rdb);
+
+/*
+ * srtp_rdb_check
+ *
+ * checks to see if index appears in rdb
+ *
+ * returns srtp_err_status_fail if the index already appears in rdb,
+ * returns srtp_err_status_ok otherwise
+ */
+srtp_err_status_t srtp_rdb_check(const srtp_rdb_t *rdb, uint32_t rdb_index);
+
+/*
+ * srtp_rdb_add_index
+ *
+ * adds index to srtp_rdb_t (and does *not* check if index appears in db)
+ *
+ * returns srtp_err_status_ok on success, srtp_err_status_fail otherwise
+ *
+ */
+srtp_err_status_t srtp_rdb_add_index(srtp_rdb_t *rdb, uint32_t rdb_index);
+
+/*
+ * the functions srtp_rdb_increment() and srtp_rdb_get_value() are for use by
+ * senders, not receivers - DO NOT use these functions on the same
+ * srtp_rdb_t upon which srtp_rdb_add_index is used!
+ */
+
+/*
+ * srtp_rdb_increment(db) increments the sequence number in db, if it is
+ * not too high
+ *
+ * return values:
+ *
+ * srtp_err_status_ok no problem
+ * srtp_err_status_key_expired sequence number too high
+ *
+ */
+srtp_err_status_t srtp_rdb_increment(srtp_rdb_t *rdb);
+
+/*
+ * srtp_rdb_get_value(db) returns the current sequence number of db
+ */
+uint32_t srtp_rdb_get_value(const srtp_rdb_t *rdb);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* REPLAY_DB_H */
diff --git a/netwerk/srtp/src/crypto/include/rdbx.h b/netwerk/srtp/src/crypto/include/rdbx.h
new file mode 100644
index 0000000000..2194178ee2
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/rdbx.h
@@ -0,0 +1,209 @@
+/*
+ * rdbx.h
+ *
+ * replay database with extended packet indices, using a rollover counter
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 RDBX_H
+#define RDBX_H
+
+#include "datatypes.h"
+#include "err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* #define ROC_TEST */
+
+#ifndef ROC_TEST
+
+typedef uint16_t srtp_sequence_number_t; /* 16 bit sequence number */
+typedef uint32_t srtp_rollover_counter_t; /* 32 bit rollover counter */
+
+#else /* use small seq_num and roc datatypes for testing purposes */
+
+typedef unsigned char srtp_sequence_number_t; /* 8 bit sequence number */
+typedef uint16_t srtp_rollover_counter_t; /* 16 bit rollover counter */
+
+#endif
+
+#define seq_num_median (1 << (8 * sizeof(srtp_sequence_number_t) - 1))
+#define seq_num_max (1 << (8 * sizeof(srtp_sequence_number_t)))
+
+/*
+ * An rtp_xtd_seq_num_t is a 64-bit unsigned integer used as an 'extended'
+ * sequence number.
+ */
+typedef uint64_t srtp_xtd_seq_num_t;
+
+/*
+ * An srtp_rdbx_t is a replay database with extended range; it uses an
+ * xtd_seq_num_t and a bitmask of recently received indices.
+ */
+typedef struct {
+ srtp_xtd_seq_num_t index;
+ bitvector_t bitmask;
+} srtp_rdbx_t;
+
+/*
+ * srtp_rdbx_init(rdbx_ptr, ws)
+ *
+ * initializes the rdbx pointed to by its argument with the window size ws,
+ * setting the rollover counter and sequence number to zero
+ */
+srtp_err_status_t srtp_rdbx_init(srtp_rdbx_t *rdbx, unsigned long ws);
+
+/*
+ * srtp_rdbx_dealloc(rdbx_ptr)
+ *
+ * frees memory associated with the rdbx
+ */
+srtp_err_status_t srtp_rdbx_dealloc(srtp_rdbx_t *rdbx);
+
+/*
+ * srtp_rdbx_estimate_index(rdbx, guess, s)
+ *
+ * given an rdbx and a sequence number s (from a newly arrived packet),
+ * sets the contents of *guess to contain the best guess of the packet
+ * index to which s corresponds, and returns the difference between
+ * *guess and the locally stored synch info
+ */
+int32_t srtp_rdbx_estimate_index(const srtp_rdbx_t *rdbx,
+ srtp_xtd_seq_num_t *guess,
+ srtp_sequence_number_t s);
+
+/*
+ * srtp_rdbx_check(rdbx, delta);
+ *
+ * srtp_rdbx_check(&r, delta) checks to see if the xtd_seq_num_t
+ * which is at rdbx->window_start + delta is in the rdb
+ *
+ */
+srtp_err_status_t srtp_rdbx_check(const srtp_rdbx_t *rdbx, int difference);
+
+/*
+ * srtp_replay_add_index(rdbx, delta)
+ *
+ * adds the srtp_xtd_seq_num_t at rdbx->window_start + delta to replay_db
+ * (and does *not* check if that xtd_seq_num_t appears in db)
+ *
+ * this function should be called *only* after replay_check has
+ * indicated that the index does not appear in the rdbx, and a mutex
+ * should protect the rdbx between these calls if necessary.
+ */
+srtp_err_status_t srtp_rdbx_add_index(srtp_rdbx_t *rdbx, int delta);
+
+/*
+ * srtp_rdbx_set_roc(rdbx, roc) initalizes the srtp_rdbx_t at the location rdbx
+ * to have the rollover counter value roc. If that value is less than
+ * the current rollover counter value, then the function returns
+ * srtp_err_status_replay_old; otherwise, srtp_err_status_ok is returned.
+ *
+ */
+srtp_err_status_t srtp_rdbx_set_roc(srtp_rdbx_t *rdbx, uint32_t roc);
+
+/*
+ * srtp_rdbx_get_packet_index(rdbx) returns the value of the rollover counter
+ * for
+ * the srtp_rdbx_t pointed to by rdbx
+ *
+ */
+srtp_xtd_seq_num_t srtp_rdbx_get_packet_index(const srtp_rdbx_t *rdbx);
+
+/*
+ * srtp_xtd_seq_num_t functions - these are *internal* functions of rdbx, and
+ * shouldn't be used to manipulate rdbx internal values. use the rdbx
+ * api instead!
+ */
+
+/*
+ * srtp_rdbx_get_ws(rdbx_ptr)
+ *
+ * gets the window size which was used to initialize the rdbx
+ */
+unsigned long srtp_rdbx_get_window_size(const srtp_rdbx_t *rdbx);
+
+/* index_init(&pi) initializes a packet index pi (sets it to zero) */
+void srtp_index_init(srtp_xtd_seq_num_t *pi);
+
+/* index_advance(&pi, s) advances a xtd_seq_num_t forward by s */
+void srtp_index_advance(srtp_xtd_seq_num_t *pi, srtp_sequence_number_t s);
+
+/*
+ * srtp_index_guess(local, guess, s)
+ *
+ * given a srtp_xtd_seq_num_t local (which represents the highest
+ * known-to-be-good index) and a sequence number s (from a newly
+ * arrived packet), sets the contents of *guess to contain the best
+ * guess of the packet index to which s corresponds, and returns the
+ * difference between *guess and *local
+ */
+int32_t srtp_index_guess(const srtp_xtd_seq_num_t *local,
+ srtp_xtd_seq_num_t *guess,
+ srtp_sequence_number_t s);
+
+/*
+ * srtp_rdbx_get_roc(rdbx)
+ *
+ * Get the current rollover counter
+ *
+ */
+uint32_t srtp_rdbx_get_roc(const srtp_rdbx_t *rdbx);
+
+/*
+ * srtp_rdbx_set_roc_seq(rdbx, roc, seq) initalizes the srtp_rdbx_t at the
+ * location rdbx to have the rollover counter value roc and packet sequence
+ * number seq. If the new rollover counter value is less than the current
+ * rollover counter value, then the function returns
+ * srtp_err_status_replay_old, otherwise, srtp_err_status_ok is returned.
+ */
+srtp_err_status_t srtp_rdbx_set_roc_seq(srtp_rdbx_t *rdbx,
+ uint32_t roc,
+ uint16_t seq);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* RDBX_H */
diff --git a/netwerk/srtp/src/crypto/include/sha1.h b/netwerk/srtp/src/crypto/include/sha1.h
new file mode 100644
index 0000000000..933c1466a3
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/sha1.h
@@ -0,0 +1,184 @@
+/*
+ * sha1.h
+ *
+ * interface to the Secure Hash Algorithm v.1 (SHA-1), specified in
+ * FIPS 180-1
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 SHA1_H
+#define SHA1_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "err.h"
+#ifdef OPENSSL
+#include <openssl/evp.h>
+#include <stdint.h>
+#else
+#include "datatypes.h"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef OPENSSL
+
+/*
+ * srtp_sha1_init(&ctx) initializes the SHA1 context ctx
+ *
+ * srtp_sha1_update(&ctx, msg, len) hashes the len octets starting at msg
+ * into the SHA1 context
+ *
+ * srtp_sha1_final(&ctx, output) performs the final processing of the SHA1
+ * context and writes the result to the 20 octets at output
+ *
+ * Return values are ignored on the EVP functions since all three
+ * of these functions return void.
+ *
+ */
+
+/* OpenSSL 1.1.0 made EVP_MD_CTX an opaque structure, which must be allocated
+ using EVP_MD_CTX_new. But this function doesn't exist in OpenSSL 1.0.x. */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || LIBRESSL_VERSION_NUMBER
+
+typedef EVP_MD_CTX srtp_sha1_ctx_t;
+
+static inline void srtp_sha1_init(srtp_sha1_ctx_t *ctx)
+{
+ EVP_MD_CTX_init(ctx);
+ EVP_DigestInit(ctx, EVP_sha1());
+}
+
+static inline void srtp_sha1_update(srtp_sha1_ctx_t *ctx,
+ const uint8_t *M,
+ int octets_in_msg)
+{
+ EVP_DigestUpdate(ctx, M, octets_in_msg);
+}
+
+static inline void srtp_sha1_final(srtp_sha1_ctx_t *ctx, uint32_t *output)
+{
+ unsigned int len = 0;
+
+ EVP_DigestFinal(ctx, (unsigned char *)output, &len);
+ EVP_MD_CTX_cleanup(ctx);
+}
+
+#else
+
+typedef EVP_MD_CTX *srtp_sha1_ctx_t;
+
+static inline void srtp_sha1_init(srtp_sha1_ctx_t *ctx)
+{
+ *ctx = EVP_MD_CTX_new();
+ EVP_DigestInit(*ctx, EVP_sha1());
+}
+
+static inline void srtp_sha1_update(srtp_sha1_ctx_t *ctx,
+ const uint8_t *M,
+ int octets_in_msg)
+{
+ EVP_DigestUpdate(*ctx, M, octets_in_msg);
+}
+
+static inline void srtp_sha1_final(srtp_sha1_ctx_t *ctx, uint32_t *output)
+{
+ unsigned int len = 0;
+
+ EVP_DigestFinal(*ctx, (unsigned char *)output, &len);
+ EVP_MD_CTX_free(*ctx);
+}
+#endif
+
+#else
+
+typedef struct {
+ uint32_t H[5]; /* state vector */
+ uint32_t M[16]; /* message buffer */
+ int octets_in_buffer; /* octets of message in buffer */
+ uint32_t num_bits_in_msg; /* total number of bits in message */
+} srtp_sha1_ctx_t;
+
+/*
+ * srtp_sha1_init(&ctx) initializes the SHA1 context ctx
+ *
+ * srtp_sha1_update(&ctx, msg, len) hashes the len octets starting at msg
+ * into the SHA1 context
+ *
+ * srtp_sha1_final(&ctx, output) performs the final processing of the SHA1
+ * context and writes the result to the 20 octets at output
+ *
+ */
+void srtp_sha1_init(srtp_sha1_ctx_t *ctx);
+
+void srtp_sha1_update(srtp_sha1_ctx_t *ctx,
+ const uint8_t *M,
+ int octets_in_msg);
+
+void srtp_sha1_final(srtp_sha1_ctx_t *ctx, uint32_t output[5]);
+
+/*
+ * The srtp_sha1_core function is INTERNAL to SHA-1, but it is declared
+ * here because it is also used by the cipher SEAL 3.0 in its key
+ * setup algorithm.
+ */
+
+/*
+ * srtp_sha1_core(M, H) computes the core sha1 compression function, where M is
+ * the next part of the message and H is the intermediate state {H0,
+ * H1, ...}
+ *
+ * this function does not do any of the padding required in the
+ * complete sha1 function
+ */
+void srtp_sha1_core(const uint32_t M[16], uint32_t hash_value[5]);
+
+#endif /* else OPENSSL */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SHA1_H */
diff --git a/netwerk/srtp/src/crypto/include/stat.h b/netwerk/srtp/src/crypto/include/stat.h
new file mode 100644
index 0000000000..1894e041aa
--- /dev/null
+++ b/netwerk/srtp/src/crypto/include/stat.h
@@ -0,0 +1,66 @@
+/*
+ * stats.h
+ *
+ * interface to statistical test functions
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright(c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 STAT_H
+#define STAT_H
+
+#include "datatypes.h" /* for uint8_t */
+#include "err.h" /* for srtp_err_status_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+srtp_err_status_t stat_test_monobit(uint8_t *data);
+
+srtp_err_status_t stat_test_poker(uint8_t *data);
+
+srtp_err_status_t stat_test_runs(uint8_t *data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* STAT_H */
diff --git a/netwerk/srtp/src/crypto/kernel/alloc.c b/netwerk/srtp/src/crypto/kernel/alloc.c
new file mode 100644
index 0000000000..dbe58266f4
--- /dev/null
+++ b/netwerk/srtp/src/crypto/kernel/alloc.c
@@ -0,0 +1,101 @@
+/*
+ * alloc.c
+ *
+ * memory allocation and deallocation
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "alloc.h"
+#include "crypto_kernel.h"
+
+/* the debug module for memory allocation */
+
+srtp_debug_module_t srtp_mod_alloc = {
+ 0, /* debugging is off by default */
+ "alloc" /* printable name for module */
+};
+
+/*
+ * Nota bene: the debugging statements for srtp_crypto_alloc() and
+ * srtp_crypto_free() have identical prefixes, which include the addresses
+ * of the memory locations on which they are operating. This fact can
+ * be used to locate memory leaks, by turning on memory debugging,
+ * grepping for 'alloc', then matching alloc and free calls by
+ * address.
+ */
+
+#if defined(HAVE_STDLIB_H)
+
+void *srtp_crypto_alloc(size_t size)
+{
+ void *ptr;
+
+ if (!size) {
+ return NULL;
+ }
+
+ ptr = calloc(1, size);
+
+ if (ptr) {
+ debug_print(srtp_mod_alloc, "(location: %p) allocated", ptr);
+ } else {
+ debug_print(srtp_mod_alloc, "allocation failed (asked for %d bytes)\n",
+ size);
+ }
+
+ return ptr;
+}
+
+void srtp_crypto_free(void *ptr)
+{
+ debug_print(srtp_mod_alloc, "(location: %p) freed", ptr);
+
+ free(ptr);
+}
+
+#else /* we need to define our own memory allocation routines */
+
+#error no memory allocation defined yet
+
+#endif
diff --git a/netwerk/srtp/src/crypto/kernel/crypto_kernel.c b/netwerk/srtp/src/crypto/kernel/crypto_kernel.c
new file mode 100644
index 0000000000..df6af7da88
--- /dev/null
+++ b/netwerk/srtp/src/crypto/kernel/crypto_kernel.c
@@ -0,0 +1,561 @@
+/*
+ * crypto_kernel.c
+ *
+ * header for the cryptographic kernel
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "alloc.h"
+
+#include "crypto_kernel.h"
+#include "cipher_types.h"
+
+/* the debug module for the crypto_kernel */
+
+srtp_debug_module_t srtp_mod_crypto_kernel = {
+ 0, /* debugging is off by default */
+ "crypto kernel" /* printable name for module */
+};
+
+/* crypto_kernel is a global variable, the only one of its datatype */
+
+srtp_crypto_kernel_t crypto_kernel = {
+ srtp_crypto_kernel_state_insecure, /* start off in insecure state */
+ NULL, /* no cipher types yet */
+ NULL, /* no auth types yet */
+ NULL /* no debug modules yet */
+};
+
+#define MAX_RNG_TRIALS 25
+
+srtp_err_status_t srtp_crypto_kernel_init()
+{
+ srtp_err_status_t status;
+
+ /* check the security state */
+ if (crypto_kernel.state == srtp_crypto_kernel_state_secure) {
+ /*
+ * we're already in the secure state, but we've been asked to
+ * re-initialize, so we just re-run the self-tests and then return
+ */
+ return srtp_crypto_kernel_status();
+ }
+
+ /* initialize error reporting system */
+ status = srtp_err_reporting_init();
+ if (status) {
+ return status;
+ }
+
+ /* load debug modules */
+ status = srtp_crypto_kernel_load_debug_module(&srtp_mod_crypto_kernel);
+ if (status) {
+ return status;
+ }
+ status = srtp_crypto_kernel_load_debug_module(&srtp_mod_auth);
+ if (status) {
+ return status;
+ }
+ status = srtp_crypto_kernel_load_debug_module(&srtp_mod_cipher);
+ if (status) {
+ return status;
+ }
+ status = srtp_crypto_kernel_load_debug_module(&srtp_mod_stat);
+ if (status) {
+ return status;
+ }
+ status = srtp_crypto_kernel_load_debug_module(&srtp_mod_alloc);
+ if (status) {
+ return status;
+ }
+
+ /* load cipher types */
+ status = srtp_crypto_kernel_load_cipher_type(&srtp_null_cipher,
+ SRTP_NULL_CIPHER);
+ if (status) {
+ return status;
+ }
+ status = srtp_crypto_kernel_load_cipher_type(&srtp_aes_icm_128,
+ SRTP_AES_ICM_128);
+ if (status) {
+ return status;
+ }
+ status = srtp_crypto_kernel_load_cipher_type(&srtp_aes_icm_256,
+ SRTP_AES_ICM_256);
+ if (status) {
+ return status;
+ }
+ status = srtp_crypto_kernel_load_debug_module(&srtp_mod_aes_icm);
+ if (status) {
+ return status;
+ }
+#ifdef GCM
+ status = srtp_crypto_kernel_load_cipher_type(&srtp_aes_icm_192,
+ SRTP_AES_ICM_192);
+ if (status) {
+ return status;
+ }
+ status = srtp_crypto_kernel_load_cipher_type(&srtp_aes_gcm_128,
+ SRTP_AES_GCM_128);
+ if (status) {
+ return status;
+ }
+ status = srtp_crypto_kernel_load_cipher_type(&srtp_aes_gcm_256,
+ SRTP_AES_GCM_256);
+ if (status) {
+ return status;
+ }
+ status = srtp_crypto_kernel_load_debug_module(&srtp_mod_aes_gcm);
+ if (status) {
+ return status;
+ }
+#endif
+
+ /* load auth func types */
+ status = srtp_crypto_kernel_load_auth_type(&srtp_null_auth, SRTP_NULL_AUTH);
+ if (status) {
+ return status;
+ }
+ status = srtp_crypto_kernel_load_auth_type(&srtp_hmac, SRTP_HMAC_SHA1);
+ if (status) {
+ return status;
+ }
+ status = srtp_crypto_kernel_load_debug_module(&srtp_mod_hmac);
+ if (status) {
+ return status;
+ }
+
+ /* change state to secure */
+ crypto_kernel.state = srtp_crypto_kernel_state_secure;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_crypto_kernel_status()
+{
+ srtp_err_status_t status;
+ srtp_kernel_cipher_type_t *ctype = crypto_kernel.cipher_type_list;
+ srtp_kernel_auth_type_t *atype = crypto_kernel.auth_type_list;
+
+ /* for each cipher type, describe and test */
+ while (ctype != NULL) {
+ srtp_err_report(srtp_err_level_info, "cipher: %s\n",
+ ctype->cipher_type->description);
+ srtp_err_report(srtp_err_level_info, " self-test: ");
+ status = srtp_cipher_type_self_test(ctype->cipher_type);
+ if (status) {
+ srtp_err_report(srtp_err_level_error, "failed with error code %d\n",
+ status);
+ exit(status);
+ }
+ srtp_err_report(srtp_err_level_info, "passed\n");
+ ctype = ctype->next;
+ }
+
+ /* for each auth type, describe and test */
+ while (atype != NULL) {
+ srtp_err_report(srtp_err_level_info, "auth func: %s\n",
+ atype->auth_type->description);
+ srtp_err_report(srtp_err_level_info, " self-test: ");
+ status = srtp_auth_type_self_test(atype->auth_type);
+ if (status) {
+ srtp_err_report(srtp_err_level_error, "failed with error code %d\n",
+ status);
+ exit(status);
+ }
+ srtp_err_report(srtp_err_level_info, "passed\n");
+ atype = atype->next;
+ }
+
+ srtp_crypto_kernel_list_debug_modules();
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_crypto_kernel_list_debug_modules()
+{
+ srtp_kernel_debug_module_t *dm = crypto_kernel.debug_module_list;
+
+ /* describe each debug module */
+ srtp_err_report(srtp_err_level_info, "debug modules loaded:\n");
+ while (dm != NULL) {
+ srtp_err_report(srtp_err_level_info, " %s ", dm->mod->name);
+ if (dm->mod->on) {
+ srtp_err_report(srtp_err_level_info, "(on)\n");
+ } else {
+ srtp_err_report(srtp_err_level_info, "(off)\n");
+ }
+ dm = dm->next;
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_crypto_kernel_shutdown()
+{
+ /*
+ * free dynamic memory used in crypto_kernel at present
+ */
+
+ /* walk down cipher type list, freeing memory */
+ while (crypto_kernel.cipher_type_list != NULL) {
+ srtp_kernel_cipher_type_t *ctype = crypto_kernel.cipher_type_list;
+ crypto_kernel.cipher_type_list = ctype->next;
+ debug_print(srtp_mod_crypto_kernel, "freeing memory for cipher %s",
+ ctype->cipher_type->description);
+ srtp_crypto_free(ctype);
+ }
+
+ /* walk down authetication module list, freeing memory */
+ while (crypto_kernel.auth_type_list != NULL) {
+ srtp_kernel_auth_type_t *atype = crypto_kernel.auth_type_list;
+ crypto_kernel.auth_type_list = atype->next;
+ debug_print(srtp_mod_crypto_kernel,
+ "freeing memory for authentication %s",
+ atype->auth_type->description);
+ srtp_crypto_free(atype);
+ }
+
+ /* walk down debug module list, freeing memory */
+ while (crypto_kernel.debug_module_list != NULL) {
+ srtp_kernel_debug_module_t *kdm = crypto_kernel.debug_module_list;
+ crypto_kernel.debug_module_list = kdm->next;
+ debug_print(srtp_mod_crypto_kernel,
+ "freeing memory for debug module %s", kdm->mod->name);
+ srtp_crypto_free(kdm);
+ }
+
+ /* return to insecure state */
+ crypto_kernel.state = srtp_crypto_kernel_state_insecure;
+
+ return srtp_err_status_ok;
+}
+
+static inline srtp_err_status_t srtp_crypto_kernel_do_load_cipher_type(
+ const srtp_cipher_type_t *new_ct,
+ srtp_cipher_type_id_t id,
+ int replace)
+{
+ srtp_kernel_cipher_type_t *ctype, *new_ctype;
+ srtp_err_status_t status;
+
+ /* defensive coding */
+ if (new_ct == NULL) {
+ return srtp_err_status_bad_param;
+ }
+
+ if (new_ct->id != id) {
+ return srtp_err_status_bad_param;
+ }
+
+ /* check cipher type by running self-test */
+ status = srtp_cipher_type_self_test(new_ct);
+ if (status) {
+ return status;
+ }
+
+ /* walk down list, checking if this type is in the list already */
+ ctype = crypto_kernel.cipher_type_list;
+ while (ctype != NULL) {
+ if (id == ctype->id) {
+ if (!replace) {
+ return srtp_err_status_bad_param;
+ }
+ status =
+ srtp_cipher_type_test(new_ct, ctype->cipher_type->test_data);
+ if (status) {
+ return status;
+ }
+ new_ctype = ctype;
+ break;
+ } else if (new_ct == ctype->cipher_type) {
+ return srtp_err_status_bad_param;
+ }
+ ctype = ctype->next;
+ }
+
+ /* if not found, put new_ct at the head of the list */
+ if (ctype == NULL) {
+ /* allocate memory */
+ new_ctype = (srtp_kernel_cipher_type_t *)srtp_crypto_alloc(
+ sizeof(srtp_kernel_cipher_type_t));
+ if (new_ctype == NULL) {
+ return srtp_err_status_alloc_fail;
+ }
+ new_ctype->next = crypto_kernel.cipher_type_list;
+
+ /* set head of list to new cipher type */
+ crypto_kernel.cipher_type_list = new_ctype;
+ }
+
+ /* set fields */
+ new_ctype->cipher_type = new_ct;
+ new_ctype->id = id;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_crypto_kernel_load_cipher_type(
+ const srtp_cipher_type_t *new_ct,
+ srtp_cipher_type_id_t id)
+{
+ return srtp_crypto_kernel_do_load_cipher_type(new_ct, id, 0);
+}
+
+srtp_err_status_t srtp_replace_cipher_type(const srtp_cipher_type_t *new_ct,
+ srtp_cipher_type_id_t id)
+{
+ return srtp_crypto_kernel_do_load_cipher_type(new_ct, id, 1);
+}
+
+srtp_err_status_t srtp_crypto_kernel_do_load_auth_type(
+ const srtp_auth_type_t *new_at,
+ srtp_auth_type_id_t id,
+ int replace)
+{
+ srtp_kernel_auth_type_t *atype, *new_atype;
+ srtp_err_status_t status;
+
+ /* defensive coding */
+ if (new_at == NULL) {
+ return srtp_err_status_bad_param;
+ }
+
+ if (new_at->id != id) {
+ return srtp_err_status_bad_param;
+ }
+
+ /* check auth type by running self-test */
+ status = srtp_auth_type_self_test(new_at);
+ if (status) {
+ return status;
+ }
+
+ /* walk down list, checking if this type is in the list already */
+ atype = crypto_kernel.auth_type_list;
+ while (atype != NULL) {
+ if (id == atype->id) {
+ if (!replace) {
+ return srtp_err_status_bad_param;
+ }
+ status = srtp_auth_type_test(new_at, atype->auth_type->test_data);
+ if (status) {
+ return status;
+ }
+ new_atype = atype;
+ break;
+ } else if (new_at == atype->auth_type) {
+ return srtp_err_status_bad_param;
+ }
+ atype = atype->next;
+ }
+
+ /* if not found, put new_at at the head of the list */
+ if (atype == NULL) {
+ /* allocate memory */
+ new_atype = (srtp_kernel_auth_type_t *)srtp_crypto_alloc(
+ sizeof(srtp_kernel_auth_type_t));
+ if (new_atype == NULL) {
+ return srtp_err_status_alloc_fail;
+ }
+
+ new_atype->next = crypto_kernel.auth_type_list;
+ /* set head of list to new auth type */
+ crypto_kernel.auth_type_list = new_atype;
+ }
+
+ /* set fields */
+ new_atype->auth_type = new_at;
+ new_atype->id = id;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_crypto_kernel_load_auth_type(
+ const srtp_auth_type_t *new_at,
+ srtp_auth_type_id_t id)
+{
+ return srtp_crypto_kernel_do_load_auth_type(new_at, id, 0);
+}
+
+srtp_err_status_t srtp_replace_auth_type(const srtp_auth_type_t *new_at,
+ srtp_auth_type_id_t id)
+{
+ return srtp_crypto_kernel_do_load_auth_type(new_at, id, 1);
+}
+
+const srtp_cipher_type_t *srtp_crypto_kernel_get_cipher_type(
+ srtp_cipher_type_id_t id)
+{
+ srtp_kernel_cipher_type_t *ctype;
+
+ /* walk down list, looking for id */
+ ctype = crypto_kernel.cipher_type_list;
+ while (ctype != NULL) {
+ if (id == ctype->id) {
+ return ctype->cipher_type;
+ }
+ ctype = ctype->next;
+ }
+
+ /* haven't found the right one, indicate failure by returning NULL */
+ return NULL;
+}
+
+srtp_err_status_t srtp_crypto_kernel_alloc_cipher(srtp_cipher_type_id_t id,
+ srtp_cipher_pointer_t *cp,
+ int key_len,
+ int tag_len)
+{
+ const srtp_cipher_type_t *ct;
+
+ /*
+ * if the crypto_kernel is not yet initialized, we refuse to allocate
+ * any ciphers - this is a bit extra-paranoid
+ */
+ if (crypto_kernel.state != srtp_crypto_kernel_state_secure) {
+ return srtp_err_status_init_fail;
+ }
+
+ ct = srtp_crypto_kernel_get_cipher_type(id);
+ if (!ct) {
+ return srtp_err_status_fail;
+ }
+
+ return ((ct)->alloc(cp, key_len, tag_len));
+}
+
+const srtp_auth_type_t *srtp_crypto_kernel_get_auth_type(srtp_auth_type_id_t id)
+{
+ srtp_kernel_auth_type_t *atype;
+
+ /* walk down list, looking for id */
+ atype = crypto_kernel.auth_type_list;
+ while (atype != NULL) {
+ if (id == atype->id) {
+ return atype->auth_type;
+ }
+ atype = atype->next;
+ }
+
+ /* haven't found the right one, indicate failure by returning NULL */
+ return NULL;
+}
+
+srtp_err_status_t srtp_crypto_kernel_alloc_auth(srtp_auth_type_id_t id,
+ srtp_auth_pointer_t *ap,
+ int key_len,
+ int tag_len)
+{
+ const srtp_auth_type_t *at;
+
+ /*
+ * if the crypto_kernel is not yet initialized, we refuse to allocate
+ * any auth functions - this is a bit extra-paranoid
+ */
+ if (crypto_kernel.state != srtp_crypto_kernel_state_secure) {
+ return srtp_err_status_init_fail;
+ }
+
+ at = srtp_crypto_kernel_get_auth_type(id);
+ if (!at) {
+ return srtp_err_status_fail;
+ }
+
+ return ((at)->alloc(ap, key_len, tag_len));
+}
+
+srtp_err_status_t srtp_crypto_kernel_load_debug_module(
+ srtp_debug_module_t *new_dm)
+{
+ srtp_kernel_debug_module_t *kdm, *new;
+
+ /* defensive coding */
+ if (new_dm == NULL || new_dm->name == NULL) {
+ return srtp_err_status_bad_param;
+ }
+
+ /* walk down list, checking if this type is in the list already */
+ kdm = crypto_kernel.debug_module_list;
+ while (kdm != NULL) {
+ if (strncmp(new_dm->name, kdm->mod->name, 64) == 0) {
+ return srtp_err_status_bad_param;
+ }
+ kdm = kdm->next;
+ }
+
+ /* put new_dm at the head of the list */
+ /* allocate memory */
+ new = (srtp_kernel_debug_module_t *)srtp_crypto_alloc(
+ sizeof(srtp_kernel_debug_module_t));
+ if (new == NULL) {
+ return srtp_err_status_alloc_fail;
+ }
+
+ /* set fields */
+ new->mod = new_dm;
+ new->next = crypto_kernel.debug_module_list;
+
+ /* set head of list to new cipher type */
+ crypto_kernel.debug_module_list = new;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_crypto_kernel_set_debug_module(const char *name, int on)
+{
+ srtp_kernel_debug_module_t *kdm;
+
+ /* walk down list, checking if this type is in the list already */
+ kdm = crypto_kernel.debug_module_list;
+ while (kdm != NULL) {
+ if (strncmp(name, kdm->mod->name, 64) == 0) {
+ kdm->mod->on = on;
+ return srtp_err_status_ok;
+ }
+ kdm = kdm->next;
+ }
+
+ return srtp_err_status_fail;
+}
diff --git a/netwerk/srtp/src/crypto/kernel/err.c b/netwerk/srtp/src/crypto/kernel/err.c
new file mode 100644
index 0000000000..f443549e5a
--- /dev/null
+++ b/netwerk/srtp/src/crypto/kernel/err.c
@@ -0,0 +1,108 @@
+/*
+ * err.c
+ *
+ * error status reporting functions
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "err.h"
+#include "datatypes.h"
+#include <string.h>
+
+/* srtp_err_file is the FILE to which errors are reported */
+
+static FILE *srtp_err_file = NULL;
+
+srtp_err_status_t srtp_err_reporting_init()
+{
+#ifdef ERR_REPORTING_STDOUT
+ srtp_err_file = stdout;
+#elif defined(ERR_REPORTING_FILE)
+ /* open file for error reporting */
+ srtp_err_file = fopen(ERR_REPORTING_FILE, "w");
+ if (srtp_err_file == NULL) {
+ return srtp_err_status_init_fail;
+ }
+#endif
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_report_handler_func_t *srtp_err_report_handler = NULL;
+
+srtp_err_status_t srtp_install_err_report_handler(
+ srtp_err_report_handler_func_t func)
+{
+ srtp_err_report_handler = func;
+ return srtp_err_status_ok;
+}
+
+void srtp_err_report(srtp_err_reporting_level_t level, const char *format, ...)
+{
+ va_list args;
+ if (srtp_err_file != NULL) {
+ va_start(args, format);
+ vfprintf(srtp_err_file, format, args);
+ va_end(args);
+ }
+ if (srtp_err_report_handler != NULL) {
+ va_start(args, format);
+ char msg[512];
+ if (vsnprintf(msg, sizeof(msg), format, args) > 0) {
+ /* strip trailing \n, callback should not have one */
+ size_t l = strlen(msg);
+ if (l && msg[l - 1] == '\n') {
+ msg[l - 1] = '\0';
+ }
+ srtp_err_report_handler(level, msg);
+ /*
+ * NOTE, need to be carefull, there is a potential that
+ * octet_string_set_to_zero() could
+ * call srtp_err_report() in the future, leading to recursion
+ */
+ octet_string_set_to_zero(msg, sizeof(msg));
+ }
+ va_end(args);
+ }
+}
diff --git a/netwerk/srtp/src/crypto/kernel/key.c b/netwerk/srtp/src/crypto/kernel/key.c
new file mode 100644
index 0000000000..0466195046
--- /dev/null
+++ b/netwerk/srtp/src/crypto/kernel/key.c
@@ -0,0 +1,122 @@
+/*
+ * key.c
+ *
+ * key usage limits enforcement
+ *
+ * David A. Mcgrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "key.h"
+
+#define soft_limit 0x10000
+
+srtp_err_status_t srtp_key_limit_set(srtp_key_limit_t key,
+ const srtp_xtd_seq_num_t s)
+{
+#ifdef NO_64BIT_MATH
+ if (high32(s) == 0 && low32(s) < soft_limit) {
+ return srtp_err_status_bad_param;
+ }
+#else
+ if (s < soft_limit) {
+ return srtp_err_status_bad_param;
+ }
+#endif
+ key->num_left = s;
+ key->state = srtp_key_state_normal;
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_key_limit_clone(srtp_key_limit_t original,
+ srtp_key_limit_t *new_key)
+{
+ if (original == NULL) {
+ return srtp_err_status_bad_param;
+ }
+ *new_key = original;
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_key_limit_check(const srtp_key_limit_t key)
+{
+ if (key->state == srtp_key_state_expired) {
+ return srtp_err_status_key_expired;
+ }
+ return srtp_err_status_ok;
+}
+
+srtp_key_event_t srtp_key_limit_update(srtp_key_limit_t key)
+{
+#ifdef NO_64BIT_MATH
+ if (low32(key->num_left) == 0) {
+ // carry
+ key->num_left =
+ make64(high32(key->num_left) - 1, low32(key->num_left) - 1);
+ } else {
+ // no carry
+ key->num_left = make64(high32(key->num_left), low32(key->num_left) - 1);
+ }
+ if (high32(key->num_left) != 0 || low32(key->num_left) >= soft_limit) {
+ return srtp_key_event_normal; /* we're above the soft limit */
+ }
+#else
+ key->num_left--;
+ if (key->num_left >= soft_limit) {
+ return srtp_key_event_normal; /* we're above the soft limit */
+ }
+#endif
+ if (key->state == srtp_key_state_normal) {
+ /* we just passed the soft limit, so change the state */
+ key->state = srtp_key_state_past_soft_limit;
+ }
+#ifdef NO_64BIT_MATH
+ if (low32(key->num_left) == 0 && high32(key->num_left == 0))
+#else
+ if (key->num_left < 1)
+#endif
+ { /* we just hit the hard limit */
+ key->state = srtp_key_state_expired;
+ return srtp_key_event_hard_limit;
+ }
+ return srtp_key_event_soft_limit;
+}
diff --git a/netwerk/srtp/src/crypto/math/datatypes.c b/netwerk/srtp/src/crypto/math/datatypes.c
new file mode 100644
index 0000000000..85c1cbf292
--- /dev/null
+++ b/netwerk/srtp/src/crypto/math/datatypes.c
@@ -0,0 +1,490 @@
+/*
+ * datatypes.c
+ *
+ * data types for finite fields and functions for input, output, and
+ * manipulation
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef OPENSSL
+#include <openssl/crypto.h>
+#endif
+
+#include "datatypes.h"
+
+static const int8_t octet_weight[256] = {
+ 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4,
+ 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+ 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4,
+ 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+ 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6,
+ 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+ 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5,
+ 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+ 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6,
+ 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+ 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
+};
+
+int octet_get_weight(uint8_t octet)
+{
+ return (int)octet_weight[octet];
+}
+
+/*
+ * bit_string is a buffer that is used to hold output strings, e.g.
+ * for printing.
+ */
+
+/* the value MAX_PRINT_STRING_LEN is defined in datatypes.h */
+
+char bit_string[MAX_PRINT_STRING_LEN];
+
+uint8_t srtp_nibble_to_hex_char(uint8_t nibble)
+{
+ char buf[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+ return buf[nibble & 0xF];
+}
+
+char *srtp_octet_string_hex_string(const void *s, int length)
+{
+ const uint8_t *str = (const uint8_t *)s;
+ int i;
+
+ /* double length, since one octet takes two hex characters */
+ length *= 2;
+
+ /* truncate string if it would be too long */
+ if (length > MAX_PRINT_STRING_LEN)
+ length = MAX_PRINT_STRING_LEN - 2;
+
+ for (i = 0; i < length; i += 2) {
+ bit_string[i] = srtp_nibble_to_hex_char(*str >> 4);
+ bit_string[i + 1] = srtp_nibble_to_hex_char(*str++ & 0xF);
+ }
+ bit_string[i] = 0; /* null terminate string */
+ return bit_string;
+}
+
+char *v128_hex_string(v128_t *x)
+{
+ int i, j;
+
+ for (i = j = 0; i < 16; i++) {
+ bit_string[j++] = srtp_nibble_to_hex_char(x->v8[i] >> 4);
+ bit_string[j++] = srtp_nibble_to_hex_char(x->v8[i] & 0xF);
+ }
+
+ bit_string[j] = 0; /* null terminate string */
+ return bit_string;
+}
+
+char *v128_bit_string(v128_t *x)
+{
+ int j, i;
+ uint32_t mask;
+
+ for (j = i = 0; j < 4; j++) {
+ for (mask = 0x80000000; mask > 0; mask >>= 1) {
+ if (x->v32[j] & mask)
+ bit_string[i] = '1';
+ else
+ bit_string[i] = '0';
+ ++i;
+ }
+ }
+ bit_string[128] = 0; /* null terminate string */
+
+ return bit_string;
+}
+
+void v128_copy_octet_string(v128_t *x, const uint8_t s[16])
+{
+#ifdef ALIGNMENT_32BIT_REQUIRED
+ if ((((uint32_t)&s[0]) & 0x3) != 0)
+#endif
+ {
+ x->v8[0] = s[0];
+ x->v8[1] = s[1];
+ x->v8[2] = s[2];
+ x->v8[3] = s[3];
+ x->v8[4] = s[4];
+ x->v8[5] = s[5];
+ x->v8[6] = s[6];
+ x->v8[7] = s[7];
+ x->v8[8] = s[8];
+ x->v8[9] = s[9];
+ x->v8[10] = s[10];
+ x->v8[11] = s[11];
+ x->v8[12] = s[12];
+ x->v8[13] = s[13];
+ x->v8[14] = s[14];
+ x->v8[15] = s[15];
+ }
+#ifdef ALIGNMENT_32BIT_REQUIRED
+ else {
+ v128_t *v = (v128_t *)&s[0];
+
+ v128_copy(x, v);
+ }
+#endif
+}
+
+#ifndef DATATYPES_USE_MACROS /* little functions are not macros */
+
+void v128_set_to_zero(v128_t *x)
+{
+ _v128_set_to_zero(x);
+}
+
+void v128_copy(v128_t *x, const v128_t *y)
+{
+ _v128_copy(x, y);
+}
+
+void v128_xor(v128_t *z, v128_t *x, v128_t *y)
+{
+ _v128_xor(z, x, y);
+}
+
+void v128_and(v128_t *z, v128_t *x, v128_t *y)
+{
+ _v128_and(z, x, y);
+}
+
+void v128_or(v128_t *z, v128_t *x, v128_t *y)
+{
+ _v128_or(z, x, y);
+}
+
+void v128_complement(v128_t *x)
+{
+ _v128_complement(x);
+}
+
+int v128_is_eq(const v128_t *x, const v128_t *y)
+{
+ return _v128_is_eq(x, y);
+}
+
+int v128_xor_eq(v128_t *x, const v128_t *y)
+{
+ return _v128_xor_eq(x, y);
+}
+
+int v128_get_bit(const v128_t *x, int i)
+{
+ return _v128_get_bit(x, i);
+}
+
+void v128_set_bit(v128_t *x, int i)
+{
+ _v128_set_bit(x, i);
+}
+
+void v128_clear_bit(v128_t *x, int i)
+{
+ _v128_clear_bit(x, i);
+}
+
+void v128_set_bit_to(v128_t *x, int i, int y)
+{
+ _v128_set_bit_to(x, i, y);
+}
+
+#endif /* DATATYPES_USE_MACROS */
+
+void v128_right_shift(v128_t *x, int shift)
+{
+ const int base_index = shift >> 5;
+ const int bit_index = shift & 31;
+ int i, from;
+ uint32_t b;
+
+ if (shift > 127) {
+ v128_set_to_zero(x);
+ return;
+ }
+
+ if (bit_index == 0) {
+ /* copy each word from left size to right side */
+ x->v32[4 - 1] = x->v32[4 - 1 - base_index];
+ for (i = 4 - 1; i > base_index; i--)
+ x->v32[i - 1] = x->v32[i - 1 - base_index];
+
+ } else {
+ /* set each word to the "or" of the two bit-shifted words */
+ for (i = 4; i > base_index; i--) {
+ from = i - 1 - base_index;
+ b = x->v32[from] << bit_index;
+ if (from > 0)
+ b |= x->v32[from - 1] >> (32 - bit_index);
+ x->v32[i - 1] = b;
+ }
+ }
+
+ /* now wrap up the final portion */
+ for (i = 0; i < base_index; i++)
+ x->v32[i] = 0;
+}
+
+void v128_left_shift(v128_t *x, int shift)
+{
+ int i;
+ const int base_index = shift >> 5;
+ const int bit_index = shift & 31;
+
+ if (shift > 127) {
+ v128_set_to_zero(x);
+ return;
+ }
+
+ if (bit_index == 0) {
+ for (i = 0; i < 4 - base_index; i++)
+ x->v32[i] = x->v32[i + base_index];
+ } else {
+ for (i = 0; i < 4 - base_index - 1; i++)
+ x->v32[i] = (x->v32[i + base_index] >> bit_index) ^
+ (x->v32[i + base_index + 1] << (32 - bit_index));
+ x->v32[4 - base_index - 1] = x->v32[4 - 1] >> bit_index;
+ }
+
+ /* now wrap up the final portion */
+ for (i = 4 - base_index; i < 4; i++)
+ x->v32[i] = 0;
+}
+
+/* functions manipulating bitvector_t */
+
+#ifndef DATATYPES_USE_MACROS /* little functions are not macros */
+
+int bitvector_get_bit(const bitvector_t *v, int bit_index)
+{
+ return _bitvector_get_bit(v, bit_index);
+}
+
+void bitvector_set_bit(bitvector_t *v, int bit_index)
+{
+ _bitvector_set_bit(v, bit_index);
+}
+
+void bitvector_clear_bit(bitvector_t *v, int bit_index)
+{
+ _bitvector_clear_bit(v, bit_index);
+}
+
+#endif /* DATATYPES_USE_MACROS */
+
+int bitvector_alloc(bitvector_t *v, unsigned long length)
+{
+ unsigned long l;
+
+ /* Round length up to a multiple of bits_per_word */
+ length =
+ (length + bits_per_word - 1) & ~(unsigned long)((bits_per_word - 1));
+
+ l = length / bits_per_word * bytes_per_word;
+
+ /* allocate memory, then set parameters */
+ if (l == 0) {
+ v->word = NULL;
+ v->length = 0;
+ return -1;
+ } else {
+ v->word = (uint32_t *)srtp_crypto_alloc(l);
+ if (v->word == NULL) {
+ v->length = 0;
+ return -1;
+ }
+ }
+ v->length = length;
+
+ /* initialize bitvector to zero */
+ bitvector_set_to_zero(v);
+
+ return 0;
+}
+
+void bitvector_dealloc(bitvector_t *v)
+{
+ if (v->word != NULL)
+ srtp_crypto_free(v->word);
+ v->word = NULL;
+ v->length = 0;
+}
+
+void bitvector_set_to_zero(bitvector_t *x)
+{
+ /* C99 guarantees that memset(0) will set the value 0 for uint32_t */
+ memset(x->word, 0, x->length >> 3);
+}
+
+char *bitvector_bit_string(bitvector_t *x, char *buf, int len)
+{
+ int j, i;
+ uint32_t mask;
+
+ for (j = i = 0; j < (int)(x->length >> 5) && i < len - 1; j++) {
+ for (mask = 0x80000000; mask > 0; mask >>= 1) {
+ if (x->word[j] & mask)
+ buf[i] = '1';
+ else
+ buf[i] = '0';
+ ++i;
+ if (i >= len - 1)
+ break;
+ }
+ }
+ buf[i] = 0; /* null terminate string */
+
+ return buf;
+}
+
+void bitvector_left_shift(bitvector_t *x, int shift)
+{
+ int i;
+ const int base_index = shift >> 5;
+ const int bit_index = shift & 31;
+ const int word_length = x->length >> 5;
+
+ if (shift >= (int)x->length) {
+ bitvector_set_to_zero(x);
+ return;
+ }
+
+ if (bit_index == 0) {
+ for (i = 0; i < word_length - base_index; i++)
+ x->word[i] = x->word[i + base_index];
+ } else {
+ for (i = 0; i < word_length - base_index - 1; i++)
+ x->word[i] = (x->word[i + base_index] >> bit_index) ^
+ (x->word[i + base_index + 1] << (32 - bit_index));
+ x->word[word_length - base_index - 1] =
+ x->word[word_length - 1] >> bit_index;
+ }
+
+ /* now wrap up the final portion */
+ for (i = word_length - base_index; i < word_length; i++)
+ x->word[i] = 0;
+}
+
+int octet_string_is_eq(uint8_t *a, uint8_t *b, int len)
+{
+ uint8_t *end = b + len;
+ uint8_t accumulator = 0;
+
+ /*
+ * We use this somewhat obscure implementation to try to ensure the running
+ * time only depends on len, even accounting for compiler optimizations.
+ * The accumulator ends up zero iff the strings are equal.
+ */
+ while (b < end)
+ accumulator |= (*a++ ^ *b++);
+
+ /* Return 1 if *not* equal. */
+ return accumulator != 0;
+}
+
+void srtp_cleanse(void *s, size_t len)
+{
+ volatile unsigned char *p = (volatile unsigned char *)s;
+ while (len--)
+ *p++ = 0;
+}
+
+void octet_string_set_to_zero(void *s, size_t len)
+{
+#if defined(OPENSSL) && !defined(OPENSSL_CLEANSE_BROKEN)
+ OPENSSL_cleanse(s, len);
+#else
+ srtp_cleanse(s, len);
+#endif
+}
+
+#ifdef TESTAPP_SOURCE
+
+static const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static int base64_block_to_octet_triple(char *out, char *in)
+{
+ unsigned char sextets[4] = { 0 };
+ int j = 0;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ char *p = strchr(b64chars, in[i]);
+ if (p != NULL)
+ sextets[i] = p - b64chars;
+ else
+ j++;
+ }
+
+ out[0] = (sextets[0] << 2) | (sextets[1] >> 4);
+ if (j < 2)
+ out[1] = (sextets[1] << 4) | (sextets[2] >> 2);
+ if (j < 1)
+ out[2] = (sextets[2] << 6) | sextets[3];
+ return j;
+}
+
+int base64_string_to_octet_string(char *out, int *pad, char *in, int len)
+{
+ int k = 0;
+ int i = 0;
+ int j = 0;
+ if (len % 4 != 0)
+ return 0;
+
+ while (i < len && j == 0) {
+ j = base64_block_to_octet_triple(out + k, in + i);
+ k += 3;
+ i += 4;
+ }
+ *pad = j;
+ return i;
+}
+
+#endif
diff --git a/netwerk/srtp/src/crypto/math/stat.c b/netwerk/srtp/src/crypto/math/stat.c
new file mode 100644
index 0000000000..6dd332b0cf
--- /dev/null
+++ b/netwerk/srtp/src/crypto/math/stat.c
@@ -0,0 +1,213 @@
+/*
+ * stats.c
+ *
+ * statistical tests
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "stat.h"
+
+srtp_debug_module_t srtp_mod_stat = {
+ 0, /* debugging is off by default */
+ (char *)"stat test" /* printable module name */
+};
+
+/*
+ * each test assumes that 20,000 bits (2500 octets) of data is
+ * provided as input
+ */
+
+#define STAT_TEST_DATA_LEN 2500
+
+srtp_err_status_t stat_test_monobit(uint8_t *data)
+{
+ uint8_t *data_end = data + STAT_TEST_DATA_LEN;
+ uint16_t ones_count;
+
+ ones_count = 0;
+ while (data < data_end) {
+ ones_count += octet_get_weight(*data);
+ data++;
+ }
+
+ debug_print(srtp_mod_stat, "bit count: %d", ones_count);
+
+ if ((ones_count < 9725) || (ones_count > 10275))
+ return srtp_err_status_algo_fail;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t stat_test_poker(uint8_t *data)
+{
+ int i;
+ uint8_t *data_end = data + STAT_TEST_DATA_LEN;
+ double poker;
+ uint16_t f[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ while (data < data_end) {
+ f[*data & 0x0f]++; /* increment freq. count for low nibble */
+ f[(*data) >> 4]++; /* increment freq. count for high nibble */
+ data++;
+ }
+
+ poker = 0.0;
+ for (i = 0; i < 16; i++)
+ poker += (double)f[i] * f[i];
+
+ poker *= (16.0 / 5000.0);
+ poker -= 5000.0;
+
+ debug_print(srtp_mod_stat, "poker test: %f\n", poker);
+
+ if ((poker < 2.16) || (poker > 46.17))
+ return srtp_err_status_algo_fail;
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * runs[i] holds the number of runs of size (i-1)
+ */
+
+srtp_err_status_t stat_test_runs(uint8_t *data)
+{
+ uint8_t *data_end = data + STAT_TEST_DATA_LEN;
+ uint16_t runs[6] = { 0, 0, 0, 0, 0, 0 };
+ uint16_t gaps[6] = { 0, 0, 0, 0, 0, 0 };
+ uint16_t lo_value[6] = { 2315, 1114, 527, 240, 103, 103 };
+ uint16_t hi_value[6] = { 2685, 1386, 723, 384, 209, 209 };
+ int state = 0;
+ uint16_t mask;
+ int i;
+
+ /*
+ * the state variable holds the number of bits in the
+ * current run (or gap, if negative)
+ */
+
+ while (data < data_end) {
+ /* loop over the bits of this byte */
+ for (mask = 1; mask < 256; mask <<= 1) {
+ if (*data & mask) {
+ /* next bit is a one */
+ if (state > 0) {
+ /* prefix is a run, so increment the run-count */
+ state++;
+
+ /* check for long runs */
+ if (state > 25) {
+ debug_print(srtp_mod_stat, ">25 runs: %d", state);
+ return srtp_err_status_algo_fail;
+ }
+
+ } else if (state < 0) {
+ /* prefix is a gap */
+ if (state < -25) {
+ debug_print(srtp_mod_stat, ">25 gaps: %d", state);
+ return srtp_err_status_algo_fail; /* long-runs test
+ failed */
+ }
+ if (state < -6) {
+ state = -6; /* group together gaps > 5 */
+ }
+ gaps[-1 - state]++; /* increment gap count */
+ state = 1; /* set state at one set bit */
+ } else {
+ /* state is zero; this happens only at initialization */
+ state = 1;
+ }
+ } else {
+ /* next bit is a zero */
+ if (state > 0) {
+ /* prefix is a run */
+ if (state > 25) {
+ debug_print(srtp_mod_stat, ">25 runs (2): %d", state);
+ return srtp_err_status_algo_fail; /* long-runs test
+ failed */
+ }
+ if (state > 6) {
+ state = 6; /* group together runs > 5 */
+ }
+ runs[state - 1]++; /* increment run count */
+ state = -1; /* set state at one zero bit */
+ } else if (state < 0) {
+ /* prefix is a gap, so increment gap-count (decrement state)
+ */
+ state--;
+
+ /* check for long gaps */
+ if (state < -25) {
+ debug_print(srtp_mod_stat, ">25 gaps (2): %d", state);
+ return srtp_err_status_algo_fail;
+ }
+
+ } else {
+ /* state is zero; this happens only at initialization */
+ state = -1;
+ }
+ }
+ }
+
+ /* move along to next octet */
+ data++;
+ }
+
+ if (srtp_mod_stat.on) {
+ debug_print(srtp_mod_stat, "runs test", NULL);
+ for (i = 0; i < 6; i++)
+ debug_print(srtp_mod_stat, " runs[]: %d", runs[i]);
+ for (i = 0; i < 6; i++)
+ debug_print(srtp_mod_stat, " gaps[]: %d", gaps[i]);
+ }
+
+ /* check run and gap counts against the fixed limits */
+ for (i = 0; i < 6; i++)
+ if ((runs[i] < lo_value[i]) || (runs[i] > hi_value[i]) ||
+ (gaps[i] < lo_value[i]) || (gaps[i] > hi_value[i]))
+ return srtp_err_status_algo_fail;
+
+ return srtp_err_status_ok;
+}
diff --git a/netwerk/srtp/src/crypto/replay/rdb.c b/netwerk/srtp/src/crypto/replay/rdb.c
new file mode 100644
index 0000000000..ab1c7b55b6
--- /dev/null
+++ b/netwerk/srtp/src/crypto/replay/rdb.c
@@ -0,0 +1,137 @@
+/*
+ * rdb.c
+ *
+ * Implements a replay database for packet security
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "rdb.h"
+
+/*
+ * this implementation of a replay database works as follows:
+ *
+ * window_start is the index of the first packet in the window
+ * bitmask a bit-buffer, containing the most recently entered
+ * index as the leftmost bit
+ *
+ */
+
+/* srtp_rdb_init initalizes rdb */
+srtp_err_status_t srtp_rdb_init(srtp_rdb_t *rdb)
+{
+ v128_set_to_zero(&rdb->bitmask);
+ rdb->window_start = 0;
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp_rdb_check checks to see if index appears in rdb
+ */
+srtp_err_status_t srtp_rdb_check(const srtp_rdb_t *rdb, uint32_t p_index)
+{
+ /* if the index appears after (or at very end of) the window, its good */
+ if (p_index >= rdb->window_start + rdb_bits_in_bitmask) {
+ return srtp_err_status_ok;
+ }
+
+ /* if the index appears before the window, its bad */
+ if (p_index < rdb->window_start) {
+ return srtp_err_status_replay_old;
+ }
+
+ /* otherwise, the index appears within the window, so check the bitmask */
+ if (v128_get_bit(&rdb->bitmask, (p_index - rdb->window_start)) == 1) {
+ return srtp_err_status_replay_fail;
+ }
+
+ /* otherwise, the index is okay */
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp_rdb_add_index adds index to srtp_rdb_t (and does *not* check if
+ * index appears in db)
+ *
+ * this function should be called only after srtp_rdb_check has
+ * indicated that the index does not appear in the rdb, e.g., a mutex
+ * should protect the rdb between these calls
+ */
+srtp_err_status_t srtp_rdb_add_index(srtp_rdb_t *rdb, uint32_t p_index)
+{
+ unsigned int delta;
+
+ if (p_index < rdb->window_start)
+ return srtp_err_status_replay_fail;
+
+ delta = (p_index - rdb->window_start);
+ if (delta < rdb_bits_in_bitmask) {
+ /* if the p_index is within the window, set the appropriate bit */
+ v128_set_bit(&rdb->bitmask, delta);
+
+ } else {
+ delta -= rdb_bits_in_bitmask - 1;
+
+ /* shift the window forward by delta bits*/
+ v128_left_shift(&rdb->bitmask, delta);
+ v128_set_bit(&rdb->bitmask, rdb_bits_in_bitmask - 1);
+ rdb->window_start += delta;
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_rdb_increment(srtp_rdb_t *rdb)
+{
+ if (rdb->window_start >= 0x7fffffff) {
+ return srtp_err_status_key_expired;
+ }
+ ++rdb->window_start;
+ return srtp_err_status_ok;
+}
+
+uint32_t srtp_rdb_get_value(const srtp_rdb_t *rdb)
+{
+ return rdb->window_start;
+}
diff --git a/netwerk/srtp/src/crypto/replay/rdbx.c b/netwerk/srtp/src/crypto/replay/rdbx.c
new file mode 100644
index 0000000000..40cba01a1f
--- /dev/null
+++ b/netwerk/srtp/src/crypto/replay/rdbx.c
@@ -0,0 +1,386 @@
+/*
+ * rdbx.c
+ *
+ * a replay database with extended range, using a rollover counter
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "rdbx.h"
+
+/*
+ * from RFC 3711:
+ *
+ * A receiver reconstructs the index i of a packet with sequence
+ * number SEQ using the estimate
+ *
+ * i = 2^16 * v + SEQ,
+ *
+ * where v is chosen from the set { ROC-1, ROC, ROC+1 } such that i is
+ * closest to the value 2^16 * ROC + s_l. If the value r+1 is used,
+ * then the rollover counter r in the cryptographic context is
+ * incremented by one (if the packet containing s is authentic).
+ */
+
+/*
+ * rdbx implementation notes
+ *
+ * A srtp_xtd_seq_num_t is essentially a sequence number for which some of
+ * the data on the wire are implicit. It logically consists of a
+ * rollover counter and a sequence number; the sequence number is the
+ * explicit part, and the rollover counter is the implicit part.
+ *
+ * Upon receiving a sequence_number (e.g. in a newly received SRTP
+ * packet), the complete srtp_xtd_seq_num_t can be estimated by using a
+ * local srtp_xtd_seq_num_t as a basis. This is done using the function
+ * srtp_index_guess(&local, &guess, seq_from_packet). This function
+ * returns the difference of the guess and the local value. The local
+ * srtp_xtd_seq_num_t can be moved forward to the guess using the function
+ * srtp_index_advance(&guess, delta), where delta is the difference.
+ *
+ *
+ * A srtp_rdbx_t consists of a srtp_xtd_seq_num_t and a bitmask. The index is
+ * highest sequence number that has been received, and the bitmask indicates
+ * which of the recent indicies have been received as well. The
+ * highest bit in the bitmask corresponds to the index in the bitmask.
+ */
+
+void srtp_index_init(srtp_xtd_seq_num_t *pi)
+{
+#ifdef NO_64BIT_MATH
+ *pi = make64(0, 0);
+#else
+ *pi = 0;
+#endif
+}
+
+void srtp_index_advance(srtp_xtd_seq_num_t *pi, srtp_sequence_number_t s)
+{
+#ifdef NO_64BIT_MATH
+ /* a > ~b means a+b will generate a carry */
+ /* s is uint16 here */
+ *pi = make64(high32(*pi) + (s > ~low32(*pi) ? 1 : 0), low32(*pi) + s);
+#else
+ *pi += s;
+#endif
+}
+
+/*
+ * srtp_index_guess(local, guess, s)
+ *
+ * given a srtp_xtd_seq_num_t local (which represents the last
+ * known-to-be-good received srtp_xtd_seq_num_t) and a sequence number s
+ * (from a newly arrived packet), sets the contents of *guess to
+ * contain the best guess of the packet index to which s corresponds,
+ * and returns the difference between *guess and *local
+ *
+ * nota bene - the output is a signed integer, DON'T cast it to a
+ * unsigned integer!
+ */
+
+int32_t srtp_index_guess(const srtp_xtd_seq_num_t *local,
+ srtp_xtd_seq_num_t *guess,
+ srtp_sequence_number_t s)
+{
+#ifdef NO_64BIT_MATH
+ uint32_t local_roc = ((high32(*local) << 16) | (low32(*local) >> 16));
+ uint16_t local_seq = (uint16_t)(low32(*local));
+#else
+ uint32_t local_roc = (uint32_t)(*local >> 16);
+ uint16_t local_seq = (uint16_t)*local;
+#endif
+ uint32_t guess_roc;
+ uint16_t guess_seq;
+ int32_t difference;
+
+ if (local_seq < seq_num_median) {
+ if (s - local_seq > seq_num_median) {
+ guess_roc = local_roc - 1;
+ difference = s - local_seq - seq_num_max;
+ } else {
+ guess_roc = local_roc;
+ difference = s - local_seq;
+ }
+ } else {
+ if (local_seq - seq_num_median > s) {
+ guess_roc = local_roc + 1;
+ difference = s - local_seq + seq_num_max;
+ } else {
+ guess_roc = local_roc;
+ difference = s - local_seq;
+ }
+ }
+ guess_seq = s;
+
+/* Note: guess_roc is 32 bits, so this generates a 48-bit result! */
+#ifdef NO_64BIT_MATH
+ *guess = make64(guess_roc >> 16, (guess_roc << 16) | guess_seq);
+#else
+ *guess = (((uint64_t)guess_roc) << 16) | guess_seq;
+#endif
+
+ return difference;
+}
+
+/*
+ * rdbx
+ *
+ */
+
+/*
+ * srtp_rdbx_init(&r, ws) initializes the srtp_rdbx_t pointed to by r with
+ * window size ws
+ */
+srtp_err_status_t srtp_rdbx_init(srtp_rdbx_t *rdbx, unsigned long ws)
+{
+ if (ws == 0) {
+ return srtp_err_status_bad_param;
+ }
+
+ if (bitvector_alloc(&rdbx->bitmask, ws) != 0) {
+ return srtp_err_status_alloc_fail;
+ }
+
+ srtp_index_init(&rdbx->index);
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp_rdbx_dealloc(&r) frees memory for the srtp_rdbx_t pointed to by r
+ */
+srtp_err_status_t srtp_rdbx_dealloc(srtp_rdbx_t *rdbx)
+{
+ bitvector_dealloc(&rdbx->bitmask);
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp_rdbx_set_roc(rdbx, roc) initalizes the srtp_rdbx_t at the location rdbx
+ * to have the rollover counter value roc. If that value is less than
+ * the current rollover counter value, then the function returns
+ * srtp_err_status_replay_old; otherwise, srtp_err_status_ok is returned.
+ *
+ */
+srtp_err_status_t srtp_rdbx_set_roc(srtp_rdbx_t *rdbx, uint32_t roc)
+{
+ bitvector_set_to_zero(&rdbx->bitmask);
+
+#ifdef NO_64BIT_MATH
+#error not yet implemented
+#else
+
+ /* make sure that we're not moving backwards */
+ if (roc < (rdbx->index >> 16)) {
+ return srtp_err_status_replay_old;
+ }
+
+ rdbx->index &= 0xffff; /* retain lowest 16 bits */
+ rdbx->index |= ((uint64_t)roc) << 16; /* set ROC */
+#endif
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp_rdbx_get_packet_index(rdbx) returns the value of the packet index
+ * for the srtp_rdbx_t pointed to by rdbx
+ *
+ */
+srtp_xtd_seq_num_t srtp_rdbx_get_packet_index(const srtp_rdbx_t *rdbx)
+{
+ return rdbx->index;
+}
+
+/*
+ * srtp_rdbx_get_window_size(rdbx) returns the value of the window size
+ * for the srtp_rdbx_t pointed to by rdbx
+ *
+ */
+unsigned long srtp_rdbx_get_window_size(const srtp_rdbx_t *rdbx)
+{
+ return bitvector_get_length(&rdbx->bitmask);
+}
+
+/*
+ * srtp_rdbx_check(&r, delta) checks to see if the srtp_xtd_seq_num_t
+ * which is at rdbx->index + delta is in the rdb
+ */
+srtp_err_status_t srtp_rdbx_check(const srtp_rdbx_t *rdbx, int delta)
+{
+ if (delta > 0) { /* if delta is positive, it's good */
+ return srtp_err_status_ok;
+ } else if ((int)(bitvector_get_length(&rdbx->bitmask) - 1) + delta < 0) {
+ /* if delta is lower than the bitmask, it's bad */
+ return srtp_err_status_replay_old;
+ } else if (bitvector_get_bit(
+ &rdbx->bitmask,
+ (int)(bitvector_get_length(&rdbx->bitmask) - 1) + delta) ==
+ 1) {
+ /* delta is within the window, so check the bitmask */
+ return srtp_err_status_replay_fail;
+ }
+ /* otherwise, the index is okay */
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp_rdbx_add_index adds the srtp_xtd_seq_num_t at rdbx->window_start + d to
+ * replay_db (and does *not* check if that srtp_xtd_seq_num_t appears in db)
+ *
+ * this function should be called only after replay_check has
+ * indicated that the index does not appear in the rdbx, e.g., a mutex
+ * should protect the rdbx between these calls if need be
+ */
+srtp_err_status_t srtp_rdbx_add_index(srtp_rdbx_t *rdbx, int delta)
+{
+ if (delta > 0) {
+ /* shift forward by delta */
+ srtp_index_advance(&rdbx->index, delta);
+ bitvector_left_shift(&rdbx->bitmask, delta);
+ bitvector_set_bit(&rdbx->bitmask,
+ bitvector_get_length(&rdbx->bitmask) - 1);
+ } else {
+ /* delta is in window */
+ bitvector_set_bit(&rdbx->bitmask,
+ bitvector_get_length(&rdbx->bitmask) - 1 + delta);
+ }
+
+ /* note that we need not consider the case that delta == 0 */
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp_rdbx_estimate_index(rdbx, guess, s)
+ *
+ * given an rdbx and a sequence number s (from a newly arrived packet),
+ * sets the contents of *guess to contain the best guess of the packet
+ * index to which s corresponds, and returns the difference between
+ * *guess and the locally stored synch info
+ */
+int32_t srtp_rdbx_estimate_index(const srtp_rdbx_t *rdbx,
+ srtp_xtd_seq_num_t *guess,
+ srtp_sequence_number_t s)
+{
+/*
+ * if the sequence number and rollover counter in the rdbx are
+ * non-zero, then use the srtp_index_guess(...) function, otherwise, just
+ * set the rollover counter to zero (since the srtp_index_guess(...)
+ * function might incorrectly guess that the rollover counter is
+ * 0xffffffff)
+ */
+
+#ifdef NO_64BIT_MATH
+ /* seq_num_median = 0x8000 */
+ if (high32(rdbx->index) > 0 || low32(rdbx->index) > seq_num_median)
+#else
+ if (rdbx->index > seq_num_median)
+#endif
+ {
+ return srtp_index_guess(&rdbx->index, guess, s);
+ }
+
+#ifdef NO_64BIT_MATH
+ *guess = make64(0, (uint32_t)s);
+#else
+ *guess = s;
+#endif
+
+#ifdef NO_64BIT_MATH
+ return s - (uint16_t)low32(rdbx->index);
+#else
+ return s - (uint16_t)rdbx->index;
+#endif
+}
+
+/*
+ * srtp_rdbx_get_roc(rdbx)
+ *
+ * Get the current rollover counter
+ *
+ */
+uint32_t srtp_rdbx_get_roc(const srtp_rdbx_t *rdbx)
+{
+ uint32_t roc;
+
+#ifdef NO_64BIT_MATH
+ roc = ((high32(rdbx->index) << 16) | (low32(rdbx->index) >> 16));
+#else
+ roc = (uint32_t)(rdbx->index >> 16);
+#endif
+
+ return roc;
+}
+
+/*
+ * srtp_rdbx_set_roc_seq(rdbx, roc, seq) initalizes the srtp_rdbx_t at the
+ * location rdbx to have the rollover counter value roc and packet sequence
+ * number seq. If the new rollover counter value is less than the current
+ * rollover counter value, then the function returns
+ * srtp_err_status_replay_old, otherwise, srtp_err_status_ok is returned.
+ */
+srtp_err_status_t srtp_rdbx_set_roc_seq(srtp_rdbx_t *rdbx,
+ uint32_t roc,
+ uint16_t seq)
+{
+#ifdef NO_64BIT_MATH
+#error not yet implemented
+#else
+
+ /* make sure that we're not moving backwards */
+ if (roc < (rdbx->index >> 16)) {
+ return srtp_err_status_replay_old;
+ }
+
+ rdbx->index = seq;
+ rdbx->index |= ((uint64_t)roc) << 16; /* set ROC */
+#endif
+
+ bitvector_set_to_zero(&rdbx->bitmask);
+
+ return srtp_err_status_ok;
+}
diff --git a/netwerk/srtp/src/crypto/replay/ut_sim.c b/netwerk/srtp/src/crypto/replay/ut_sim.c
new file mode 100644
index 0000000000..c621c0e9f2
--- /dev/null
+++ b/netwerk/srtp/src/crypto/replay/ut_sim.c
@@ -0,0 +1,104 @@
+/*
+ * ut_sim.c
+ *
+ * an unreliable transport simulator
+ * (for testing replay databases and suchlike)
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ut_sim.h"
+
+int ut_compar(const void *a, const void *b)
+{
+ return rand() > (RAND_MAX / 2) ? -1 : 1;
+}
+
+void ut_init(ut_connection *utc)
+{
+ int i;
+ utc->index = 0;
+
+ for (i = 0; i < UT_BUF; i++)
+ utc->buffer[i] = i;
+
+ qsort(utc->buffer, UT_BUF, sizeof(uint32_t), ut_compar);
+
+ utc->index = UT_BUF - 1;
+}
+
+uint32_t ut_next_index(ut_connection *utc)
+{
+ uint32_t tmp;
+
+ tmp = utc->buffer[0];
+ utc->index++;
+ utc->buffer[0] = utc->index;
+
+ qsort(utc->buffer, UT_BUF, sizeof(uint32_t), ut_compar);
+
+ return tmp;
+}
+
+#ifdef UT_TEST
+
+#include <stdio.h>
+
+int main()
+{
+ uint32_t i, irecvd, idiff;
+ ut_connection utc;
+
+ ut_init(&utc);
+
+ for (i = 0; i < 1000; i++) {
+ irecvd = ut_next_index(&utc);
+ idiff = i - irecvd;
+ printf("%lu\t%lu\t%d\n", i, irecvd, idiff);
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/netwerk/srtp/src/crypto/test/aes_calc.c b/netwerk/srtp/src/crypto/test/aes_calc.c
new file mode 100644
index 0000000000..b362fd57fc
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/aes_calc.c
@@ -0,0 +1,157 @@
+/*
+ * aes_calc.c
+ *
+ * A simple AES calculator for generating AES encryption values
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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.
+ *
+ */
+
+/*
+
+ Example usage (with first NIST FIPS 197 test case):
+
+ [sh]$ test/aes_calc 000102030405060708090a0b0c0d0e0f \
+ 00112233445566778899aabbccddeeff -v
+
+ plaintext: 00112233445566778899aabbccddeeff
+ key: 000102030405060708090a0b0c0d0e0f
+ ciphertext: 69c4e0d86a7b0430d8cdb78070b4c55a
+
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "aes.h"
+#include <stdio.h>
+#include <string.h>
+#include "util.h"
+
+void usage(char *prog_name)
+{
+ printf("usage: %s <key> <plaintext> [-v]\n", prog_name);
+ exit(255);
+}
+
+#define AES_MAX_KEY_LEN 32
+
+int main(int argc, char *argv[])
+{
+ v128_t data;
+ uint8_t key[AES_MAX_KEY_LEN];
+ srtp_aes_expanded_key_t exp_key;
+ int key_len, len;
+ int verbose = 0;
+ srtp_err_status_t status;
+
+ if (argc == 3) {
+ /* we're not in verbose mode */
+ verbose = 0;
+ } else if (argc == 4) {
+ if (strncmp(argv[3], "-v", 2) == 0) {
+ /* we're in verbose mode */
+ verbose = 1;
+ } else {
+ /* unrecognized flag, complain and exit */
+ usage(argv[0]);
+ }
+ } else {
+ /* we've been fed the wrong number of arguments - compain and exit */
+ usage(argv[0]);
+ }
+
+ /* read in key, checking length */
+ if (strlen(argv[1]) > AES_MAX_KEY_LEN * 2) {
+ fprintf(stderr, "error: too many digits in key "
+ "(should be at most %d hexadecimal digits, found %u)\n",
+ AES_MAX_KEY_LEN * 2, (unsigned)strlen(argv[1]));
+ exit(1);
+ }
+ len = hex_string_to_octet_string((char *)key, argv[1], AES_MAX_KEY_LEN * 2);
+ /* check that hex string is the right length */
+ if (len != 32 && len != 48 && len != 64) {
+ fprintf(stderr, "error: bad number of digits in key "
+ "(should be 32/48/64 hexadecimal digits, found %d)\n",
+ len);
+ exit(1);
+ }
+ key_len = len / 2;
+
+ /* read in plaintext, checking length */
+ if (strlen(argv[2]) > 16 * 2) {
+ fprintf(stderr, "error: too many digits in plaintext "
+ "(should be %d hexadecimal digits, found %u)\n",
+ 16 * 2, (unsigned)strlen(argv[2]));
+ exit(1);
+ }
+ len = hex_string_to_octet_string((char *)(&data), argv[2], 16 * 2);
+ /* check that hex string is the right length */
+ if (len < 16 * 2) {
+ fprintf(stderr, "error: too few digits in plaintext "
+ "(should be %d hexadecimal digits, found %d)\n",
+ 16 * 2, len);
+ exit(1);
+ }
+
+ if (verbose) {
+ /* print out plaintext */
+ printf("plaintext:\t%s\n",
+ octet_string_hex_string((uint8_t *)&data, 16));
+ }
+
+ /* encrypt plaintext */
+ status = srtp_aes_expand_encryption_key(key, key_len, &exp_key);
+ if (status) {
+ fprintf(stderr, "error: AES key expansion failed.\n");
+ exit(1);
+ }
+
+ srtp_aes_encrypt(&data, &exp_key);
+
+ /* write ciphertext to output */
+ if (verbose) {
+ printf("key:\t\t%s\n", octet_string_hex_string(key, key_len));
+ printf("ciphertext:\t");
+ }
+ printf("%s\n", v128_hex_string(&data));
+
+ return 0;
+}
diff --git a/netwerk/srtp/src/crypto/test/cipher_driver.c b/netwerk/srtp/src/crypto/test/cipher_driver.c
new file mode 100644
index 0000000000..4959c90c02
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/cipher_driver.c
@@ -0,0 +1,604 @@
+/*
+ * cipher_driver.c
+ *
+ * A driver for the generic cipher type
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h> /* for printf() */
+#include <stdlib.h> /* for rand() */
+#include "getopt_s.h"
+#include "cipher.h"
+#ifdef GCM
+#include "aes_icm_ext.h"
+#include "aes_gcm.h"
+#else
+#include "aes_icm.h"
+#endif
+
+#define PRINT_DEBUG 0
+
+void cipher_driver_test_throughput(srtp_cipher_t *c);
+
+srtp_err_status_t cipher_driver_self_test(srtp_cipher_type_t *ct);
+
+/*
+ * cipher_driver_test_buffering(ct) tests the cipher's output
+ * buffering for correctness by checking the consistency of succesive
+ * calls
+ */
+
+srtp_err_status_t cipher_driver_test_buffering(srtp_cipher_t *c);
+
+/*
+ * functions for testing cipher cache thrash
+ */
+srtp_err_status_t cipher_driver_test_array_throughput(srtp_cipher_type_t *ct,
+ int klen,
+ int num_cipher);
+
+void cipher_array_test_throughput(srtp_cipher_t *ca[], int num_cipher);
+
+uint64_t cipher_array_bits_per_second(srtp_cipher_t *cipher_array[],
+ int num_cipher,
+ unsigned octets_in_buffer,
+ int num_trials);
+
+srtp_err_status_t cipher_array_delete(srtp_cipher_t *cipher_array[],
+ int num_cipher);
+
+srtp_err_status_t cipher_array_alloc_init(srtp_cipher_t ***cipher_array,
+ int num_ciphers,
+ srtp_cipher_type_t *ctype,
+ int klen);
+
+void usage(char *prog_name)
+{
+ printf("usage: %s [ -t | -v | -a ]\n", prog_name);
+ exit(255);
+}
+
+void check_status(srtp_err_status_t s)
+{
+ if (s) {
+ printf("error (code %d)\n", s);
+ exit(s);
+ }
+ return;
+}
+
+/*
+ * null_cipher and srtp_aes_icm are the cipher meta-objects
+ * defined in the files in crypto/cipher subdirectory. these are
+ * declared external so that we can use these cipher types here
+ */
+
+extern srtp_cipher_type_t srtp_null_cipher;
+extern srtp_cipher_type_t srtp_aes_icm_128;
+extern srtp_cipher_type_t srtp_aes_icm_256;
+#ifdef GCM
+extern srtp_cipher_type_t srtp_aes_icm_192;
+extern srtp_cipher_type_t srtp_aes_gcm_128;
+extern srtp_cipher_type_t srtp_aes_gcm_256;
+#endif
+
+int main(int argc, char *argv[])
+{
+ srtp_cipher_t *c = NULL;
+ srtp_err_status_t status;
+ /* clang-format off */
+ unsigned char test_key[48] = {
+ 0x00, 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,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+ };
+ /* clang-format on */
+ int q;
+ unsigned do_timing_test = 0;
+ unsigned do_validation = 0;
+ unsigned do_array_timing_test = 0;
+
+ /* process input arguments */
+ while (1) {
+ q = getopt_s(argc, argv, "tva");
+ if (q == -1)
+ break;
+ switch (q) {
+ case 't':
+ do_timing_test = 1;
+ break;
+ case 'v':
+ do_validation = 1;
+ break;
+ case 'a':
+ do_array_timing_test = 1;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ printf("cipher test driver\n"
+ "David A. McGrew\n"
+ "Cisco Systems, Inc.\n");
+
+ if (!do_validation && !do_timing_test && !do_array_timing_test)
+ usage(argv[0]);
+
+ /* arry timing (cache thrash) test */
+ if (do_array_timing_test) {
+ int max_num_cipher = 1 << 16; /* number of ciphers in cipher_array */
+ int num_cipher;
+
+ for (num_cipher = 1; num_cipher < max_num_cipher; num_cipher *= 8)
+ cipher_driver_test_array_throughput(&srtp_null_cipher, 0,
+ num_cipher);
+
+ for (num_cipher = 1; num_cipher < max_num_cipher; num_cipher *= 8)
+ cipher_driver_test_array_throughput(
+ &srtp_aes_icm_128, SRTP_AES_ICM_128_KEY_LEN_WSALT, num_cipher);
+
+ for (num_cipher = 1; num_cipher < max_num_cipher; num_cipher *= 8)
+ cipher_driver_test_array_throughput(
+ &srtp_aes_icm_256, SRTP_AES_ICM_256_KEY_LEN_WSALT, num_cipher);
+
+#ifdef GCM
+ for (num_cipher = 1; num_cipher < max_num_cipher; num_cipher *= 8)
+ cipher_driver_test_array_throughput(
+ &srtp_aes_icm_192, SRTP_AES_ICM_192_KEY_LEN_WSALT, num_cipher);
+
+ for (num_cipher = 1; num_cipher < max_num_cipher; num_cipher *= 8) {
+ cipher_driver_test_array_throughput(
+ &srtp_aes_gcm_128, SRTP_AES_GCM_128_KEY_LEN_WSALT, num_cipher);
+ }
+
+ for (num_cipher = 1; num_cipher < max_num_cipher; num_cipher *= 8) {
+ cipher_driver_test_array_throughput(
+ &srtp_aes_gcm_256, SRTP_AES_GCM_256_KEY_LEN_WSALT, num_cipher);
+ }
+#endif
+ }
+
+ if (do_validation) {
+ cipher_driver_self_test(&srtp_null_cipher);
+ cipher_driver_self_test(&srtp_aes_icm_128);
+ cipher_driver_self_test(&srtp_aes_icm_256);
+#ifdef GCM
+ cipher_driver_self_test(&srtp_aes_icm_192);
+ cipher_driver_self_test(&srtp_aes_gcm_128);
+ cipher_driver_self_test(&srtp_aes_gcm_256);
+#endif
+ }
+
+ /* do timing and/or buffer_test on srtp_null_cipher */
+ status = srtp_cipher_type_alloc(&srtp_null_cipher, &c, 0, 0);
+ check_status(status);
+
+ status = srtp_cipher_init(c, NULL);
+ check_status(status);
+
+ if (do_timing_test)
+ cipher_driver_test_throughput(c);
+ if (do_validation) {
+ status = cipher_driver_test_buffering(c);
+ check_status(status);
+ }
+ status = srtp_cipher_dealloc(c);
+ check_status(status);
+
+ /* run the throughput test on the aes_icm cipher (128-bit key) */
+ status = srtp_cipher_type_alloc(&srtp_aes_icm_128, &c,
+ SRTP_AES_ICM_128_KEY_LEN_WSALT, 0);
+ if (status) {
+ fprintf(stderr, "error: can't allocate cipher\n");
+ exit(status);
+ }
+
+ status = srtp_cipher_init(c, test_key);
+ check_status(status);
+
+ if (do_timing_test)
+ cipher_driver_test_throughput(c);
+
+ if (do_validation) {
+ status = cipher_driver_test_buffering(c);
+ check_status(status);
+ }
+
+ status = srtp_cipher_dealloc(c);
+ check_status(status);
+
+ /* repeat the tests with 256-bit keys */
+ status = srtp_cipher_type_alloc(&srtp_aes_icm_256, &c,
+ SRTP_AES_ICM_256_KEY_LEN_WSALT, 0);
+ if (status) {
+ fprintf(stderr, "error: can't allocate cipher\n");
+ exit(status);
+ }
+
+ status = srtp_cipher_init(c, test_key);
+ check_status(status);
+
+ if (do_timing_test)
+ cipher_driver_test_throughput(c);
+
+ if (do_validation) {
+ status = cipher_driver_test_buffering(c);
+ check_status(status);
+ }
+
+ status = srtp_cipher_dealloc(c);
+ check_status(status);
+
+#ifdef GCM
+ /* run the throughput test on the aes_gcm_128 cipher */
+ status = srtp_cipher_type_alloc(&srtp_aes_gcm_128, &c,
+ SRTP_AES_GCM_128_KEY_LEN_WSALT, 8);
+ if (status) {
+ fprintf(stderr, "error: can't allocate GCM 128 cipher\n");
+ exit(status);
+ }
+ status = srtp_cipher_init(c, test_key);
+ check_status(status);
+ if (do_timing_test) {
+ cipher_driver_test_throughput(c);
+ }
+
+ // GCM ciphers don't do buffering; they're "one shot"
+
+ status = srtp_cipher_dealloc(c);
+ check_status(status);
+
+ /* run the throughput test on the aes_gcm_256 cipher */
+ status = srtp_cipher_type_alloc(&srtp_aes_gcm_256, &c,
+ SRTP_AES_GCM_256_KEY_LEN_WSALT, 16);
+ if (status) {
+ fprintf(stderr, "error: can't allocate GCM 256 cipher\n");
+ exit(status);
+ }
+ status = srtp_cipher_init(c, test_key);
+ check_status(status);
+ if (do_timing_test) {
+ cipher_driver_test_throughput(c);
+ }
+
+ // GCM ciphers don't do buffering; they're "one shot"
+
+ status = srtp_cipher_dealloc(c);
+ check_status(status);
+#endif
+
+ return 0;
+}
+
+void cipher_driver_test_throughput(srtp_cipher_t *c)
+{
+ int i;
+ int min_enc_len = 32;
+ int max_enc_len = 2048; /* should be a power of two */
+ int num_trials = 1000000;
+
+ printf("timing %s throughput, key length %d:\n", c->type->description,
+ c->key_len);
+ fflush(stdout);
+ for (i = min_enc_len; i <= max_enc_len; i = i * 2)
+ printf("msg len: %d\tgigabits per second: %f\n", i,
+ srtp_cipher_bits_per_second(c, i, num_trials) / 1e9);
+}
+
+srtp_err_status_t cipher_driver_self_test(srtp_cipher_type_t *ct)
+{
+ srtp_err_status_t status;
+
+ printf("running cipher self-test for %s...", ct->description);
+ status = srtp_cipher_type_self_test(ct);
+ if (status) {
+ printf("failed with error code %d\n", status);
+ exit(status);
+ }
+ printf("passed\n");
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * cipher_driver_test_buffering(ct) tests the cipher's output
+ * buffering for correctness by checking the consistency of succesive
+ * calls
+ */
+
+#define INITIAL_BUFLEN 1024
+srtp_err_status_t cipher_driver_test_buffering(srtp_cipher_t *c)
+{
+ int i, j, num_trials = 1000;
+ unsigned len, buflen = INITIAL_BUFLEN;
+ uint8_t buffer0[INITIAL_BUFLEN], buffer1[INITIAL_BUFLEN], *current, *end;
+ uint8_t idx[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34 };
+ srtp_err_status_t status;
+
+ printf("testing output buffering for cipher %s...", c->type->description);
+
+ for (i = 0; i < num_trials; i++) {
+ /* set buffers to zero */
+ for (j = 0; j < (int)buflen; j++) {
+ buffer0[j] = buffer1[j] = 0;
+ }
+
+ /* initialize cipher */
+ status = srtp_cipher_set_iv(c, (uint8_t *)idx, srtp_direction_encrypt);
+ if (status)
+ return status;
+
+ /* generate 'reference' value by encrypting all at once */
+ status = srtp_cipher_encrypt(c, buffer0, &buflen);
+ if (status)
+ return status;
+
+ /* re-initialize cipher */
+ status = srtp_cipher_set_iv(c, (uint8_t *)idx, srtp_direction_encrypt);
+ if (status)
+ return status;
+
+ /* now loop over short lengths until buffer1 is encrypted */
+ current = buffer1;
+ end = buffer1 + buflen;
+ while (current < end) {
+ /* choose a short length */
+ len = rand() & 0x01f;
+
+ /* make sure that len doesn't cause us to overreach the buffer */
+ if (current + len > end)
+ len = end - current;
+
+ status = srtp_cipher_encrypt(c, current, &len);
+ if (status)
+ return status;
+
+ /* advance pointer into buffer1 to reflect encryption */
+ current += len;
+
+ /* if buffer1 is all encrypted, break out of loop */
+ if (current == end)
+ break;
+ }
+
+ /* compare buffers */
+ for (j = 0; j < (int)buflen; j++) {
+ if (buffer0[j] != buffer1[j]) {
+#if PRINT_DEBUG
+ printf("test case %d failed at byte %d\n", i, j);
+ printf("computed: %s\n",
+ octet_string_hex_string(buffer1, buflen));
+ printf("expected: %s\n",
+ octet_string_hex_string(buffer0, buflen));
+#endif
+ return srtp_err_status_algo_fail;
+ }
+ }
+ }
+
+ printf("passed\n");
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * The function cipher_test_throughput_array() tests the effect of CPU
+ * cache thrash on cipher throughput.
+ *
+ * cipher_array_alloc_init(ctype, array, num_ciphers) creates an array
+ * of srtp_cipher_t of type ctype
+ */
+
+srtp_err_status_t cipher_array_alloc_init(srtp_cipher_t ***ca,
+ int num_ciphers,
+ srtp_cipher_type_t *ctype,
+ int klen)
+{
+ int i, j;
+ srtp_err_status_t status;
+ uint8_t *key;
+ srtp_cipher_t **cipher_array;
+ /* pad klen allocation, to handle aes_icm reading 16 bytes for the
+ 14-byte salt */
+ int klen_pad = ((klen + 15) >> 4) << 4;
+
+ /* allocate array of pointers to ciphers */
+ cipher_array = (srtp_cipher_t **)srtp_crypto_alloc(sizeof(srtp_cipher_t *) *
+ num_ciphers);
+ if (cipher_array == NULL)
+ return srtp_err_status_alloc_fail;
+
+ /* set ca to location of cipher_array */
+ *ca = cipher_array;
+
+ /* allocate key */
+ key = srtp_crypto_alloc(klen_pad);
+ if (key == NULL) {
+ srtp_crypto_free(cipher_array);
+ return srtp_err_status_alloc_fail;
+ }
+
+ /* allocate and initialize an array of ciphers */
+ for (i = 0; i < num_ciphers; i++) {
+ /* allocate cipher */
+ status = srtp_cipher_type_alloc(ctype, cipher_array, klen, 16);
+ if (status)
+ return status;
+
+ /* generate random key and initialize cipher */
+ for (j = 0; j < klen; j++)
+ key[j] = (uint8_t)rand();
+ for (; j < klen_pad; j++)
+ key[j] = 0;
+ status = srtp_cipher_init(*cipher_array, key);
+ if (status)
+ return status;
+
+ /* printf("%dth cipher is at %p\n", i, *cipher_array); */
+ /* printf("%dth cipher description: %s\n", i, */
+ /* (*cipher_array)->type->description); */
+
+ /* advance cipher array pointer */
+ cipher_array++;
+ }
+
+ srtp_crypto_free(key);
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t cipher_array_delete(srtp_cipher_t *cipher_array[],
+ int num_cipher)
+{
+ int i;
+
+ for (i = 0; i < num_cipher; i++) {
+ srtp_cipher_dealloc(cipher_array[i]);
+ }
+
+ srtp_crypto_free(cipher_array);
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * cipher_array_bits_per_second(c, l, t) computes (an estimate of) the
+ * number of bits that a cipher implementation can encrypt in a second
+ * when distinct keys are used to encrypt distinct messages
+ *
+ * c is a cipher (which MUST be allocated an initialized already), l
+ * is the length in octets of the test data to be encrypted, and t is
+ * the number of trials
+ *
+ * if an error is encountered, the value 0 is returned
+ */
+
+uint64_t cipher_array_bits_per_second(srtp_cipher_t *cipher_array[],
+ int num_cipher,
+ unsigned octets_in_buffer,
+ int num_trials)
+{
+ int i;
+ v128_t nonce;
+ clock_t timer;
+ unsigned char *enc_buf;
+ int cipher_index = rand() % num_cipher;
+
+ /* Over-alloc, for NIST CBC padding */
+ enc_buf = srtp_crypto_alloc(octets_in_buffer + 17);
+ if (enc_buf == NULL)
+ return 0; /* indicate bad parameters by returning null */
+
+ /* time repeated trials */
+ v128_set_to_zero(&nonce);
+ timer = clock();
+ for (i = 0; i < num_trials; i++, nonce.v32[3] = i) {
+ /* length parameter to srtp_cipher_encrypt is in/out -- out is total,
+ * padded
+ * length -- so reset it each time. */
+ unsigned octets_to_encrypt = octets_in_buffer;
+
+ /* encrypt buffer with cipher */
+ srtp_cipher_set_iv(cipher_array[cipher_index], (uint8_t *)&nonce,
+ srtp_direction_encrypt);
+ srtp_cipher_encrypt(cipher_array[cipher_index], enc_buf,
+ &octets_to_encrypt);
+
+ /* choose a cipher at random from the array*/
+ cipher_index = (*((uint32_t *)enc_buf)) % num_cipher;
+ }
+ timer = clock() - timer;
+
+ srtp_crypto_free(enc_buf);
+
+ if (timer == 0) {
+ /* Too fast! */
+ return 0;
+ }
+
+ return (uint64_t)CLOCKS_PER_SEC * num_trials * 8 * octets_in_buffer / timer;
+}
+
+void cipher_array_test_throughput(srtp_cipher_t *ca[], int num_cipher)
+{
+ int i;
+ int min_enc_len = 16;
+ int max_enc_len = 2048; /* should be a power of two */
+ int num_trials = 1000000;
+
+ printf("timing %s throughput with key length %d, array size %d:\n",
+ (ca[0])->type->description, (ca[0])->key_len, num_cipher);
+ fflush(stdout);
+ for (i = min_enc_len; i <= max_enc_len; i = i * 4)
+ printf("msg len: %d\tgigabits per second: %f\n", i,
+ cipher_array_bits_per_second(ca, num_cipher, i, num_trials) /
+ 1e9);
+}
+
+srtp_err_status_t cipher_driver_test_array_throughput(srtp_cipher_type_t *ct,
+ int klen,
+ int num_cipher)
+{
+ srtp_cipher_t **ca = NULL;
+ srtp_err_status_t status;
+
+ status = cipher_array_alloc_init(&ca, num_cipher, ct, klen);
+ if (status) {
+ printf("error: cipher_array_alloc_init() failed with error code %d\n",
+ status);
+ return status;
+ }
+
+ cipher_array_test_throughput(ca, num_cipher);
+
+ cipher_array_delete(ca, num_cipher);
+
+ return srtp_err_status_ok;
+}
diff --git a/netwerk/srtp/src/crypto/test/datatypes_driver.c b/netwerk/srtp/src/crypto/test/datatypes_driver.c
new file mode 100644
index 0000000000..96379befe1
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/datatypes_driver.c
@@ -0,0 +1,256 @@
+/*
+ * datatypes_driver.c
+ *
+ * a test driver for crypto/math datatypes
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h> /* for printf() */
+#include <string.h> /* for strlen() */
+#include "datatypes.h"
+#include "util.h"
+
+void byte_order(void);
+
+void test_hex_string_funcs(void);
+
+void print_string(char *s);
+
+void test_bswap(void);
+
+void test_set_to_zero(void);
+
+int main(void)
+{
+ /*
+ * this program includes various and sundry tests for fundamental
+ * datatypes. it's a grab-bag of throwaway code, retained only in
+ * case of future problems
+ */
+
+ int i, j;
+ v128_t x;
+ char *r = "The Moving Finger writes; and, having writ,\n"
+ "Moves on: nor all thy Piety nor Wit\n"
+ "Shall lure it back to cancel half a Line,\n"
+ "Nor all thy Tears wash out a Word of it.";
+ char *s = "incomplet";
+
+ print_string(r);
+ print_string(s);
+
+ byte_order();
+ test_hex_string_funcs();
+
+ for (j = 0; j < 128; j++) {
+ v128_set_to_zero(&x);
+ /* x.v32[0] = (1 << j); */
+ v128_set_bit(&x, j);
+ printf("%s\n", v128_bit_string(&x));
+ v128_clear_bit(&x, j);
+ printf("%s\n", v128_bit_string(&x));
+ }
+
+ printf("----------------------------------------------\n");
+ v128_set_to_zero(&x);
+ for (i = 0; i < 128; i++) {
+ v128_set_bit(&x, i);
+ }
+ printf("%s\n", v128_bit_string(&x));
+
+ printf("----------------------------------------------\n");
+ v128_set_to_zero(&x);
+ v128_set_bit(&x, 0);
+ for (i = 0; i < 128; i++) {
+ printf("%s\n", v128_bit_string(&x));
+ v128_right_shift(&x, 1);
+ }
+ printf("----------------------------------------------\n");
+ v128_set_to_zero(&x);
+ v128_set_bit(&x, 127);
+ for (i = 0; i < 128; i++) {
+ printf("%s\n", v128_bit_string(&x));
+ v128_left_shift(&x, 1);
+ }
+ printf("----------------------------------------------\n");
+ for (i = 0; i < 128; i++) {
+ v128_set_to_zero(&x);
+ v128_set_bit(&x, 127);
+ v128_left_shift(&x, i);
+ printf("%s\n", v128_bit_string(&x));
+ }
+ printf("----------------------------------------------\n");
+ v128_set_to_zero(&x);
+ for (i = 0; i < 128; i += 2) {
+ v128_set_bit(&x, i);
+ }
+ printf("bit_string: { %s }\n", v128_bit_string(&x));
+ printf("get_bit: { ");
+ for (i = 0; i < 128; i++) {
+ if (v128_get_bit(&x, i) == 1)
+ printf("1");
+ else
+ printf("0");
+ }
+ printf(" } \n");
+
+ test_bswap();
+ test_set_to_zero();
+
+ return 0;
+}
+
+/* byte_order() prints out byte ordering of datatypes */
+
+void byte_order(void)
+{
+ int i;
+ v128_t e;
+#if 0
+ v16_t b;
+ v32_t c;
+ v64_t d;
+
+ for (i=0; i < sizeof(b); i++)
+ b.octet[i] = i;
+ for (i=0; i < sizeof(c); i++)
+ c.octet[i] = i;
+ for (i=0; i < sizeof(d); i++)
+ d.octet[i] = i;
+
+ printf("v128_t:\t%s\n", v128_hex_string(&e));
+ printf("v64_t:\t%s\n", v64_hex_string(&d));
+ printf("v32_t:\t%s\n", v32_hex_string(c));
+ printf("v16_t:\t%s\n", v16_hex_string(b));
+
+ c.value = 0x01020304;
+ printf("v32_t:\t%s\n", v32_hex_string(c));
+ b.value = 0x0102;
+ printf("v16_t:\t%s\n", v16_hex_string(b));
+
+ printf("uint16_t ordering:\n");
+
+ c.value = 0x00010002;
+ printf("v32_t:\t%x%x\n", c.v16[0], c.v16[1]);
+#endif
+
+ printf("byte ordering of crypto/math datatypes:\n");
+ for (i = 0; i < sizeof(e); i++)
+ e.v8[i] = i;
+ printf("v128_t: %s\n", v128_hex_string(&e));
+}
+
+void test_hex_string_funcs(void)
+{
+ char hex1[] = "abadcafe";
+ char hex2[] = "0123456789abcdefqqqqq";
+ char raw[10];
+ int len;
+
+ len = hex_string_to_octet_string(raw, hex1, strlen(hex1));
+ printf("computed length: %d\tstring: %s\n", len,
+ octet_string_hex_string(raw, len / 2));
+ printf("expected length: %u\tstring: %s\n", (unsigned)strlen(hex1), hex1);
+
+ len = hex_string_to_octet_string(raw, hex2, strlen(hex2));
+ printf("computed length: %d\tstring: %s\n", len,
+ octet_string_hex_string(raw, len / 2));
+ printf("expected length: %d\tstring: %s\n", 16, "0123456789abcdef");
+}
+
+void print_string(char *s)
+{
+ size_t i;
+ printf("%s\n", s);
+ printf("strlen(s) = %u\n", (unsigned)strlen(s));
+ printf("{ ");
+ for (i = 0; i < strlen(s); i++) {
+ printf("0x%x, ", s[i]);
+ if (((i + 1) % 8) == 0)
+ printf("\n ");
+ }
+ printf("}\n");
+}
+
+void test_bswap(void)
+{
+ uint32_t x = 0x11223344;
+ uint64_t y = 0x1122334455667788LL;
+
+ printf("before: %0x\nafter: %0x\n", x, (unsigned int)be32_to_cpu(x));
+ printf("before: %0llx\nafter: %0llx\n", (unsigned long long)y,
+ (unsigned long long)be64_to_cpu(y));
+
+ y = 1234;
+
+ printf("1234: %0llx\n", (unsigned long long)y);
+ printf("as octet string: %s\n", octet_string_hex_string((uint8_t *)&y, 8));
+ y = be64_to_cpu(y);
+ printf("bswapped octet string: %s\n",
+ octet_string_hex_string((uint8_t *)&y, 8));
+}
+
+void test_set_to_zero(void)
+{
+#define BUFFER_SIZE (16)
+ uint8_t buffer[BUFFER_SIZE];
+ size_t i;
+
+ for (i = 0; i < BUFFER_SIZE; i++) {
+ buffer[i] = i & 0xff;
+ }
+ printf("Buffer before: %s\n", octet_string_hex_string(buffer, BUFFER_SIZE));
+ octet_string_set_to_zero(buffer, BUFFER_SIZE);
+ printf("Buffer after: %s\n", octet_string_hex_string(buffer, BUFFER_SIZE));
+ for (i = 0; i < BUFFER_SIZE; i++) {
+ if (buffer[i]) {
+ fprintf(stderr,
+ "Buffer contents not zero at position %zu (is %d)\n", i,
+ buffer[i]);
+ abort();
+ }
+ }
+#undef BUFFER_SIZE
+}
diff --git a/netwerk/srtp/src/crypto/test/env.c b/netwerk/srtp/src/crypto/test/env.c
new file mode 100644
index 0000000000..8c3f4edfac
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/env.c
@@ -0,0 +1,89 @@
+/*
+ * env.c
+ *
+ * prints out a brief report on the build environment
+ *
+ * David McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 <stdio.h>
+#include <string.h> /* for srtcmp() */
+#include "config.h"
+
+int main(void)
+{
+ int err_count = 0;
+
+#ifdef WORDS_BIGENDIAN
+ printf("CPU set to big-endian\t\t\t(WORDS_BIGENDIAN == 1)\n");
+#else
+ printf("CPU set to little-endian\t\t(WORDS_BIGENDIAN == 0)\n");
+#endif
+
+#ifdef CPU_RISC
+ printf("CPU set to RISC\t\t\t\t(CPU_RISC == 1)\n");
+#elif defined(CPU_CISC)
+ printf("CPU set to CISC\t\t\t\t(CPU_CISC == 1)\n");
+#else
+ printf(
+ "CPU set to an unknown type, probably due to a configuration error\n");
+ err_count++;
+#endif
+
+#ifdef CPU_ALTIVEC
+ printf("CPU set to ALTIVEC\t\t\t\t(CPU_ALTIVEC == 0)\n");
+#endif
+
+#ifndef NO_64BIT_MATH
+ printf("using native 64-bit type\t\t(NO_64_BIT_MATH == 0)\n");
+#else
+ printf("using built-in 64-bit math\t\t(NO_64_BIT_MATH == 1)\n");
+#endif
+
+#ifdef ERR_REPORTING_STDOUT
+ printf("using stdout for error reporting\t(ERR_REPORTING_STDOUT == 1)\n");
+#endif
+
+ if (err_count)
+ printf("warning: configuration is probably in error "
+ "(found %d problems)\n",
+ err_count);
+
+ return err_count;
+}
diff --git a/netwerk/srtp/src/crypto/test/kernel_driver.c b/netwerk/srtp/src/crypto/test/kernel_driver.c
new file mode 100644
index 0000000000..d29405a971
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/kernel_driver.c
@@ -0,0 +1,127 @@
+/*
+ * kernel_driver.c
+ *
+ * a test driver for the crypto_kernel
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright(c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h> /* for printf() */
+#include "getopt_s.h"
+#include "crypto_kernel.h"
+
+void usage(char *prog_name)
+{
+ printf("usage: %s [ -v ][ -d debug_module ]*\n", prog_name);
+ exit(255);
+}
+
+int main(int argc, char *argv[])
+{
+ int q;
+ int do_validation = 0;
+ srtp_err_status_t status;
+
+ if (argc == 1)
+ usage(argv[0]);
+
+ /* initialize kernel - we need to do this before anything else */
+ status = srtp_crypto_kernel_init();
+ if (status) {
+ printf("error: srtp_crypto_kernel init failed\n");
+ exit(1);
+ }
+ printf("srtp_crypto_kernel successfully initalized\n");
+
+ /* process input arguments */
+ while (1) {
+ q = getopt_s(argc, argv, "vd:");
+ if (q == -1)
+ break;
+ switch (q) {
+ case 'v':
+ do_validation = 1;
+ break;
+ case 'd':
+ status = srtp_crypto_kernel_set_debug_module(optarg_s, 1);
+ if (status) {
+ printf("error: set debug module (%s) failed\n", optarg_s);
+ exit(1);
+ }
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (do_validation) {
+ printf("checking srtp_crypto_kernel status...\n");
+ status = srtp_crypto_kernel_status();
+ if (status) {
+ printf("failed\n");
+ exit(1);
+ }
+ printf("srtp_crypto_kernel passed self-tests\n");
+ }
+
+ status = srtp_crypto_kernel_shutdown();
+ if (status) {
+ printf("error: srtp_crypto_kernel shutdown failed\n");
+ exit(1);
+ }
+ printf("srtp_crypto_kernel successfully shut down\n");
+
+ return 0;
+}
+
+/*
+ * crypto_kernel_cipher_test() is a test of the cipher interface
+ * of the crypto_kernel
+ */
+
+srtp_err_status_t crypto_kernel_cipher_test(void)
+{
+ /* not implemented yet! */
+
+ return srtp_err_status_ok;
+}
diff --git a/netwerk/srtp/src/crypto/test/sha1_driver.c b/netwerk/srtp/src/crypto/test/sha1_driver.c
new file mode 100644
index 0000000000..c64b000f81
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/sha1_driver.c
@@ -0,0 +1,387 @@
+/*
+ * sha1_driver.c
+ *
+ * a test driver for SHA-1
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include "sha1.h"
+#include "util.h"
+
+#define SHA_PASS 0
+#define SHA_FAIL 1
+
+#define MAX_HASH_DATA_LEN 1024
+#define MAX_HASH_OUT_LEN 20
+
+typedef struct hash_test_case_t {
+ unsigned data_len; /* number of octets in data */
+ unsigned hash_len; /* number of octets output by hash */
+ uint8_t data[MAX_HASH_DATA_LEN]; /* message data */
+ uint8_t hash[MAX_HASH_OUT_LEN]; /* expected hash output */
+ struct hash_test_case_t *next_test_case;
+} hash_test_case_t;
+
+hash_test_case_t *sha1_test_case_list;
+
+srtp_err_status_t hash_test_case_add(hash_test_case_t **list_ptr,
+ char *hex_data,
+ unsigned data_len,
+ char *hex_hash,
+ unsigned hash_len)
+{
+ hash_test_case_t *list_head = *list_ptr;
+ hash_test_case_t *test_case;
+ unsigned tmp_len;
+
+ test_case = malloc(sizeof(hash_test_case_t));
+ if (test_case == NULL)
+ return srtp_err_status_alloc_fail;
+
+ tmp_len = hex_string_to_octet_string((char *)test_case->data, hex_data,
+ data_len * 2);
+ if (tmp_len != data_len * 2) {
+ free(test_case);
+ return srtp_err_status_parse_err;
+ }
+
+ tmp_len = hex_string_to_octet_string((char *)test_case->hash, hex_hash,
+ hash_len * 2);
+ if (tmp_len != hash_len * 2) {
+ free(test_case);
+ return srtp_err_status_parse_err;
+ }
+
+ test_case->data_len = data_len;
+ test_case->hash_len = hash_len;
+
+ /* add the new test case to the head of the list */
+ test_case->next_test_case = list_head;
+ *list_ptr = test_case;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t sha1_test_case_validate(const hash_test_case_t *test_case)
+{
+ srtp_sha1_ctx_t ctx;
+ uint32_t hash_value[5];
+
+ if (test_case == NULL)
+ return srtp_err_status_bad_param;
+
+ if (test_case->hash_len != 20)
+ return srtp_err_status_bad_param;
+ if (test_case->data_len > MAX_HASH_DATA_LEN)
+ return srtp_err_status_bad_param;
+
+ srtp_sha1_init(&ctx);
+ srtp_sha1_update(&ctx, test_case->data, test_case->data_len);
+ srtp_sha1_final(&ctx, hash_value);
+ if (0 == memcmp(test_case->hash, hash_value, 20)) {
+#if VERBOSE
+ printf("PASSED: reference value: %s\n",
+ octet_string_hex_string((const uint8_t *)test_case->hash, 20));
+ printf("PASSED: computed value: %s\n",
+ octet_string_hex_string((const uint8_t *)hash_value, 20));
+#endif
+ return srtp_err_status_ok;
+ }
+
+ printf("reference value: %s\n",
+ octet_string_hex_string((const uint8_t *)test_case->hash, 20));
+ printf("computed value: %s\n",
+ octet_string_hex_string((const uint8_t *)hash_value, 20));
+
+ return srtp_err_status_algo_fail;
+}
+
+struct hex_sha1_test_case_t {
+ unsigned bit_len;
+ char hex_data[MAX_HASH_DATA_LEN * 2];
+ char hex_hash[40];
+};
+
+srtp_err_status_t sha1_add_test_cases(void)
+{
+ int i;
+ srtp_err_status_t err;
+
+ /*
+ * these test cases are taken from the "SHA-1 Sample Vectors"
+ * provided by NIST at http://csrc.nist.gov/cryptval/shs.html
+ */
+
+ struct hex_sha1_test_case_t tc[] = {
+ { 0, "", "da39a3ee5e6b4b0d3255bfef95601890afd80709" },
+ { 8, "a8", "99f2aa95e36f95c2acb0eaf23998f030638f3f15" },
+ { 16, "3000", "f944dcd635f9801f7ac90a407fbc479964dec024" },
+ { 24, "42749e", "a444319e9b6cc1e8464c511ec0969c37d6bb2619" },
+ { 32, "9fc3fe08", "16a0ff84fcc156fd5d3ca3a744f20a232d172253" },
+ { 40, "b5c1c6f1af", "fec9deebfcdedaf66dda525e1be43597a73a1f93" },
+ { 48, "e47571e5022e", "8ce051181f0ed5e9d0c498f6bc4caf448d20deb5" },
+ { 56, "3e1b28839fb758", "67da53837d89e03bf652ef09c369a3415937cfd3" },
+ { 64, "a81350cbb224cb90", "305e4ff9888ad855a78573cddf4c5640cce7e946" },
+ { 72, "c243d167923dec3ce1",
+ "5902b77b3265f023f9bbc396ba1a93fa3509bde7" },
+ { 80, "50ac18c59d6a37a29bf4",
+ "fcade5f5d156bf6f9af97bdfa9c19bccfb4ff6ab" },
+ { 88, "98e2b611ad3b1cccf634f6",
+ "1d20fbe00533c10e3cbd6b27088a5de0c632c4b5" },
+ { 96, "73fe9afb68e1e8712e5d4eec",
+ "7e1b7e0f7a8f3455a9c03e9580fd63ae205a2d93" },
+ { 104, "9e701ed7d412a9226a2a130e66",
+ "706f0677146307b20bb0e8d6311e329966884d13" },
+ { 112, "6d3ee90413b0a7cbf69e5e6144ca",
+ "a7241a703aaf0d53fe142f86bf2e849251fa8dff" },
+ { 120, "fae24d56514efcb530fd4802f5e71f",
+ "400f53546916d33ad01a5e6df66822dfbdc4e9e6" },
+ { 128, "c5a22dd6eda3fe2bdc4ddb3ce6b35fd1",
+ "fac8ab93c1ae6c16f0311872b984f729dc928ccd" },
+ { 136, "d98cded2adabf08fda356445c781802d95",
+ "fba6d750c18da58f6e2aab10112b9a5ef3301b3b" },
+ { 144, "bcc6d7087a84f00103ccb32e5f5487a751a2",
+ "29d27c2d44c205c8107f0351b05753ac708226b6" },
+ { 152, "36ecacb1055434190dbbc556c48bafcb0feb0d",
+ "b971bfc1ebd6f359e8d74cb7ecfe7f898d0ba845" },
+ { 160, "5ff9edb69e8f6bbd498eb4537580b7fba7ad31d0",
+ "96d08c430094b9fcc164ad2fb6f72d0a24268f68" },
+ { 168, "c95b441d8270822a46a798fae5defcf7b26abace36",
+ "a287ea752a593d5209e287881a09c49fa3f0beb1" },
+ { 176, "83104c1d8a55b28f906f1b72cb53f68cbb097b44f860",
+ "a06c713779cbd88519ed4a585ac0cb8a5e9d612b" },
+ { 184, "755175528d55c39c56493d697b790f099a5ce741f7754b",
+ "bff7d52c13a3688132a1d407b1ab40f5b5ace298" },
+ { 192, "088fc38128bbdb9fd7d65228b3184b3faac6c8715f07272f",
+ "c7566b91d7b6f56bdfcaa9781a7b6841aacb17e9" },
+ { 200, "a4a586eb9245a6c87e3adf1009ac8a49f46c07e14185016895",
+ "ffa30c0b5c550ea4b1e34f8a60ec9295a1e06ac1" },
+ { 208, "8e7c555270c006092c2a3189e2a526b873e2e269f0fb28245256",
+ "29e66ed23e914351e872aa761df6e4f1a07f4b81" },
+ { 216, "a5f3bfa6bb0ba3b59f6b9cbdef8a558ec565e8aa3121f405e7f2f0",
+ "b28cf5e5b806a01491d41f69bd9248765c5dc292" },
+ { 224, "589054f0d2bd3c2c85b466bfd8ce18e6ec3e0b87d944cd093ba36469",
+ "60224fb72c46069652cd78bcd08029ef64da62f3" },
+ { 232, "a0abb12083b5bbc78128601bf1cbdbc0fdf4b862b24d899953d8da0ff3",
+ "b72c4a86f72608f24c05f3b9088ef92fba431df7" },
+ { 240, "82143f4cea6fadbf998e128a8811dc75301cf1db4f079501ea568da68eeb",
+ "73779ad5d6b71b9b8328ef7220ff12eb167076ac" },
+ { 248, "9f1231dd6df1ff7bc0b0d4f989d048672683ce35d956d2f57913046267e6f3",
+ "a09671d4452d7cf50015c914a1e31973d20cc1a0" },
+ { 256,
+ "041c512b5eed791f80d3282f3a28df263bb1df95e1239a7650e5670fc2187919",
+ "e88cdcd233d99184a6fd260b8fca1b7f7687aee0" },
+ { 264,
+ "17e81f6ae8c2e5579d69dafa6e070e7111461552d314b691e7a3e7a4feb3fae418",
+ "010def22850deb1168d525e8c84c28116cb8a269" },
+ { 272, "d15976b23a1d712ad28fad04d805f572026b54dd64961fda94d5355a0cc9862"
+ "0cf77",
+ "aeaa40ba1717ed5439b1e6ea901b294ba500f9ad" },
+ { 280, "09fce4d434f6bd32a44e04b848ff50ec9f642a8a85b37a264dc73f130f22838"
+ "443328f",
+ "c6433791238795e34f080a5f1f1723f065463ca0" },
+ { 288, "f17af27d776ec82a257d8d46d2b46b639462c56984cc1be9c1222eadb8b2659"
+ "4a25c709d",
+ "e21e22b89c1bb944a32932e6b2a2f20d491982c3" },
+ { 296, "b13ce635d6f8758143ffb114f2f601cb20b6276951416a2f94fbf4ad081779d"
+ "79f4f195b22",
+ "575323a9661f5d28387964d2ba6ab92c17d05a8a" },
+ { 304, "5498793f60916ff1c918dde572cdea76da8629ba4ead6d065de3dfb48de94d2"
+ "34cc1c5002910",
+ "feb44494af72f245bfe68e86c4d7986d57c11db7" },
+ { 312, "498a1e0b39fa49582ae688cd715c86fbaf8a81b8b11b4d1594c49c902d197c8"
+ "ba8a621fd6e3be5",
+ "cff2290b3648ba2831b98dde436a72f9ebf51eee" },
+ { 320, "3a36ae71521f9af628b3e34dcb0d4513f84c78ee49f10416a98857150b8b15c"
+ "b5c83afb4b570376e",
+ "9b4efe9d27b965905b0c3dab67b8d7c9ebacd56c" },
+ { 328, "dcc76b40ae0ea3ba253e92ac50fcde791662c5b6c948538cffc2d95e9de99ca"
+ "c34dfca38910db2678f",
+ "afedb0ff156205bcd831cbdbda43db8b0588c113" },
+ { 336, "5b5ec6ec4fd3ad9c4906f65c747fd4233c11a1736b6b228b92e90cddabb0c7c"
+ "2fcf9716d3fad261dff33",
+ "8deb1e858f88293a5e5e4d521a34b2a4efa70fc4" },
+ { 344, "df48a37b29b1d6de4e94717d60cdb4293fcf170bba388bddf7a9035a15d433f"
+ "20fd697c3e4c8b8c5f590ab",
+ "95cbdac0f74afa69cebd0e5c7defbc6faf0cbeaf" },
+ { 352, "1f179b3b82250a65e1b0aee949e218e2f45c7a8dbfd6ba08de05c55acfc226b"
+ "48c68d7f7057e5675cd96fcfc",
+ "f0307bcb92842e5ae0cd4f4f14f3df7f877fbef2" },
+ { 360, "ee3d72da3a44d971578972a8e6780ce64941267e0f7d0179b214fa97855e179"
+ "0e888e09fbe3a70412176cb3b54",
+ "7b13bb0dbf14964bd63b133ac85e22100542ef55" },
+ { 368, "d4d4c7843d312b30f610b3682254c8be96d5f6684503f8fbfbcd15774fc1b08"
+ "4d3741afb8d24aaa8ab9c104f7258",
+ "c314d2b6cf439be678d2a74e890d96cfac1c02ed" },
+ { 376, "32c094944f5936a190a0877fb9178a7bf60ceae36fd530671c5b38c5dbd5e6a"
+ "6c0d615c2ac8ad04b213cc589541cf6",
+ "4d0be361e410b47a9d67d8ce0bb6a8e01c53c078" },
+ { 384, "e5d3180c14bf27a5409fa12b104a8fd7e9639609bfde6ee82bbf9648be2546d"
+ "29688a65e2e3f3da47a45ac14343c9c02",
+ "e5353431ffae097f675cbf498869f6fbb6e1c9f2" },
+ { 392, "e7b6e4b69f724327e41e1188a37f4fe38b1dba19cbf5a7311d6e32f1038e97a"
+ "b506ee05aebebc1eed09fc0e357109818b9",
+ "b8720a7068a085c018ab18961de2765aa6cd9ac4" },
+ { 400, "bc880cb83b8ac68ef2fedc2da95e7677ce2aa18b0e2d8b322701f67af7d5e7a"
+ "0d96e9e33326ccb7747cfff0852b961bfd475",
+ "b0732181568543ba85f2b6da602b4b065d9931aa" },
+ { 408, "235ea9c2ba7af25400f2e98a47a291b0bccdaad63faa2475721fda5510cc7da"
+ "d814bce8dabb611790a6abe56030b798b75c944",
+ "9c22674cf3222c3ba921672694aafee4ce67b96b" },
+ { 416, "07e3e29fed63104b8410f323b975fd9fba53f636af8c4e68a53fb202ca35dd9"
+ "ee07cb169ec5186292e44c27e5696a967f5e67709",
+ "d128335f4cecca9066cdae08958ce656ff0b4cfc" },
+ { 424, "65d2a1dd60a517eb27bfbf530cf6a5458f9d5f4730058bd9814379547f34241"
+ "822bf67e6335a6d8b5ed06abf8841884c636a25733f",
+ "0b67c57ac578de88a2ae055caeaec8bb9b0085a0" },
+ { 432, "dcc86b3bd461615bab739d8daafac231c0f462e819ad29f9f14058f3ab5b759"
+ "41d4241ea2f17ebb8a458831b37a9b16dead4a76a9b0e",
+ "c766f912a89d4ccda88e0cce6a713ef5f178b596" },
+ { 440, "4627d54f0568dc126b62a8c35fb46a9ac5024400f2995e51635636e1afc4373"
+ "dbb848eb32df23914230560b82477e9c3572647a7f2bb92",
+ "9aa3925a9dcb177b15ccff9b78e70cf344858779" },
+ { 448, "ba531affd4381168ef24d8b275a84d9254c7f5cc55fded53aa8024b2c5c5c8a"
+ "a7146fe1d1b83d62b70467e9a2e2cb67b3361830adbab28d7",
+ "4811fa30042fc076acf37c8e2274d025307e5943" },
+ { 456, "8764dcbcf89dcf4282eb644e3d568bdccb4b13508bfa7bfe0ffc05efd1390be"
+ "22109969262992d377691eb4f77f3d59ea8466a74abf57b2ef4",
+ "6743018450c9730761ee2b130df9b91c1e118150" },
+ { 464, "497d9df9ddb554f3d17870b1a31986c1be277bc44feff713544217a9f579623"
+ "d18b5ffae306c25a45521d2759a72c0459b58957255ab592f3be4",
+ "71ad4a19d37d92a5e6ef3694ddbeb5aa61ada645" },
+ { 472, "72c3c2e065aefa8d9f7a65229e818176eef05da83f835107ba90ec2e95472e7"
+ "3e538f783b416c04654ba8909f26a12db6e5c4e376b7615e4a25819",
+ "a7d9dc68dacefb7d6116186048cb355cc548e11d" },
+ { 480, "7cc9894454d0055ab5069a33984e2f712bef7e3124960d33559f5f3b81906bb"
+ "66fe64da13c153ca7f5cabc89667314c32c01036d12ecaf5f9a78de98",
+ "142e429f0522ba5abf5131fa81df82d355b96909" },
+ { 488, "74e8404d5a453c5f4d306f2cfa338ca65501c840ddab3fb82117933483afd69"
+ "13c56aaf8a0a0a6b2a342fc3d9dc7599f4a850dfa15d06c61966d74ea59",
+ "ef72db70dcbcab991e9637976c6faf00d22caae9" },
+ { 496, "46fe5ed326c8fe376fcc92dc9e2714e2240d3253b105adfbb256ff7a19bc409"
+ "75c604ad7c0071c4fd78a7cb64786e1bece548fa4833c04065fe593f6fb10",
+ "f220a7457f4588d639dc21407c942e9843f8e26b" },
+ { 504, "836dfa2524d621cf07c3d2908835de859e549d35030433c796b81272fd8bc03"
+ "48e8ddbc7705a5ad1fdf2155b6bc48884ac0cd376925f069a37849c089c864"
+ "5",
+ "ddd2117b6e309c233ede85f962a0c2fc215e5c69" },
+ { 512, "7e3a4c325cb9c52b88387f93d01ae86d42098f5efa7f9457388b5e74b6d28b2"
+ "438d42d8b64703324d4aa25ab6aad153ae30cd2b2af4d5e5c00a8a2d0220c61"
+ "16",
+ "a3054427cdb13f164a610b348702724c808a0dcc" }
+ };
+
+ for (i = 0; i < 65; i++) {
+ err = hash_test_case_add(&sha1_test_case_list, tc[i].hex_data,
+ tc[i].bit_len / 8, tc[i].hex_hash, 20);
+ if (err) {
+ printf("error adding hash test case (code %d)\n", err);
+ return err;
+ }
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t sha1_dealloc_test_cases(void)
+{
+ hash_test_case_t *t, *next;
+
+ for (t = sha1_test_case_list; t != NULL; t = next) {
+ next = t->next_test_case;
+ free(t);
+ }
+
+ sha1_test_case_list = NULL;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t sha1_validate(void)
+{
+ hash_test_case_t *test_case;
+ srtp_err_status_t err;
+
+ err = sha1_add_test_cases();
+ if (err) {
+ printf("error adding SHA1 test cases (error code %d)\n", err);
+ return err;
+ }
+
+ if (sha1_test_case_list == NULL)
+ return srtp_err_status_cant_check;
+
+ test_case = sha1_test_case_list;
+ while (test_case != NULL) {
+ err = sha1_test_case_validate(test_case);
+ if (err) {
+ printf("error validating hash test case (error code %d)\n", err);
+ return err;
+ }
+ test_case = test_case->next_test_case;
+ }
+
+ sha1_dealloc_test_cases();
+
+ return srtp_err_status_ok;
+}
+
+int main(void)
+{
+ srtp_err_status_t err;
+
+ printf("sha1 test driver\n");
+
+ err = sha1_validate();
+ if (err) {
+ printf("SHA1 did not pass validation testing\n");
+ return 1;
+ }
+ printf("SHA1 passed validation tests\n");
+
+ return 0;
+}
diff --git a/netwerk/srtp/src/crypto/test/stat_driver.c b/netwerk/srtp/src/crypto/test/stat_driver.c
new file mode 100644
index 0000000000..7f835855ce
--- /dev/null
+++ b/netwerk/srtp/src/crypto/test/stat_driver.c
@@ -0,0 +1,258 @@
+/*
+ * stat-driver.c
+ *
+ * test driver for the stat_test functions
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h> /* for printf() */
+
+#include "err.h"
+#include "stat.h"
+#include "srtp.h"
+
+#include "cipher.h"
+
+typedef struct {
+ void *state;
+} random_source_t;
+
+srtp_err_status_t random_source_alloc(void);
+
+void err_check(srtp_err_status_t s)
+{
+ if (s) {
+ printf("error (code %d)\n", s);
+ exit(1);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ uint8_t buffer[2532];
+ unsigned int buf_len = 2500;
+ int i, j;
+ extern srtp_cipher_type_t srtp_aes_icm_128;
+ extern srtp_cipher_type_t srtp_aes_icm_256;
+#ifdef GCM
+ extern srtp_cipher_type_t srtp_aes_gcm_128;
+ extern srtp_cipher_type_t srtp_aes_gcm_256;
+#endif
+ srtp_cipher_t *c;
+ /* clang-format off */
+ uint8_t key[46] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05
+ };
+ /* clang-format on */
+ v128_t nonce;
+ int num_trials = 500;
+ int num_fail;
+
+ printf("statistical tests driver\n");
+
+ v128_set_to_zero(&nonce);
+ for (i = 0; i < 2500; i++)
+ buffer[i] = 0;
+
+ /* run tests */
+ printf("running stat_tests on all-null buffer, expecting failure\n");
+ printf("monobit %d\n", stat_test_monobit(buffer));
+ printf("poker %d\n", stat_test_poker(buffer));
+ printf("runs %d\n", stat_test_runs(buffer));
+
+ for (i = 0; i < 2500; i++)
+ buffer[i] = rand();
+ printf("running stat_tests on rand(), expecting success\n");
+ printf("monobit %d\n", stat_test_monobit(buffer));
+ printf("poker %d\n", stat_test_poker(buffer));
+ printf("runs %d\n", stat_test_runs(buffer));
+
+ printf("running stat_tests on AES-128-ICM, expecting success\n");
+ /* set buffer to cipher output */
+ for (i = 0; i < 2500; i++)
+ buffer[i] = 0;
+ err_check(srtp_cipher_type_alloc(&srtp_aes_icm_128, &c,
+ SRTP_AES_ICM_128_KEY_LEN_WSALT, 0));
+ err_check(srtp_cipher_init(c, key));
+ err_check(srtp_cipher_set_iv(c, (uint8_t *)&nonce, srtp_direction_encrypt));
+ err_check(srtp_cipher_encrypt(c, buffer, &buf_len));
+ /* run tests on cipher outout */
+ printf("monobit %d\n", stat_test_monobit(buffer));
+ printf("poker %d\n", stat_test_poker(buffer));
+ printf("runs %d\n", stat_test_runs(buffer));
+
+ printf("runs test (please be patient): ");
+ fflush(stdout);
+ num_fail = 0;
+ v128_set_to_zero(&nonce);
+ for (j = 0; j < num_trials; j++) {
+ for (i = 0; i < 2500; i++)
+ buffer[i] = 0;
+ nonce.v32[3] = i;
+ err_check(
+ srtp_cipher_set_iv(c, (uint8_t *)&nonce, srtp_direction_encrypt));
+ err_check(srtp_cipher_encrypt(c, buffer, &buf_len));
+ if (stat_test_runs(buffer)) {
+ num_fail++;
+ }
+ }
+
+ printf("%d failures in %d tests\n", num_fail, num_trials);
+ printf("(nota bene: a small fraction of stat_test failures does not \n"
+ "indicate that the random source is invalid)\n");
+
+ err_check(srtp_cipher_dealloc(c));
+
+ printf("running stat_tests on AES-256-ICM, expecting success\n");
+ /* set buffer to cipher output */
+ for (i = 0; i < 2500; i++)
+ buffer[i] = 0;
+ err_check(srtp_cipher_type_alloc(&srtp_aes_icm_256, &c,
+ SRTP_AES_ICM_256_KEY_LEN_WSALT, 0));
+ err_check(srtp_cipher_init(c, key));
+ err_check(srtp_cipher_set_iv(c, (uint8_t *)&nonce, srtp_direction_encrypt));
+ err_check(srtp_cipher_encrypt(c, buffer, &buf_len));
+ /* run tests on cipher outout */
+ printf("monobit %d\n", stat_test_monobit(buffer));
+ printf("poker %d\n", stat_test_poker(buffer));
+ printf("runs %d\n", stat_test_runs(buffer));
+
+ printf("runs test (please be patient): ");
+ fflush(stdout);
+ num_fail = 0;
+ v128_set_to_zero(&nonce);
+ for (j = 0; j < num_trials; j++) {
+ for (i = 0; i < 2500; i++)
+ buffer[i] = 0;
+ nonce.v32[3] = i;
+ err_check(
+ srtp_cipher_set_iv(c, (uint8_t *)&nonce, srtp_direction_encrypt));
+ err_check(srtp_cipher_encrypt(c, buffer, &buf_len));
+ if (stat_test_runs(buffer)) {
+ num_fail++;
+ }
+ }
+
+#ifdef GCM
+ {
+ printf("running stat_tests on AES-128-GCM, expecting success\n");
+ /* set buffer to cipher output */
+ for (i = 0; i < 2500; i++) {
+ buffer[i] = 0;
+ }
+ err_check(srtp_cipher_type_alloc(&srtp_aes_gcm_128, &c,
+ SRTP_AES_GCM_128_KEY_LEN_WSALT, 8));
+ err_check(srtp_cipher_init(c, key));
+ err_check(
+ srtp_cipher_set_iv(c, (uint8_t *)&nonce, srtp_direction_encrypt));
+ err_check(srtp_cipher_encrypt(c, buffer, &buf_len));
+ /* run tests on cipher outout */
+ printf("monobit %d\n", stat_test_monobit(buffer));
+ printf("poker %d\n", stat_test_poker(buffer));
+ printf("runs %d\n", stat_test_runs(buffer));
+ fflush(stdout);
+ num_fail = 0;
+ v128_set_to_zero(&nonce);
+ for (j = 0; j < num_trials; j++) {
+ for (i = 0; i < 2500; i++) {
+ buffer[i] = 0;
+ }
+ nonce.v32[3] = i;
+ err_check(srtp_cipher_set_iv(c, (uint8_t *)&nonce,
+ srtp_direction_encrypt));
+ err_check(srtp_cipher_encrypt(c, buffer, &buf_len));
+ buf_len = 2500;
+ if (stat_test_runs(buffer)) {
+ num_fail++;
+ }
+ }
+
+ printf("running stat_tests on AES-256-GCM, expecting success\n");
+ /* set buffer to cipher output */
+ for (i = 0; i < 2500; i++) {
+ buffer[i] = 0;
+ }
+ err_check(srtp_cipher_type_alloc(&srtp_aes_gcm_256, &c,
+ SRTP_AES_GCM_256_KEY_LEN_WSALT, 16));
+ err_check(srtp_cipher_init(c, key));
+ err_check(
+ srtp_cipher_set_iv(c, (uint8_t *)&nonce, srtp_direction_encrypt));
+ err_check(srtp_cipher_encrypt(c, buffer, &buf_len));
+ /* run tests on cipher outout */
+ printf("monobit %d\n", stat_test_monobit(buffer));
+ printf("poker %d\n", stat_test_poker(buffer));
+ printf("runs %d\n", stat_test_runs(buffer));
+ fflush(stdout);
+ num_fail = 0;
+ v128_set_to_zero(&nonce);
+ for (j = 0; j < num_trials; j++) {
+ for (i = 0; i < 2500; i++) {
+ buffer[i] = 0;
+ }
+ nonce.v32[3] = i;
+ err_check(srtp_cipher_set_iv(c, (uint8_t *)&nonce,
+ srtp_direction_encrypt));
+ err_check(srtp_cipher_encrypt(c, buffer, &buf_len));
+ buf_len = 2500;
+ if (stat_test_runs(buffer)) {
+ num_fail++;
+ }
+ }
+ }
+#endif
+
+ printf("%d failures in %d tests\n", num_fail, num_trials);
+ printf("(nota bene: a small fraction of stat_test failures does not \n"
+ "indicate that the random source is invalid)\n");
+
+ err_check(srtp_cipher_dealloc(c));
+
+ return 0;
+}
diff --git a/netwerk/srtp/src/include/config.h b/netwerk/srtp/src/include/config.h
new file mode 100644
index 0000000000..75e05e897f
--- /dev/null
+++ b/netwerk/srtp/src/include/config.h
@@ -0,0 +1,6 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* empty file; config done via Makefile.in & configure.in */
diff --git a/netwerk/srtp/src/include/ekt.h b/netwerk/srtp/src/include/ekt.h
new file mode 100644
index 0000000000..a289a53dab
--- /dev/null
+++ b/netwerk/srtp/src/include/ekt.h
@@ -0,0 +1,181 @@
+/*
+ * ekt.h
+ *
+ * interface to Encrypted Key Transport for SRTP
+ *
+ * David McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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.
+ *
+ */
+
+/*
+ * EKT implementation strategy
+ *
+ * use stream_template approach
+ *
+ * in srtp_unprotect, when a new stream appears, check if template has
+ * EKT defined, and if it does, then apply EKT processing
+ *
+ * question: will we want to allow key-sharing templates in addition
+ * to EKT templates? could define a new ssrc_type_t that's associated
+ * with an EKT, e.g. ssrc_any_ekt.
+ *
+ *
+ */
+
+#ifndef SRTP_EKT_H
+#define SRTP_EKT_H
+
+// left in commented out as reminder to not include private headers
+//#include "srtp_priv.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SRTP_EKT_CIPHER_DEFAULT 1
+#define SRTP_EKT_CIPHER_AES_128_ECB 1
+#define SRTP_EKT_CIPHER_AES_192_KEY_WRAP 2
+#define SRTP_EKT_CIPHER_AES_256_KEY_WRAP 3
+
+typedef uint16_t srtp_ekt_spi_t;
+
+unsigned srtp_ekt_octets_after_base_tag(srtp_ekt_stream_t ekt);
+
+/*
+ * an srtp_policy_t structure can contain a pointer to an
+ * srtp_ekt_policy_t structure
+ *
+ * this structure holds all of the high level EKT information, and it
+ * is passed into libsrtp to indicate what policy should be in effect
+ */
+
+typedef struct srtp_ekt_policy_ctx_t {
+ srtp_ekt_spi_t spi; /* security parameter index */
+ uint8_t ekt_cipher_type;
+ uint8_t *ekt_key;
+ struct srtp_ekt_policy_ctx_t *next_ekt_policy;
+} srtp_ekt_policy_ctx_t;
+
+/*
+ * an srtp_ekt_data_t structure holds the data corresponding to an ekt key,
+ * spi, and so on
+ */
+
+typedef struct srtp_ekt_data_t {
+ srtp_ekt_spi_t spi;
+ uint8_t ekt_cipher_type;
+ srtp_aes_expanded_key_t ekt_enc_key;
+ srtp_aes_expanded_key_t ekt_dec_key;
+ struct ekt_data_t *next_ekt_data;
+} srtp_ekt_data_t;
+
+/*
+ * an srtp_stream_ctx_t can contain an srtp_ekt_stream_ctx_t
+ *
+ * an srtp_ekt_stream_ctx_t structure holds all of the EKT information for
+ * a specific SRTP stream
+ */
+
+typedef struct srtp_ekt_stream_ctx_t {
+ srtp_ekt_data_t *data;
+ uint16_t isn; /* initial sequence number */
+ uint8_t encrypted_master_key[SRTP_MAX_KEY_LEN];
+} srtp_ekt_stream_ctx_t;
+
+srtp_err_status_t srtp_ekt_alloc(srtp_ekt_stream_t *stream_data,
+ srtp_ekt_policy_t policy);
+
+srtp_err_status_t srtp_ekt_stream_init(srtp_ekt_stream_t e,
+ srtp_ekt_spi_t spi,
+ void *ekt_key,
+ unsigned ekt_cipher_type);
+
+srtp_err_status_t srtp_ekt_stream_init_from_policy(srtp_ekt_stream_t e,
+ srtp_ekt_policy_t p);
+
+srtp_err_status_t srtp_stream_init_from_ekt(srtp_stream_t stream,
+ const void *srtcp_hdr,
+ unsigned pkt_octet_len);
+
+void srtp_ekt_write_data(srtp_ekt_stream_t ekt,
+ uint8_t *base_tag,
+ unsigned base_tag_len,
+ int *packet_len,
+ srtp_xtd_seq_num_t pkt_index);
+
+/*
+ * We handle EKT by performing some additional steps before
+ * authentication (copying the auth tag into a temporary location,
+ * zeroizing the "base tag" field in the packet)
+ *
+ * With EKT, the tag_len parameter is actually the base tag
+ * length
+ */
+srtp_err_status_t srtp_ekt_tag_verification_preproces(uint8_t *pkt_tag,
+ uint8_t *pkt_tag_copy,
+ unsigned tag_len);
+
+srtp_err_status_t srtp_ekt_tag_verification_postproces(uint8_t *pkt_tag,
+ uint8_t *pkt_tag_copy,
+ unsigned tag_len);
+
+/*
+ * @brief EKT pre-processing for srtcp tag generation
+ *
+ * This function does the pre-processing of the SRTCP authentication
+ * tag format. When EKT is used, it consists of writing the Encrypted
+ * Master Key, the SRTP ROC, the Initial Sequence Number, and SPI
+ * fields. The Base Authentication Tag field is set to the all-zero
+ * value
+ *
+ * When EKT is not used, this function is a no-op.
+ *
+ */
+srtp_err_status_t srtp_stream_srtcp_auth_tag_generation_preprocess(
+ const srtp_stream_t *s,
+ uint8_t *pkt_tag,
+ unsigned pkt_octet_len);
+
+/* it's not clear that a tag_generation_postprocess function is needed */
+srtp_err_status_t srtcp_auth_tag_generation_postprocess(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SRTP_EKT_H */
diff --git a/netwerk/srtp/src/include/getopt_s.h b/netwerk/srtp/src/include/getopt_s.h
new file mode 100644
index 0000000000..53fb25ba71
--- /dev/null
+++ b/netwerk/srtp/src/include/getopt_s.h
@@ -0,0 +1,67 @@
+/*
+ * getopt.h
+ *
+ * interface to a minimal implementation of the getopt() function,
+ * written so that test applications that use that function can run on
+ * non-POSIX platforms
+ *
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 GETOPT_S_H
+#define GETOPT_S_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * getopt_s(), optarg_s, and optind_s are small, locally defined
+ * versions of the POSIX standard getopt() interface.
+ */
+
+int getopt_s(int argc, char *const argv[], const char *optstring);
+
+extern char *optarg_s; /* defined in getopt.c */
+
+extern int optind_s; /* defined in getopt.c */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* GETOPT_S_H */
diff --git a/netwerk/srtp/src/include/srtp.h b/netwerk/srtp/src/include/srtp.h
new file mode 100644
index 0000000000..1fdd6c3aff
--- /dev/null
+++ b/netwerk/srtp/src/include/srtp.h
@@ -0,0 +1,1759 @@
+/*
+ * srtp.h
+ *
+ * interface to libsrtp
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 SRTP_SRTP_H
+#define SRTP_SRTP_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @defgroup SRTP Secure RTP
+ *
+ * @brief libSRTP provides functions for protecting RTP and RTCP. See
+ * Section @ref Overview for an introduction to the use of the library.
+ *
+ * @{
+ */
+
+/*
+ * SRTP_MASTER_KEY_LEN is the nominal master key length supported by libSRTP
+ */
+
+#define SRTP_MASTER_KEY_LEN 30
+
+/*
+ * SRTP_MAX_KEY_LEN is the maximum key length supported by libSRTP
+ */
+#define SRTP_MAX_KEY_LEN 64
+
+/*
+ * SRTP_MAX_TAG_LEN is the maximum tag length supported by libSRTP
+ */
+
+#define SRTP_MAX_TAG_LEN 16
+
+/**
+ * SRTP_MAX_MKI_LEN is the maximum size the MKI could be which is
+ * 128 bytes
+ */
+#define SRTP_MAX_MKI_LEN 128
+
+/**
+ * SRTP_MAX_TRAILER_LEN is the maximum length of the SRTP trailer
+ * (authentication tag and MKI) supported by libSRTP. This value is
+ * the maixmum number of octets that will be added to an RTP packet by
+ * srtp_protect().
+ *
+ * @brief the maximum number of octets added by srtp_protect().
+ */
+#define SRTP_MAX_TRAILER_LEN (SRTP_MAX_TAG_LEN + SRTP_MAX_MKI_LEN)
+
+/**
+ * SRTP_MAX_NUM_MASTER_KEYS is the maximum number of Master keys for
+ * MKI supported by libSRTP.
+ *
+ */
+#define SRTP_MAX_NUM_MASTER_KEYS 16
+
+#define SRTP_SALT_LEN 14
+
+/*
+ * SRTP_AEAD_SALT_LEN is the length of the SALT values used with
+ * GCM mode. GCM mode requires an IV. The SALT value is used
+ * as part of the IV formation logic applied to each RTP packet.
+ */
+#define SRTP_AEAD_SALT_LEN 12
+
+#define SRTP_AES_128_KEY_LEN 16
+#define SRTP_AES_192_KEY_LEN 24
+#define SRTP_AES_256_KEY_LEN 32
+
+#define SRTP_AES_ICM_128_KEY_LEN_WSALT (SRTP_SALT_LEN + SRTP_AES_128_KEY_LEN)
+#define SRTP_AES_ICM_192_KEY_LEN_WSALT (SRTP_SALT_LEN + SRTP_AES_192_KEY_LEN)
+#define SRTP_AES_ICM_256_KEY_LEN_WSALT (SRTP_SALT_LEN + SRTP_AES_256_KEY_LEN)
+
+#define SRTP_AES_GCM_128_KEY_LEN_WSALT \
+ (SRTP_AEAD_SALT_LEN + SRTP_AES_128_KEY_LEN)
+#define SRTP_AES_GCM_192_KEY_LEN_WSALT \
+ (SRTP_AEAD_SALT_LEN + SRTP_AES_192_KEY_LEN)
+#define SRTP_AES_GCM_256_KEY_LEN_WSALT \
+ (SRTP_AEAD_SALT_LEN + SRTP_AES_256_KEY_LEN)
+
+/**
+ * @brief A srtp_cipher_type_id_t is an identifier for a particular cipher
+ * type.
+ *
+ * A srtp_cipher_type_id_t is an integer that represents a particular
+ * cipher type, e.g. the Advanced Encryption Standard (AES). A
+ * SRTP_NULL_CIPHER is avaliable; this cipher leaves the data unchanged,
+ * and can be selected to indicate that no encryption is to take
+ * place.
+ *
+ * @ingroup Ciphers
+ */
+typedef uint32_t srtp_cipher_type_id_t;
+
+/**
+ * @brief An srtp_auth_type_id_t is an identifier for a particular
+ * authentication
+ * function.
+ *
+ * An srtp_auth_type_id_t is an integer that represents a particular
+ * authentication function type, e.g. HMAC-SHA1. A SRTP_NULL_AUTH is
+ * avaliable; this authentication function performs no computation,
+ * and can be selected to indicate that no authentication is to take
+ * place.
+ *
+ * @ingroup Authentication
+ */
+typedef uint32_t srtp_auth_type_id_t;
+
+/**
+ * @brief srtp_err_status_t defines error codes.
+ *
+ * The enumeration srtp_err_status_t defines error codes. Note that the
+ * value of srtp_err_status_ok is equal to zero, which can simplify error
+ * checking somewhat.
+ *
+ */
+typedef enum {
+ srtp_err_status_ok = 0, /**< nothing to report */
+ srtp_err_status_fail = 1, /**< unspecified failure */
+ srtp_err_status_bad_param = 2, /**< unsupported parameter */
+ srtp_err_status_alloc_fail = 3, /**< couldn't allocate memory */
+ srtp_err_status_dealloc_fail = 4, /**< couldn't deallocate properly */
+ srtp_err_status_init_fail = 5, /**< couldn't initialize */
+ srtp_err_status_terminus = 6, /**< can't process as much data as */
+ /**< requested */
+ srtp_err_status_auth_fail = 7, /**< authentication failure */
+ srtp_err_status_cipher_fail = 8, /**< cipher failure */
+ srtp_err_status_replay_fail = 9, /**< replay check failed (bad index) */
+ srtp_err_status_replay_old = 10, /**< replay check failed (index too */
+ /**< old) */
+ srtp_err_status_algo_fail = 11, /**< algorithm failed test routine */
+ srtp_err_status_no_such_op = 12, /**< unsupported operation */
+ srtp_err_status_no_ctx = 13, /**< no appropriate context found */
+ srtp_err_status_cant_check = 14, /**< unable to perform desired */
+ /**< validation */
+ srtp_err_status_key_expired = 15, /**< can't use key any more */
+ srtp_err_status_socket_err = 16, /**< error in use of socket */
+ srtp_err_status_signal_err = 17, /**< error in use POSIX signals */
+ srtp_err_status_nonce_bad = 18, /**< nonce check failed */
+ srtp_err_status_read_fail = 19, /**< couldn't read data */
+ srtp_err_status_write_fail = 20, /**< couldn't write data */
+ srtp_err_status_parse_err = 21, /**< error parsing data */
+ srtp_err_status_encode_err = 22, /**< error encoding data */
+ srtp_err_status_semaphore_err = 23, /**< error while using semaphores */
+ srtp_err_status_pfkey_err = 24, /**< error while using pfkey */
+ srtp_err_status_bad_mki = 25, /**< error MKI present in packet is */
+ /**< invalid */
+ srtp_err_status_pkt_idx_old = 26, /**< packet index is too old to */
+ /**< consider */
+ srtp_err_status_pkt_idx_adv = 27 /**< packet index advanced, reset */
+ /**< needed */
+} srtp_err_status_t;
+
+typedef struct srtp_ctx_t_ srtp_ctx_t;
+
+/**
+ * @brief srtp_sec_serv_t describes a set of security services.
+ *
+ * A srtp_sec_serv_t enumeration is used to describe the particular
+ * security services that will be applied by a particular crypto
+ * policy (or other mechanism).
+ */
+typedef enum {
+ sec_serv_none = 0, /**< no services */
+ sec_serv_conf = 1, /**< confidentiality */
+ sec_serv_auth = 2, /**< authentication */
+ sec_serv_conf_and_auth = 3 /**< confidentiality and authentication */
+} srtp_sec_serv_t;
+
+/**
+ * @brief srtp_crypto_policy_t describes a particular crypto policy that
+ * can be applied to an SRTP stream.
+ *
+ * A srtp_crypto_policy_t describes a particular cryptographic policy that
+ * can be applied to an SRTP or SRTCP stream. An SRTP session policy
+ * consists of a list of these policies, one for each SRTP stream
+ * in the session.
+ */
+typedef struct srtp_crypto_policy_t {
+ srtp_cipher_type_id_t cipher_type; /**< An integer representing */
+ /**< the type of cipher. */
+ int cipher_key_len; /**< The length of the cipher key */
+ /**< in octets. */
+ srtp_auth_type_id_t auth_type; /**< An integer representing the */
+ /**< authentication function. */
+ int auth_key_len; /**< The length of the authentication */
+ /**< function key in octets. */
+ int auth_tag_len; /**< The length of the authentication */
+ /**< tag in octets. */
+ srtp_sec_serv_t sec_serv; /**< The flag indicating the security */
+ /**< services to be applied. */
+} srtp_crypto_policy_t;
+
+/**
+ * @brief srtp_ssrc_type_t describes the type of an SSRC.
+ *
+ * An srtp_ssrc_type_t enumeration is used to indicate a type of SSRC. See
+ * @ref srtp_policy_t for more informataion.
+ */
+typedef enum {
+ ssrc_undefined = 0, /**< Indicates an undefined SSRC type. */
+ ssrc_specific = 1, /**< Indicates a specific SSRC value */
+ ssrc_any_inbound = 2, /**< Indicates any inbound SSRC value */
+ /**< (i.e. a value that is used in the */
+ /**< function srtp_unprotect()) */
+ ssrc_any_outbound = 3 /**< Indicates any outbound SSRC value */
+ /**< (i.e. a value that is used in the */
+ /**< function srtp_protect()) */
+} srtp_ssrc_type_t;
+
+/**
+ * @brief An srtp_ssrc_t represents a particular SSRC value, or a `wildcard'
+ * SSRC.
+ *
+ * An srtp_ssrc_t represents a particular SSRC value (if its type is
+ * ssrc_specific), or a wildcard SSRC value that will match all
+ * outbound SSRCs (if its type is ssrc_any_outbound) or all inbound
+ * SSRCs (if its type is ssrc_any_inbound).
+ */
+typedef struct {
+ srtp_ssrc_type_t type; /**< The type of this particular SSRC */
+ unsigned int value; /**< The value of this SSRC, if it is not a */
+ /**< wildcard */
+} srtp_ssrc_t;
+
+/**
+ * @brief points to an EKT policy
+ */
+typedef struct srtp_ekt_policy_ctx_t *srtp_ekt_policy_t;
+
+/**
+ * @brief points to EKT stream data
+ */
+typedef struct srtp_ekt_stream_ctx_t *srtp_ekt_stream_t;
+
+/**
+ * @brief srtp_master_key_t represents a master key. There will
+ * be a Master Key Index and the Master Key associated with the
+ * Master Key Index. Need to also keep track of the Master Key
+ * Index Size to correctly read it from a packet.
+ */
+typedef struct srtp_master_key_t {
+ unsigned char *key;
+ unsigned char *mki_id;
+ unsigned int mki_size;
+} srtp_master_key_t;
+
+/**
+ * @brief represents the policy for an SRTP session.
+ *
+ * A single srtp_policy_t struct represents the policy for a single
+ * SRTP stream, and a linked list of these elements represents the
+ * policy for an entire SRTP session. Each element contains the SRTP
+ * and SRTCP crypto policies for that stream, a pointer to the SRTP
+ * master key for that stream, the SSRC describing that stream, or a
+ * flag indicating a `wildcard' SSRC value, and a `next' field that
+ * holds a pointer to the next element in the list of policy elements,
+ * or NULL if it is the last element.
+ *
+ * The wildcard value SSRC_ANY_INBOUND matches any SSRC from an
+ * inbound stream that for which there is no explicit SSRC entry in
+ * another policy element. Similarly, the value SSRC_ANY_OUTBOUND
+ * will matches any SSRC from an outbound stream that does not appear
+ * in another policy element. Note that wildcard SSRCs &b cannot be
+ * used to match both inbound and outbound traffic. This restriction
+ * is intentional, and it allows libSRTP to ensure that no security
+ * lapses result from accidental re-use of SSRC values during key
+ * sharing.
+ *
+ * @warning The final element of the list @b must have its `next' pointer
+ * set to NULL.
+ */
+
+typedef struct srtp_policy_t {
+ srtp_ssrc_t ssrc; /**< The SSRC value of stream, or the */
+ /**< flags SSRC_ANY_INBOUND or */
+ /**< SSRC_ANY_OUTBOUND if key sharing */
+ /**< is used for this policy element. */
+ srtp_crypto_policy_t rtp; /**< SRTP crypto policy. */
+ srtp_crypto_policy_t rtcp; /**< SRTCP crypto policy. */
+ unsigned char *key; /**< Pointer to the SRTP master key for */
+ /**< this stream. */
+ srtp_master_key_t **keys; /** Array of Master Key structures */
+ unsigned long num_master_keys; /** Number of master keys */
+ srtp_ekt_policy_t ekt; /**< Pointer to the EKT policy structure */
+ /**< for this stream (if any) */
+ unsigned long window_size; /**< The window size to use for replay */
+ /**< protection. */
+ int allow_repeat_tx; /**< Whether retransmissions of */
+ /**< packets with the same sequence */
+ /**< number are allowed. */
+ /**< (Note that such repeated */
+ /**< transmissions must have the same */
+ /**< RTP payload, or a severe security */
+ /**< weakness is introduced!) */
+ int *enc_xtn_hdr; /**< List of header ids to encrypt. */
+ int enc_xtn_hdr_count; /**< Number of entries in list of header */
+ /**< ids. */
+ struct srtp_policy_t *next; /**< Pointer to next stream policy. */
+} srtp_policy_t;
+
+/**
+ * @brief An srtp_t points to an SRTP session structure.
+ *
+ * The typedef srtp_t is a pointer to a structure that represents
+ * an SRTP session. This datatype is intentially opaque in
+ * order to separate the interface from the implementation.
+ *
+ * An SRTP session consists of all of the traffic sent to the RTP and
+ * RTCP destination transport addresses, using the RTP/SAVP (Secure
+ * Audio/Video Profile). A session can be viewed as a set of SRTP
+ * streams, each of which originates with a different participant.
+ */
+typedef srtp_ctx_t *srtp_t;
+
+/**
+ * @brief srtp_init() initializes the srtp library.
+ *
+ * @warning This function @b must be called before any other srtp
+ * functions.
+ */
+srtp_err_status_t srtp_init(void);
+
+/**
+ * @brief srtp_shutdown() de-initializes the srtp library.
+ *
+ * @warning No srtp functions may be called after calling this function.
+ */
+srtp_err_status_t srtp_shutdown(void);
+
+/**
+ * @brief srtp_protect() is the Secure RTP sender-side packet processing
+ * function.
+ *
+ * The function call srtp_protect(ctx, rtp_hdr, len_ptr) applies SRTP
+ * protection to the RTP packet rtp_hdr (which has length *len_ptr) using
+ * the SRTP context ctx. If srtp_err_status_ok is returned, then rtp_hdr
+ * points to the resulting SRTP packet and *len_ptr is the number of
+ * octets in that packet; otherwise, no assumptions should be made
+ * about the value of either data elements.
+ *
+ * The sequence numbers of the RTP packets presented to this function
+ * need not be consecutive, but they @b must be out of order by less
+ * than 2^15 = 32,768 packets.
+ *
+ * @warning This function assumes that it can write the authentication
+ * tag into the location in memory immediately following the RTP
+ * packet, and assumes that the RTP packet is aligned on a 32-bit
+ * boundary.
+ *
+ * @warning This function assumes that it can write SRTP_MAX_TRAILER_LEN
+ * into the location in memory immediately following the RTP packet.
+ * Callers MUST ensure that this much writable memory is available in
+ * the buffer that holds the RTP packet.
+ *
+ * @param ctx is the SRTP context to use in processing the packet.
+ *
+ * @param rtp_hdr is a pointer to the RTP packet (before the call); after
+ * the function returns, it points to the srtp packet.
+ *
+ * @param len_ptr is a pointer to the length in octets of the complete
+ * RTP packet (header and body) before the function call, and of the
+ * complete SRTP packet after the call, if srtp_err_status_ok was returned.
+ * Otherwise, the value of the data to which it points is undefined.
+ *
+ * @return
+ * - srtp_err_status_ok no problems
+ * - srtp_err_status_replay_fail rtp sequence number was non-increasing
+ * - @e other failure in cryptographic mechanisms
+ */
+srtp_err_status_t srtp_protect(srtp_t ctx, void *rtp_hdr, int *len_ptr);
+
+/**
+ * @brief srtp_protect_mki() is the Secure RTP sender-side packet processing
+ * function that can utilize MKI.
+ *
+ * The function call srtp_protect(ctx, rtp_hdr, len_ptr) applies SRTP
+ * protection to the RTP packet rtp_hdr (which has length *len_ptr) using
+ * the SRTP context ctx. If srtp_err_status_ok is returned, then rtp_hdr
+ * points to the resulting SRTP packet and *len_ptr is the number of
+ * octets in that packet; otherwise, no assumptions should be made
+ * about the value of either data elements.
+ *
+ * The sequence numbers of the RTP packets presented to this function
+ * need not be consecutive, but they @b must be out of order by less
+ * than 2^15 = 32,768 packets.
+ *
+ * @warning This function assumes that it can write the authentication
+ * tag into the location in memory immediately following the RTP
+ * packet, and assumes that the RTP packet is aligned on a 32-bit
+ * boundary.
+ *
+ * @warning This function assumes that it can write SRTP_MAX_TRAILER_LEN
+ * into the location in memory immediately following the RTP packet.
+ * Callers MUST ensure that this much writable memory is available in
+ * the buffer that holds the RTP packet.
+ *
+ * @param ctx is the SRTP context to use in processing the packet.
+ *
+ * @param rtp_hdr is a pointer to the RTP packet (before the call); after
+ * the function returns, it points to the srtp packet.
+ *
+ * @param pkt_octet_len is a pointer to the length in octets of the complete
+ * RTP packet (header and body) before the function call, and of the
+ * complete SRTP packet after the call, if srtp_err_status_ok was returned.
+ * Otherwise, the value of the data to which it points is undefined.
+ *
+ * @param use_mki is a boolean to tell the system if mki is being used. If
+ * set to false then will use the first set of session keys. If set to true
+ * will
+ * use the session keys identified by the mki_index
+ *
+ * @param mki_index integer value specifying which set of session keys should be
+ * used if use_mki is set to true.
+ *
+ * @return
+ * - srtp_err_status_ok no problems
+ * - srtp_err_status_replay_fail rtp sequence number was non-increasing
+ * - @e other failure in cryptographic mechanisms
+ */
+srtp_err_status_t srtp_protect_mki(srtp_ctx_t *ctx,
+ void *rtp_hdr,
+ int *pkt_octet_len,
+ unsigned int use_mki,
+ unsigned int mki_index);
+
+/**
+ * @brief srtp_unprotect() is the Secure RTP receiver-side packet
+ * processing function.
+ *
+ * The function call srtp_unprotect(ctx, srtp_hdr, len_ptr) verifies
+ * the Secure RTP protection of the SRTP packet pointed to by srtp_hdr
+ * (which has length *len_ptr), using the SRTP context ctx. If
+ * srtp_err_status_ok is returned, then srtp_hdr points to the resulting
+ * RTP packet and *len_ptr is the number of octets in that packet;
+ * otherwise, no assumptions should be made about the value of either
+ * data elements.
+ *
+ * The sequence numbers of the RTP packets presented to this function
+ * need not be consecutive, but they @b must be out of order by less
+ * than 2^15 = 32,768 packets.
+ *
+ * @warning This function assumes that the SRTP packet is aligned on a
+ * 32-bit boundary.
+ *
+ * @param ctx is the SRTP session which applies to the particular packet.
+ *
+ * @param srtp_hdr is a pointer to the header of the SRTP packet
+ * (before the call). after the function returns, it points to the
+ * rtp packet if srtp_err_status_ok was returned; otherwise, the value of
+ * the data to which it points is undefined.
+ *
+ * @param len_ptr is a pointer to the length in octets of the complete
+ * srtp packet (header and body) before the function call, and of the
+ * complete rtp packet after the call, if srtp_err_status_ok was returned.
+ * Otherwise, the value of the data to which it points is undefined.
+ *
+ * @return
+ * - srtp_err_status_ok if the RTP packet is valid.
+ * - srtp_err_status_auth_fail if the SRTP packet failed the message
+ * authentication check.
+ * - srtp_err_status_replay_fail if the SRTP packet is a replay (e.g. packet
+ * has already been processed and accepted).
+ * - [other] if there has been an error in the cryptographic mechanisms.
+ *
+ */
+srtp_err_status_t srtp_unprotect(srtp_t ctx, void *srtp_hdr, int *len_ptr);
+
+/**
+ * @brief srtp_unprotect_mki() is the Secure RTP receiver-side packet
+ * processing function that checks for MKI.
+ *
+ * The function call srtp_unprotect(ctx, srtp_hdr, len_ptr) verifies
+ * the Secure RTP protection of the SRTP packet pointed to by srtp_hdr
+ * (which has length *len_ptr), using the SRTP context ctx. If
+ * srtp_err_status_ok is returned, then srtp_hdr points to the resulting
+ * RTP packet and *len_ptr is the number of octets in that packet;
+ * otherwise, no assumptions should be made about the value of either
+ * data elements.
+ *
+ * The sequence numbers of the RTP packets presented to this function
+ * need not be consecutive, but they @b must be out of order by less
+ * than 2^15 = 32,768 packets.
+ *
+ * @warning This function assumes that the SRTP packet is aligned on a
+ * 32-bit boundary.
+ *
+ * @param ctx is the SRTP session which applies to the particular packet.
+ *
+ * @param srtp_hdr is a pointer to the header of the SRTP packet
+ * (before the call). after the function returns, it points to the
+ * rtp packet if srtp_err_status_ok was returned; otherwise, the value of
+ * the data to which it points is undefined.
+ *
+ * @param len_ptr is a pointer to the length in octets of the complete
+ * srtp packet (header and body) before the function call, and of the
+ * complete rtp packet after the call, if srtp_err_status_ok was returned.
+ * Otherwise, the value of the data to which it points is undefined.
+ *
+ * @param use_mki is a boolean to tell the system if mki is being used. If
+ * set to false then will use the first set of session keys. If set to true
+ * will
+ * use the session keys identified by the mki_index
+ *
+ * @return
+ * - srtp_err_status_ok if the RTP packet is valid.
+ * - srtp_err_status_auth_fail if the SRTP packet failed the message
+ * authentication check.
+ * - srtp_err_status_replay_fail if the SRTP packet is a replay (e.g. packet
+ * has already been processed and accepted).
+ * - srtp_err_status_bad_mki if the MKI in the packet is not a known MKI id
+ * - [other] if there has been an error in the cryptographic mechanisms.
+ *
+ */
+srtp_err_status_t srtp_unprotect_mki(srtp_t ctx,
+ void *srtp_hdr,
+ int *len_ptr,
+ unsigned int use_mki);
+
+/**
+ * @brief srtp_create() allocates and initializes an SRTP session.
+
+ * The function call srtp_create(session, policy) allocates and
+ * initializes an SRTP session context, applying the given policy.
+ *
+ * @param session is a pointer to the SRTP session to which the policy is
+ * to be added.
+ *
+ * @param policy is the srtp_policy_t struct that describes the policy
+ * for the session. The struct may be a single element, or it may be
+ * the head of a list, in which case each element of the list is
+ * processed. It may also be NULL, in which case streams should be added
+ * later using srtp_add_stream(). The final element of the list @b must
+ * have its `next' field set to NULL.
+ *
+ * @return
+ * - srtp_err_status_ok if creation succeded.
+ * - srtp_err_status_alloc_fail if allocation failed.
+ * - srtp_err_status_init_fail if initialization failed.
+ */
+srtp_err_status_t srtp_create(srtp_t *session, const srtp_policy_t *policy);
+
+/**
+ * @brief srtp_add_stream() allocates and initializes an SRTP stream
+ * within a given SRTP session.
+ *
+ * The function call srtp_add_stream(session, policy) allocates and
+ * initializes a new SRTP stream within a given, previously created
+ * session, applying the policy given as the other argument to that
+ * stream.
+ *
+ * @return values:
+ * - srtp_err_status_ok if stream creation succeded.
+ * - srtp_err_status_alloc_fail if stream allocation failed
+ * - srtp_err_status_init_fail if stream initialization failed.
+ */
+srtp_err_status_t srtp_add_stream(srtp_t session, const srtp_policy_t *policy);
+
+/**
+ * @brief srtp_remove_stream() deallocates an SRTP stream.
+ *
+ * The function call srtp_remove_stream(session, ssrc) removes
+ * the SRTP stream with the SSRC value ssrc from the SRTP session
+ * context given by the argument session.
+ *
+ * @param session is the SRTP session from which the stream
+ * will be removed.
+ *
+ * @param ssrc is the SSRC value of the stream to be removed
+ * in network byte order.
+ *
+ * @warning Wildcard SSRC values cannot be removed from a
+ * session.
+ *
+ * @return
+ * - srtp_err_status_ok if the stream deallocation succeded.
+ * - [other] otherwise.
+ *
+ */
+srtp_err_status_t srtp_remove_stream(srtp_t session, unsigned int ssrc);
+
+/**
+ * @brief srtp_update() udpates all streams in the session.
+ *
+ * The function call srtp_update(session, policy) updates
+ * all the streams in the session applying the given policy
+ * and key. The exsisting ROC value of all streams will be
+ * preserved.
+ *
+ * @param session is the SRTP session that contains the streams
+ * to be updated.
+ *
+ * @param policy is the srtp_policy_t struct that describes the policy
+ * for the session. The struct may be a single element, or it may be
+ * the head of a list, in which case each element of the list is
+ * processed. The final element of the list @b must
+ * have its `next' field set to NULL.
+ *
+ * @return
+ * - srtp_err_status_ok if stream creation succeded.
+ * - srtp_err_status_alloc_fail if stream allocation failed
+ * - srtp_err_status_init_fail if stream initialization failed.
+ * - [other] otherwise.
+ *
+ */
+srtp_err_status_t srtp_update(srtp_t session, const srtp_policy_t *policy);
+
+/**
+ * @brief srtp_update_stream() udpates a SRTP stream.
+ *
+ * The function call srtp_update_stream(session, policy) updates
+ * the stream(s) in the session that match applying the given
+ * policy and key. The exsisting ROC value of all stream(s) will
+ * be preserved.
+ *
+ * @param session is the SRTP session that contains the streams
+ * to be updated.
+ *
+ * @param policy is the srtp_policy_t struct that describes the policy
+ * for the session.
+ *
+ * @return
+ * - srtp_err_status_ok if stream creation succeded.
+ * - srtp_err_status_alloc_fail if stream allocation failed
+ * - srtp_err_status_init_fail if stream initialization failed.
+ * - [other] otherwise.
+ *
+ */
+srtp_err_status_t srtp_update_stream(srtp_t session,
+ const srtp_policy_t *policy);
+
+/**
+ * @brief srtp_crypto_policy_set_rtp_default() sets a crypto policy
+ * structure to the SRTP default policy for RTP protection.
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call crypto_policy_set_rtp_default(&p) sets the
+ * crypto_policy_t at location p to the SRTP default policy for RTP
+ * protection, as defined in the specification. This function is a
+ * convenience that helps to avoid dealing directly with the policy
+ * data structure. You are encouraged to initialize policy elements
+ * with this function call. Doing so may allow your code to be
+ * forward compatible with later versions of libSRTP that include more
+ * elements in the crypto_policy_t datatype.
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_rtp_default(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_rtcp_default() sets a crypto policy
+ * structure to the SRTP default policy for RTCP protection.
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_rtcp_default(&p) sets the
+ * srtp_crypto_policy_t at location p to the SRTP default policy for RTCP
+ * protection, as defined in the specification. This function is a
+ * convenience that helps to avoid dealing directly with the policy
+ * data structure. You are encouraged to initialize policy elements
+ * with this function call. Doing so may allow your code to be
+ * forward compatible with later versions of libSRTP that include more
+ * elements in the srtp_crypto_policy_t datatype.
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_rtcp_default(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80() sets a crypto
+ * policy structure to the SRTP default policy for RTP protection.
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80() is a
+ * synonym for srtp_crypto_policy_set_rtp_default(). It conforms to the
+ * naming convention used in RFC 4568 (SDP Security Descriptions for
+ * Media Streams).
+ *
+ * @return void.
+ *
+ */
+#define srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(p) \
+ srtp_crypto_policy_set_rtp_default(p)
+
+/**
+ * @brief srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32() sets a crypto
+ * policy structure to a short-authentication tag policy
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&p)
+ * sets the srtp_crypto_policy_t at location p to use policy
+ * AES_CM_128_HMAC_SHA1_32 as defined in RFC 4568.
+ * This policy uses AES-128
+ * Counter Mode encryption and HMAC-SHA1 authentication, with an
+ * authentication tag that is only 32 bits long. This length is
+ * considered adequate only for protecting audio and video media that
+ * use a stateless playback function. See Section 7.5 of RFC 3711
+ * (http://www.ietf.org/rfc/rfc3711.txt).
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @warning This crypto policy is intended for use in SRTP, but not in
+ * SRTCP. It is recommended that a policy that uses longer
+ * authentication tags be used for SRTCP. See Section 7.5 of RFC 3711
+ * (http://www.ietf.org/rfc/rfc3711.txt).
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_aes_cm_128_null_auth() sets a crypto
+ * policy structure to an encryption-only policy
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_aes_cm_128_null_auth(&p) sets
+ * the srtp_crypto_policy_t at location p to use the SRTP default cipher
+ * (AES-128 Counter Mode), but to use no authentication method. This
+ * policy is NOT RECOMMENDED unless it is unavoidable; see Section 7.5
+ * of RFC 3711 (http://www.ietf.org/rfc/rfc3711.txt).
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @warning This policy is NOT RECOMMENDED for SRTP unless it is
+ * unavoidable, and it is NOT RECOMMENDED at all for SRTCP; see
+ * Section 7.5 of RFC 3711 (http://www.ietf.org/rfc/rfc3711.txt).
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_aes_cm_128_null_auth(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_null_cipher_hmac_sha1_80() sets a crypto
+ * policy structure to an authentication-only policy
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_null_cipher_hmac_sha1_80(&p)
+ * sets the srtp_crypto_policy_t at location p to use HMAC-SHA1 with an 80
+ * bit authentication tag to provide message authentication, but to
+ * use no encryption. This policy is NOT RECOMMENDED for SRTP unless
+ * there is a requirement to forego encryption.
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @warning This policy is NOT RECOMMENDED for SRTP unless there is a
+ * requirement to forego encryption.
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_null_cipher_hmac_sha1_80(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_null_cipher_hmac_null() sets a crypto
+ * policy structure to use no encryption or authentication.
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_null_cipher_hmac_null(&p)
+ * sets the srtp_crypto_policy_t at location p to use no encryption and
+ * no authentication. This policy should only be used for testing and
+ * troubleshootingl.
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @warning This policy is NOT RECOMMENDED for SRTP unless there is a
+ * requirement to forego encryption and authentication.
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_null_cipher_hmac_null(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80() sets a crypto
+ * policy structure to a encryption and authentication policy using AES-256
+ * for RTP protection.
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(&p)
+ * sets the srtp_crypto_policy_t at location p to use policy
+ * AES_CM_256_HMAC_SHA1_80 as defined in RFC 6188. This policy uses AES-256
+ * Counter Mode encryption and HMAC-SHA1 authentication, with an 80 bit
+ * authentication tag.
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_aes_cm_256_hmac_sha1_32() sets a crypto
+ * policy structure to a short-authentication tag policy using AES-256
+ * encryption.
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_aes_cm_256_hmac_sha1_32(&p)
+ * sets the srtp_crypto_policy_t at location p to use policy
+ * AES_CM_256_HMAC_SHA1_32 as defined in RFC 6188. This policy uses AES-256
+ * Counter Mode encryption and HMAC-SHA1 authentication, with an
+ * authentication tag that is only 32 bits long. This length is
+ * considered adequate only for protecting audio and video media that
+ * use a stateless playback function. See Section 7.5 of RFC 3711
+ * (http://www.ietf.org/rfc/rfc3711.txt).
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @warning This crypto policy is intended for use in SRTP, but not in
+ * SRTCP. It is recommended that a policy that uses longer
+ * authentication tags be used for SRTCP. See Section 7.5 of RFC 3711
+ * (http://www.ietf.org/rfc/rfc3711.txt).
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_aes_cm_256_hmac_sha1_32(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_aes_cm_256_null_auth() sets a crypto
+ * policy structure to an encryption-only policy
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_aes_cm_256_null_auth(&p) sets
+ * the srtp_crypto_policy_t at location p to use the SRTP default cipher
+ * (AES-256 Counter Mode), but to use no authentication method. This
+ * policy is NOT RECOMMENDED unless it is unavoidable; see Section 7.5
+ * of RFC 3711 (http://www.ietf.org/rfc/rfc3711.txt).
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @warning This policy is NOT RECOMMENDED for SRTP unless it is
+ * unavoidable, and it is NOT RECOMMENDED at all for SRTCP; see
+ * Section 7.5 of RFC 3711 (http://www.ietf.org/rfc/rfc3711.txt).
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_aes_cm_256_null_auth(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_aes_cm_192_hmac_sha1_80() sets a crypto
+ * policy structure to a encryption and authentication policy using AES-192
+ * for RTP protection.
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_aes_cm_192_hmac_sha1_80(&p)
+ * sets the crypto_policy_t at location p to use policy
+ * AES_CM_192_HMAC_SHA1_80 as defined in RFC 6188. This policy uses AES-192
+ * Counter Mode encryption and HMAC-SHA1 authentication, with an 80 bit
+ * authentication tag.
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the crypto_policy_t datatype.
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_aes_cm_192_hmac_sha1_80(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_aes_cm_192_hmac_sha1_32() sets a crypto
+ * policy structure to a short-authentication tag policy using AES-192
+ * encryption.
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_aes_cm_192_hmac_sha1_32(&p)
+ * sets the crypto_policy_t at location p to use policy
+ * AES_CM_192_HMAC_SHA1_32 as defined in RFC 6188. This policy uses AES-192
+ * Counter Mode encryption and HMAC-SHA1 authentication, with an
+ * authentication tag that is only 32 bits long. This length is
+ * considered adequate only for protecting audio and video media that
+ * use a stateless playback function. See Section 7.5 of RFC 3711
+ * (http://www.ietf.org/rfc/rfc3711.txt).
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the crypto_policy_t datatype.
+ *
+ * @warning This crypto policy is intended for use in SRTP, but not in
+ * SRTCP. It is recommended that a policy that uses longer
+ * authentication tags be used for SRTCP. See Section 7.5 of RFC 3711
+ * (http://www.ietf.org/rfc/rfc3711.txt).
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_aes_cm_192_hmac_sha1_32(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_aes_cm_192_null_auth() sets a crypto
+ * policy structure to an encryption-only policy
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_aes_cm_192_null_auth(&p) sets
+ * the crypto_policy_t at location p to use the SRTP default cipher
+ * (AES-192 Counter Mode), but to use no authentication method. This
+ * policy is NOT RECOMMENDED unless it is unavoidable; see Section 7.5
+ * of RFC 3711 (http://www.ietf.org/rfc/rfc3711.txt).
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the crypto_policy_t datatype.
+ *
+ * @warning This policy is NOT RECOMMENDED for SRTP unless it is
+ * unavoidable, and it is NOT RECOMMENDED at all for SRTCP; see
+ * Section 7.5 of RFC 3711 (http://www.ietf.org/rfc/rfc3711.txt).
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_aes_cm_192_null_auth(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_aes_gcm_128_8_auth() sets a crypto
+ * policy structure to an AEAD encryption policy.
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_aes_gcm_128_8_auth(&p) sets
+ * the srtp_crypto_policy_t at location p to use the SRTP default cipher
+ * (AES-128 Galois Counter Mode) with 8 octet auth tag. This
+ * policy applies confidentiality and authentication to both the
+ * RTP and RTCP packets.
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_aes_gcm_128_8_auth(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_aes_gcm_256_8_auth() sets a crypto
+ * policy structure to an AEAD encryption policy
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_aes_gcm_256_8_auth(&p) sets
+ * the srtp_crypto_policy_t at location p to use the SRTP default cipher
+ * (AES-256 Galois Counter Mode) with 8 octet auth tag. This
+ * policy applies confidentiality and authentication to both the
+ * RTP and RTCP packets.
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_aes_gcm_256_8_auth(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_aes_gcm_128_8_only_auth() sets a crypto
+ * policy structure to an AEAD authentication-only policy
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_aes_gcm_128_8_only_auth(&p) sets
+ * the srtp_crypto_policy_t at location p to use the SRTP default cipher
+ * (AES-128 Galois Counter Mode) with 8 octet auth tag. This policy
+ * applies confidentiality and authentication to the RTP packets,
+ * but only authentication to the RTCP packets.
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_aes_gcm_128_8_only_auth(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_aes_gcm_256_8_only_auth() sets a crypto
+ * policy structure to an AEAD authentication-only policy
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_aes_gcm_256_8_only_auth(&p) sets
+ * the srtp_crypto_policy_t at location p to use the SRTP default cipher
+ * (AES-256 Galois Counter Mode) with 8 octet auth tag. This policy
+ * applies confidentiality and authentication to the RTP packets,
+ * but only authentication to the RTCP packets.
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_aes_gcm_256_8_only_auth(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_aes_gcm_128_16_auth() sets a crypto
+ * policy structure to an AEAD encryption policy.
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_aes_gcm_128_16_auth(&p) sets
+ * the srtp_crypto_policy_t at location p to use the SRTP default cipher
+ * (AES-128 Galois Counter Mode) with 16 octet auth tag. This
+ * policy applies confidentiality and authentication to both the
+ * RTP and RTCP packets.
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_aes_gcm_128_16_auth(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_crypto_policy_set_aes_gcm_256_16_auth() sets a crypto
+ * policy structure to an AEAD encryption policy
+ *
+ * @param p is a pointer to the policy structure to be set
+ *
+ * The function call srtp_crypto_policy_set_aes_gcm_256_16_auth(&p) sets
+ * the srtp_crypto_policy_t at location p to use the SRTP default cipher
+ * (AES-256 Galois Counter Mode) with 16 octet auth tag. This
+ * policy applies confidentiality and authentication to both the
+ * RTP and RTCP packets.
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @return void.
+ *
+ */
+void srtp_crypto_policy_set_aes_gcm_256_16_auth(srtp_crypto_policy_t *p);
+
+/**
+ * @brief srtp_dealloc() deallocates storage for an SRTP session
+ * context.
+ *
+ * The function call srtp_dealloc(s) deallocates storage for the
+ * SRTP session context s. This function should be called no more
+ * than one time for each of the contexts allocated by the function
+ * srtp_create().
+ *
+ * @param s is the srtp_t for the session to be deallocated.
+ *
+ * @return
+ * - srtp_err_status_ok if there no problems.
+ * - srtp_err_status_dealloc_fail a memory deallocation failure occured.
+ */
+srtp_err_status_t srtp_dealloc(srtp_t s);
+
+/*
+ * @brief identifies a particular SRTP profile
+ *
+ * An srtp_profile_t enumeration is used to identify a particular SRTP
+ * profile (that is, a set of algorithms and parameters). These profiles
+ * are defined for DTLS-SRTP:
+ * https://www.iana.org/assignments/srtp-protection/srtp-protection.xhtml
+ */
+typedef enum {
+ srtp_profile_reserved = 0,
+ srtp_profile_aes128_cm_sha1_80 = 1,
+ srtp_profile_aes128_cm_sha1_32 = 2,
+ srtp_profile_null_sha1_80 = 5,
+ srtp_profile_null_sha1_32 = 6,
+ srtp_profile_aead_aes_128_gcm = 7,
+ srtp_profile_aead_aes_256_gcm = 8,
+} srtp_profile_t;
+
+/**
+ * @brief srtp_crypto_policy_set_from_profile_for_rtp() sets a crypto policy
+ * structure to the appropriate value for RTP based on an srtp_profile_t
+ *
+ * @param policy is a pointer to the policy structure to be set
+ *
+ * @param profile is an enumeration for the policy to be set
+ *
+ * The function call srtp_crypto_policy_set_rtp_default(&policy, profile)
+ * sets the srtp_crypto_policy_t at location policy to the policy for RTP
+ * protection, as defined by the srtp_profile_t profile.
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @return values
+ * - srtp_err_status_ok no problems were encountered
+ * - srtp_err_status_bad_param the profile is not supported
+ *
+ */
+srtp_err_status_t srtp_crypto_policy_set_from_profile_for_rtp(
+ srtp_crypto_policy_t *policy,
+ srtp_profile_t profile);
+
+/**
+ * @brief srtp_crypto_policy_set_from_profile_for_rtcp() sets a crypto policy
+ * structure to the appropriate value for RTCP based on an srtp_profile_t
+ *
+ * @param policy is a pointer to the policy structure to be set
+ *
+ * @param profile is an enumeration for the policy to be set
+ *
+ * The function call srtp_crypto_policy_set_rtcp_default(&policy, profile)
+ * sets the srtp_crypto_policy_t at location policy to the policy for RTCP
+ * protection, as defined by the srtp_profile_t profile.
+ *
+ * This function is a convenience that helps to avoid dealing directly
+ * with the policy data structure. You are encouraged to initialize
+ * policy elements with this function call. Doing so may allow your
+ * code to be forward compatible with later versions of libSRTP that
+ * include more elements in the srtp_crypto_policy_t datatype.
+ *
+ * @return values
+ * - srtp_err_status_ok no problems were encountered
+ * - srtp_err_status_bad_param the profile is not supported
+ *
+ */
+srtp_err_status_t srtp_crypto_policy_set_from_profile_for_rtcp(
+ srtp_crypto_policy_t *policy,
+ srtp_profile_t profile);
+
+/**
+ * @brief returns the master key length for a given SRTP profile
+ */
+unsigned int srtp_profile_get_master_key_length(srtp_profile_t profile);
+
+/**
+ * @brief returns the master salt length for a given SRTP profile
+ */
+unsigned int srtp_profile_get_master_salt_length(srtp_profile_t profile);
+
+/**
+ * @brief appends the salt to the key
+ *
+ * The function call srtp_append_salt_to_key(k, klen, s, slen)
+ * copies the string s to the location at klen bytes following
+ * the location k.
+ *
+ * @warning There must be at least bytes_in_salt + bytes_in_key bytes
+ * available at the location pointed to by key.
+ *
+ */
+void srtp_append_salt_to_key(unsigned char *key,
+ unsigned int bytes_in_key,
+ unsigned char *salt,
+ unsigned int bytes_in_salt);
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup SRTCP Secure RTCP
+ * @ingroup SRTP
+ *
+ * @brief Secure RTCP functions are used to protect RTCP traffic.
+ *
+ * RTCP is the control protocol for RTP. libSRTP protects RTCP
+ * traffic in much the same way as it does RTP traffic. The function
+ * srtp_protect_rtcp() applies cryptographic protections to outbound
+ * RTCP packets, and srtp_unprotect_rtcp() verifies the protections on
+ * inbound RTCP packets.
+ *
+ * A note on the naming convention: srtp_protect_rtcp() has an srtp_t
+ * as its first argument, and thus has `srtp_' as its prefix. The
+ * trailing `_rtcp' indicates the protocol on which it acts.
+ *
+ * @{
+ */
+
+/**
+ * @brief srtp_protect_rtcp() is the Secure RTCP sender-side packet
+ * processing function.
+ *
+ * The function call srtp_protect_rtcp(ctx, rtp_hdr, len_ptr) applies
+ * SRTCP protection to the RTCP packet rtcp_hdr (which has length
+ * *len_ptr) using the SRTP session context ctx. If srtp_err_status_ok is
+ * returned, then rtp_hdr points to the resulting SRTCP packet and
+ * *len_ptr is the number of octets in that packet; otherwise, no
+ * assumptions should be made about the value of either data elements.
+ *
+ * @warning This function assumes that it can write the authentication
+ * tag into the location in memory immediately following the RTCP
+ * packet, and assumes that the RTCP packet is aligned on a 32-bit
+ * boundary.
+ *
+ * @warning This function assumes that it can write SRTP_MAX_TRAILER_LEN+4
+ * into the location in memory immediately following the RTCP packet.
+ * Callers MUST ensure that this much writable memory is available in
+ * the buffer that holds the RTCP packet.
+ *
+ * @param ctx is the SRTP context to use in processing the packet.
+ *
+ * @param rtcp_hdr is a pointer to the RTCP packet (before the call); after
+ * the function returns, it points to the srtp packet.
+ *
+ * @param pkt_octet_len is a pointer to the length in octets of the
+ * complete RTCP packet (header and body) before the function call,
+ * and of the complete SRTCP packet after the call, if srtp_err_status_ok
+ * was returned. Otherwise, the value of the data to which it points
+ * is undefined.
+ *
+ * @return
+ * - srtp_err_status_ok if there were no problems.
+ * - [other] if there was a failure in
+ * the cryptographic mechanisms.
+ */
+srtp_err_status_t srtp_protect_rtcp(srtp_t ctx,
+ void *rtcp_hdr,
+ int *pkt_octet_len);
+
+/**
+ * @brief srtp_protect_rtcp_mki() is the Secure RTCP sender-side packet
+ * processing function that can utilize mki.
+ *
+ * The function call srtp_protect_rtcp(ctx, rtp_hdr, len_ptr) applies
+ * SRTCP protection to the RTCP packet rtcp_hdr (which has length
+ * *len_ptr) using the SRTP session context ctx. If srtp_err_status_ok is
+ * returned, then rtp_hdr points to the resulting SRTCP packet and
+ * *len_ptr is the number of octets in that packet; otherwise, no
+ * assumptions should be made about the value of either data elements.
+ *
+ * @warning This function assumes that it can write the authentication
+ * tag into the location in memory immediately following the RTCP
+ * packet, and assumes that the RTCP packet is aligned on a 32-bit
+ * boundary.
+ *
+ * @warning This function assumes that it can write SRTP_MAX_TRAILER_LEN+4
+ * into the location in memory immediately following the RTCP packet.
+ * Callers MUST ensure that this much writable memory is available in
+ * the buffer that holds the RTCP packet.
+ *
+ * @param ctx is the SRTP context to use in processing the packet.
+ *
+ * @param rtcp_hdr is a pointer to the RTCP packet (before the call); after
+ * the function returns, it points to the srtp packet.
+ *
+ * @param pkt_octet_len is a pointer to the length in octets of the
+ * complete RTCP packet (header and body) before the function call,
+ * and of the complete SRTCP packet after the call, if srtp_err_status_ok
+ * was returned. Otherwise, the value of the data to which it points
+ * is undefined.
+ *
+ * @param use_mki is a boolean to tell the system if mki is being used. If
+ * set to false then will use the first set of session keys. If set to true
+ * will
+ * use the session keys identified by the mki_index
+ *
+ * @param mki_index integer value specifying which set of session kesy should be
+ * used if use_mki is set to true.
+ *
+ * @return
+ * - srtp_err_status_ok if there were no problems.
+ * - [other] if there was a failure in
+ * the cryptographic mechanisms.
+ */
+srtp_err_status_t srtp_protect_rtcp_mki(srtp_t ctx,
+ void *rtcp_hdr,
+ int *pkt_octet_len,
+ unsigned int use_mki,
+ unsigned int mki_index);
+
+/**
+ * @brief srtp_unprotect_rtcp() is the Secure RTCP receiver-side packet
+ * processing function.
+ *
+ * The function call srtp_unprotect_rtcp(ctx, srtp_hdr, len_ptr)
+ * verifies the Secure RTCP protection of the SRTCP packet pointed to
+ * by srtcp_hdr (which has length *len_ptr), using the SRTP session
+ * context ctx. If srtp_err_status_ok is returned, then srtcp_hdr points
+ * to the resulting RTCP packet and *len_ptr is the number of octets
+ * in that packet; otherwise, no assumptions should be made about the
+ * value of either data elements.
+ *
+ * @warning This function assumes that the SRTCP packet is aligned on a
+ * 32-bit boundary.
+ *
+ * @param ctx is a pointer to the srtp_t which applies to the
+ * particular packet.
+ *
+ * @param srtcp_hdr is a pointer to the header of the SRTCP packet
+ * (before the call). After the function returns, it points to the
+ * rtp packet if srtp_err_status_ok was returned; otherwise, the value of
+ * the data to which it points is undefined.
+ *
+ * @param pkt_octet_len is a pointer to the length in octets of the
+ * complete SRTCP packet (header and body) before the function call,
+ * and of the complete rtp packet after the call, if srtp_err_status_ok was
+ * returned. Otherwise, the value of the data to which it points is
+ * undefined.
+ *
+ * @return
+ * - srtp_err_status_ok if the RTCP packet is valid.
+ * - srtp_err_status_auth_fail if the SRTCP packet failed the message
+ * authentication check.
+ * - srtp_err_status_replay_fail if the SRTCP packet is a replay (e.g. has
+ * already been processed and accepted).
+ * - [other] if there has been an error in the cryptographic mechanisms.
+ *
+ */
+srtp_err_status_t srtp_unprotect_rtcp(srtp_t ctx,
+ void *srtcp_hdr,
+ int *pkt_octet_len);
+
+/**
+ * @brief srtp_unprotect_rtcp() is the Secure RTCP receiver-side packet
+ * processing function.
+ *
+ * The function call srtp_unprotect_rtcp(ctx, srtp_hdr, len_ptr)
+ * verifies the Secure RTCP protection of the SRTCP packet pointed to
+ * by srtcp_hdr (which has length *len_ptr), using the SRTP session
+ * context ctx. If srtp_err_status_ok is returned, then srtcp_hdr points
+ * to the resulting RTCP packet and *len_ptr is the number of octets
+ * in that packet; otherwise, no assumptions should be made about the
+ * value of either data elements.
+ *
+ * @warning This function assumes that the SRTCP packet is aligned on a
+ * 32-bit boundary.
+ *
+ * @param ctx is a pointer to the srtp_t which applies to the
+ * particular packet.
+ *
+ * @param srtcp_hdr is a pointer to the header of the SRTCP packet
+ * (before the call). After the function returns, it points to the
+ * rtp packet if srtp_err_status_ok was returned; otherwise, the value of
+ * the data to which it points is undefined.
+ *
+ * @param pkt_octet_len is a pointer to the length in octets of the
+ * complete SRTCP packet (header and body) before the function call,
+ * and of the complete rtp packet after the call, if srtp_err_status_ok was
+ * returned. Otherwise, the value of the data to which it points is
+ * undefined.
+ *
+ * @param use_mki is a boolean to tell the system if mki is being used. If
+ * set to false then will use the first set of session keys. If set to true
+ * will use the session keys identified by the mki_index
+ *
+ * @return
+ * - srtp_err_status_ok if the RTCP packet is valid.
+ * - srtp_err_status_auth_fail if the SRTCP packet failed the message
+ * authentication check.
+ * - srtp_err_status_replay_fail if the SRTCP packet is a replay (e.g. has
+ * already been processed and accepted).
+ * - srtp_err_status_bad_mki if the MKI in the packet is not a known MKI
+ * id
+ * - [other] if there has been an error in the
+ * cryptographic mechanisms.
+ *
+ */
+srtp_err_status_t srtp_unprotect_rtcp_mki(srtp_t ctx,
+ void *srtcp_hdr,
+ int *pkt_octet_len,
+ unsigned int use_mki);
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup User data associated to a SRTP session.
+ * @ingroup SRTP
+ *
+ * @brief Store custom user data within a SRTP session.
+ *
+ * @{
+ */
+
+/**
+ * @brief srtp_set_user_data() stores the given pointer into the SRTP
+ * session for later retrieval.
+ *
+ * @param ctx is the srtp_t context in which the given data pointer is
+ * stored.
+ *
+ * @param data is a pointer to the custom information (struct, function,
+ * etc) associated with the SRTP session.
+ *
+ * @return void.
+ *
+ */
+void srtp_set_user_data(srtp_t ctx, void *data);
+
+/**
+ * @brief srtp_get_user_data() retrieves the pointer to the custom data
+ * previously stored with srtp_set_user_data().
+ *
+ * This function is mostly useful for retrieving data associated to a
+ * SRTP session when an event fires. The user can then get such a custom
+ * data by calling this function with the session field of the
+ * srtp_event_data_t struct as argument.
+ *
+ * @param ctx is the srtp_t context in which the given data pointer was
+ * stored.
+ *
+ * @return void* pointer to the user data.
+ *
+ */
+void *srtp_get_user_data(srtp_t ctx);
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup SRTPevents SRTP events and callbacks
+ * @ingroup SRTP
+ *
+ * @brief libSRTP can use a user-provided callback function to
+ * handle events.
+ *
+ *
+ * libSRTP allows a user to provide a callback function to handle
+ * events that need to be dealt with outside of the data plane (see
+ * the enum srtp_event_t for a description of these events). Dealing
+ * with these events is not a strict necessity; they are not
+ * security-critical, but the application may suffer if they are not
+ * handled. The function srtp_set_event_handler() is used to provide
+ * the callback function.
+ *
+ * A default event handler that merely reports on the events as they
+ * happen is included. It is also possible to set the event handler
+ * function to NULL, in which case all events will just be silently
+ * ignored.
+ *
+ * @{
+ */
+
+/**
+ * @brief srtp_event_t defines events that need to be handled
+ *
+ * The enum srtp_event_t defines events that need to be handled
+ * outside the `data plane', such as SSRC collisions and
+ * key expirations.
+ *
+ * When a key expires or the maximum number of packets has been
+ * reached, an SRTP stream will enter an `expired' state in which no
+ * more packets can be protected or unprotected. When this happens,
+ * it is likely that you will want to either deallocate the stream
+ * (using srtp_remove_stream()), and possibly allocate a new one.
+ *
+ * When an SRTP stream expires, the other streams in the same session
+ * are unaffected, unless key sharing is used by that stream. In the
+ * latter case, all of the streams in the session will expire.
+ */
+typedef enum {
+ event_ssrc_collision, /**< An SSRC collision occured. */
+ event_key_soft_limit, /**< An SRTP stream reached the soft key */
+ /**< usage limit and will expire soon. */
+ event_key_hard_limit, /**< An SRTP stream reached the hard */
+ /**< key usage limit and has expired. */
+ event_packet_index_limit /**< An SRTP stream reached the hard */
+ /**< packet limit (2^48 packets). */
+} srtp_event_t;
+
+/**
+ * @brief srtp_event_data_t is the structure passed as a callback to
+ * the event handler function
+ *
+ * The struct srtp_event_data_t holds the data passed to the event
+ * handler function.
+ */
+typedef struct srtp_event_data_t {
+ srtp_t session; /**< The session in which the event happend. */
+ uint32_t ssrc; /**< The ssrc in host order of the stream in which */
+ /**< the event happend */
+ srtp_event_t event; /**< An enum indicating the type of event. */
+} srtp_event_data_t;
+
+/**
+ * @brief srtp_event_handler_func_t is the function prototype for
+ * the event handler.
+ *
+ * The typedef srtp_event_handler_func_t is the prototype for the
+ * event handler function. It has as its only argument an
+ * srtp_event_data_t which describes the event that needs to be handled.
+ * There can only be a single, global handler for all events in
+ * libSRTP.
+ */
+typedef void(srtp_event_handler_func_t)(srtp_event_data_t *data);
+
+/**
+ * @brief sets the event handler to the function supplied by the caller.
+ *
+ * The function call srtp_install_event_handler(func) sets the event
+ * handler function to the value func. The value NULL is acceptable
+ * as an argument; in this case, events will be ignored rather than
+ * handled.
+ *
+ * @param func is a pointer to a fuction that takes an srtp_event_data_t
+ * pointer as an argument and returns void. This function
+ * will be used by libSRTP to handle events.
+ */
+srtp_err_status_t srtp_install_event_handler(srtp_event_handler_func_t func);
+
+/**
+ * @brief Returns the version string of the library.
+ *
+ */
+const char *srtp_get_version_string(void);
+
+/**
+ * @brief Returns the numeric representation of the library version.
+ *
+ */
+unsigned int srtp_get_version(void);
+
+/**
+ * @brief srtp_set_debug_module(mod_name, v)
+ *
+ * sets dynamic debugging to the value v (0 for off, 1 for on) for the
+ * debug module with the name mod_name
+ *
+ * returns err_status_ok on success, err_status_fail otherwise
+ */
+srtp_err_status_t srtp_set_debug_module(const char *mod_name, int v);
+
+/**
+ * @brief srtp_list_debug_modules() outputs a list of debugging modules
+ *
+ */
+srtp_err_status_t srtp_list_debug_modules(void);
+
+/**
+ * @brief srtp_log_level_t defines log levels.
+ *
+ * The enumeration srtp_log_level_t defines log levels reported
+ * in the srtp_log_handler_func_t.
+ *
+ */
+typedef enum {
+ srtp_log_level_error, /**< log level is reporting an error message */
+ srtp_log_level_warning, /**< log level is reporting a warning message */
+ srtp_log_level_info, /**< log level is reporting an info message */
+ srtp_log_level_debug /**< log level is reporting a debug message */
+} srtp_log_level_t;
+
+/**
+ * @brief srtp_log_handler_func_t is the function prototype for
+ * the log handler.
+ *
+ * The typedef srtp_event_handler_func_t is the prototype for the
+ * event handler function. It has as srtp_log_level_t, log
+ * message and data as arguments.
+ * There can only be a single, global handler for all log messages in
+ * libSRTP.
+ */
+typedef void(srtp_log_handler_func_t)(srtp_log_level_t level,
+ const char *msg,
+ void *data);
+
+/**
+ * @brief sets the log handler to the function supplied by the caller.
+ *
+ * The function call srtp_install_log_handler(func) sets the log
+ * handler function to the value func. The value NULL is acceptable
+ * as an argument; in this case, log messages will be ignored.
+ * This function can be called before srtp_init() inorder to capture
+ * any logging during start up.
+ *
+ * @param func is a pointer to a fuction of type srtp_log_handler_func_t.
+ * This function will be used by libSRTP to output log messages.
+ * @param data is a user pointer that will be returned as the data argument in
+ * func.
+ */
+srtp_err_status_t srtp_install_log_handler(srtp_log_handler_func_t func,
+ void *data);
+
+/**
+ * @brief srtp_get_protect_trailer_length(session, use_mki, mki_index, length)
+ *
+ * Determines the length of the amount of data Lib SRTP will add to the
+ * packet during the protect process. The length is returned in the length
+ * parameter
+ *
+ * returns err_status_ok on success, err_status_bad_mki if the MKI index is
+ * invalid
+ *
+ */
+srtp_err_status_t srtp_get_protect_trailer_length(srtp_t session,
+ uint32_t use_mki,
+ uint32_t mki_index,
+ uint32_t *length);
+
+/**
+ * @brief srtp_get_protect_rtcp_trailer_length(session, use_mki, mki_index,
+ * length)
+ *
+ * Determines the length of the amount of data Lib SRTP will add to the
+ * packet during the protect process. The length is returned in the length
+ * parameter
+ *
+ * returns err_status_ok on success, err_status_bad_mki if the MKI index is
+ * invalid
+ *
+ */
+srtp_err_status_t srtp_get_protect_rtcp_trailer_length(srtp_t session,
+ uint32_t use_mki,
+ uint32_t mki_index,
+ uint32_t *length);
+
+/**
+ * @brief srtp_set_stream_roc(session, ssrc, roc)
+ *
+ * Set the roll-over-counter on a session for a given SSRC
+ *
+ * returns err_status_ok on success, srtp_err_status_bad_param if there is no
+ * stream found
+ *
+ */
+srtp_err_status_t srtp_set_stream_roc(srtp_t session,
+ uint32_t ssrc,
+ uint32_t roc);
+
+/**
+ * @brief srtp_get_stream_roc(session, ssrc, roc)
+ *
+ * Get the roll-over-counter on a session for a given SSRC
+ *
+ * returns err_status_ok on success, srtp_err_status_bad_param if there is no
+ * stream found
+ *
+ */
+srtp_err_status_t srtp_get_stream_roc(srtp_t session,
+ uint32_t ssrc,
+ uint32_t *roc);
+
+/**
+ * @}
+ */
+
+/* in host order, so outside the #if */
+#define SRTCP_E_BIT 0x80000000
+
+/* for byte-access */
+#define SRTCP_E_BYTE_BIT 0x80
+#define SRTCP_INDEX_MASK 0x7fffffff
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SRTP_SRTP_H */
diff --git a/netwerk/srtp/src/include/srtp_priv.h b/netwerk/srtp/src/include/srtp_priv.h
new file mode 100644
index 0000000000..988efc4164
--- /dev/null
+++ b/netwerk/srtp/src/include/srtp_priv.h
@@ -0,0 +1,280 @@
+/*
+ * srtp_priv.h
+ *
+ * private internal data structures and functions for libSRTP
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 SRTP_PRIV_H
+#define SRTP_PRIV_H
+
+// Leave this as the top level import. Ensures the existence of defines
+#include "config.h"
+
+#include "srtp.h"
+#include "rdbx.h"
+#include "rdb.h"
+#include "integers.h"
+#include "cipher.h"
+#include "auth.h"
+#include "aes.h"
+#include "key.h"
+#include "crypto_kernel.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SRTP_VER_STRING PACKAGE_STRING
+#define SRTP_VERSION PACKAGE_VERSION
+
+typedef struct srtp_stream_ctx_t_ srtp_stream_ctx_t;
+typedef srtp_stream_ctx_t *srtp_stream_t;
+
+/*
+ * the following declarations are libSRTP internal functions
+ */
+
+/*
+ * srtp_get_stream(ssrc) returns a pointer to the stream corresponding
+ * to ssrc, or NULL if no stream exists for that ssrc
+ */
+srtp_stream_t srtp_get_stream(srtp_t srtp, uint32_t ssrc);
+
+/*
+ * srtp_stream_init_keys(s, k) (re)initializes the srtp_stream_t s by
+ * deriving all of the needed keys using the KDF and the key k.
+ */
+srtp_err_status_t srtp_stream_init_keys(srtp_stream_ctx_t *srtp,
+ srtp_master_key_t *master_key,
+ const unsigned int current_mki_index);
+
+/*
+ * srtp_stream_init_all_master_keys(s, k, m) (re)initializes the srtp_stream_t s
+ * by deriving all of the needed keys for all the master keys using the KDF and
+ * the keys from k.
+ */
+srtp_err_status_t srtp_steam_init_all_master_keys(
+ srtp_stream_ctx_t *srtp,
+ unsigned char *key,
+ srtp_master_key_t **keys,
+ const unsigned int max_master_keys);
+
+/*
+ * srtp_stream_init(s, p) initializes the srtp_stream_t s to
+ * use the policy at the location p
+ */
+srtp_err_status_t srtp_stream_init(srtp_stream_t srtp, const srtp_policy_t *p);
+
+/*
+ * libsrtp internal datatypes
+ */
+typedef enum direction_t {
+ dir_unknown = 0,
+ dir_srtp_sender = 1,
+ dir_srtp_receiver = 2
+} direction_t;
+
+/*
+ * srtp_session_keys_t will contain the encryption, hmac, salt keys
+ * for both SRTP and SRTCP. The session keys will also contain the
+ * MKI ID which is used to identify the session keys.
+ */
+typedef struct srtp_session_keys_t {
+ srtp_cipher_t *rtp_cipher;
+ srtp_cipher_t *rtp_xtn_hdr_cipher;
+ srtp_auth_t *rtp_auth;
+ srtp_cipher_t *rtcp_cipher;
+ srtp_auth_t *rtcp_auth;
+ uint8_t salt[SRTP_AEAD_SALT_LEN];
+ uint8_t c_salt[SRTP_AEAD_SALT_LEN];
+ uint8_t *mki_id;
+ unsigned int mki_size;
+ srtp_key_limit_ctx_t *limit;
+} srtp_session_keys_t;
+
+/*
+ * an srtp_stream_t has its own SSRC, encryption key, authentication
+ * key, sequence number, and replay database
+ *
+ * note that the keys might not actually be unique, in which case the
+ * srtp_cipher_t and srtp_auth_t pointers will point to the same structures
+ */
+typedef struct srtp_stream_ctx_t_ {
+ uint32_t ssrc;
+ srtp_session_keys_t *session_keys;
+ unsigned int num_master_keys;
+ srtp_rdbx_t rtp_rdbx;
+ srtp_sec_serv_t rtp_services;
+ srtp_rdb_t rtcp_rdb;
+ srtp_sec_serv_t rtcp_services;
+ direction_t direction;
+ int allow_repeat_tx;
+ srtp_ekt_stream_t ekt;
+ int *enc_xtn_hdr;
+ int enc_xtn_hdr_count;
+ uint32_t pending_roc;
+ struct srtp_stream_ctx_t_ *next; /* linked list of streams */
+} strp_stream_ctx_t_;
+
+/*
+ * an srtp_ctx_t holds a stream list and a service description
+ */
+typedef struct srtp_ctx_t_ {
+ struct srtp_stream_ctx_t_ *stream_list; /* linked list of streams */
+ struct srtp_stream_ctx_t_ *stream_template; /* act as template for other */
+ /* streams */
+ void *user_data; /* user custom data */
+} srtp_ctx_t_;
+
+/*
+ * srtp_hdr_t represents an RTP or SRTP header. The bit-fields in
+ * this structure should be declared "unsigned int" instead of
+ * "unsigned char", but doing so causes the MS compiler to not
+ * fully pack the bit fields.
+ *
+ * In this implementation, an srtp_hdr_t is assumed to be 32-bit aligned
+ *
+ * (note that this definition follows that of RFC 1889 Appendix A, but
+ * is not identical)
+ */
+
+#ifndef WORDS_BIGENDIAN
+
+typedef struct {
+ unsigned char cc : 4; /* CSRC count */
+ unsigned char x : 1; /* header extension flag */
+ unsigned char p : 1; /* padding flag */
+ unsigned char version : 2; /* protocol version */
+ unsigned char pt : 7; /* payload type */
+ unsigned char m : 1; /* marker bit */
+ uint16_t seq; /* sequence number */
+ uint32_t ts; /* timestamp */
+ uint32_t ssrc; /* synchronization source */
+} srtp_hdr_t;
+
+#else /* BIG_ENDIAN */
+
+typedef struct {
+ unsigned char version : 2; /* protocol version */
+ unsigned char p : 1; /* padding flag */
+ unsigned char x : 1; /* header extension flag */
+ unsigned char cc : 4; /* CSRC count */
+ unsigned char m : 1; /* marker bit */
+ unsigned char pt : 7; /* payload type */
+ uint16_t seq; /* sequence number */
+ uint32_t ts; /* timestamp */
+ uint32_t ssrc; /* synchronization source */
+} srtp_hdr_t;
+
+#endif
+
+typedef struct {
+ uint16_t profile_specific; /* profile-specific info */
+ uint16_t length; /* number of 32-bit words in extension */
+} srtp_hdr_xtnd_t;
+
+/*
+ * srtcp_hdr_t represents a secure rtcp header
+ *
+ * in this implementation, an srtcp header is assumed to be 32-bit
+ * alinged
+ */
+
+#ifndef WORDS_BIGENDIAN
+
+typedef struct {
+ unsigned char rc : 5; /* reception report count */
+ unsigned char p : 1; /* padding flag */
+ unsigned char version : 2; /* protocol version */
+ unsigned char pt : 8; /* payload type */
+ uint16_t len; /* length */
+ uint32_t ssrc; /* synchronization source */
+} srtcp_hdr_t;
+
+typedef struct {
+ unsigned int index : 31; /* srtcp packet index in network order! */
+ unsigned int e : 1; /* encrypted? 1=yes */
+ /* optional mikey/etc go here */
+ /* and then the variable-length auth tag */
+} srtcp_trailer_t;
+
+#else /* BIG_ENDIAN */
+
+typedef struct {
+ unsigned char version : 2; /* protocol version */
+ unsigned char p : 1; /* padding flag */
+ unsigned char rc : 5; /* reception report count */
+ unsigned char pt : 8; /* payload type */
+ uint16_t len; /* length */
+ uint32_t ssrc; /* synchronization source */
+} srtcp_hdr_t;
+
+typedef struct {
+ unsigned int e : 1; /* encrypted? 1=yes */
+ unsigned int index : 31; /* srtcp packet index */
+ /* optional mikey/etc go here */
+ /* and then the variable-length auth tag */
+} srtcp_trailer_t;
+
+#endif
+
+/*
+ * srtp_handle_event(srtp, srtm, evnt) calls the event handling
+ * function, if there is one.
+ *
+ * This macro is not included in the documentation as it is
+ * an internal-only function.
+ */
+
+#define srtp_handle_event(srtp, strm, evnt) \
+ if (srtp_event_handler) { \
+ srtp_event_data_t data; \
+ data.session = srtp; \
+ data.ssrc = ntohl(strm->ssrc); \
+ data.event = evnt; \
+ srtp_event_handler(&data); \
+ }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SRTP_PRIV_H */
diff --git a/netwerk/srtp/src/include/ut_sim.h b/netwerk/srtp/src/include/ut_sim.h
new file mode 100644
index 0000000000..e678b5fdf7
--- /dev/null
+++ b/netwerk/srtp/src/include/ut_sim.h
@@ -0,0 +1,83 @@
+/*
+ * ut-sim.h
+ *
+ * an unreliable transport simulator
+ * (for testing replay databases and suchlike)
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 UT_SIM_H
+#define UT_SIM_H
+
+#include "integers.h" /* for uint32_t */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define UT_BUF 160 /* maximum amount of packet reorder */
+
+typedef struct {
+ uint32_t index;
+ uint32_t buffer[UT_BUF];
+} ut_connection;
+
+/*
+ * ut_init(&u) initializes the ut_connection
+ *
+ * this function should always be the first one called on a new
+ * ut_connection
+ */
+
+void ut_init(ut_connection *utc);
+
+/*
+ * ut_next_index(&u) returns the next index from the simulated
+ * unreliable connection
+ */
+
+uint32_t ut_next_index(ut_connection *utc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UT_SIM_H */
diff --git a/netwerk/srtp/src/moz.build b/netwerk/srtp/src/moz.build
new file mode 100644
index 0000000000..a2e5a6d3e5
--- /dev/null
+++ b/netwerk/srtp/src/moz.build
@@ -0,0 +1,76 @@
+# -*- 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/.
+
+UNIFIED_SOURCES += [
+ 'crypto/cipher/aes_gcm_nss.c',
+ 'crypto/cipher/aes_icm_nss.c',
+ 'crypto/cipher/cipher.c',
+ 'crypto/cipher/null_cipher.c',
+ 'crypto/hash/auth.c',
+ 'crypto/hash/hmac.c',
+ 'crypto/hash/null_auth.c',
+ 'crypto/hash/sha1.c',
+ 'crypto/kernel/alloc.c',
+ 'crypto/kernel/crypto_kernel.c',
+ 'crypto/kernel/err.c',
+ 'crypto/kernel/key.c',
+ 'crypto/math/datatypes.c',
+ 'crypto/math/stat.c',
+ 'crypto/replay/rdb.c',
+ 'crypto/replay/rdbx.c',
+ 'crypto/replay/ut_sim.c',
+ 'srtp/ekt.c',
+ 'srtp/srtp.c',
+]
+
+Library('nksrtp_s')
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ 'crypto/include',
+ 'include',
+]
+
+DEFINES['PACKAGE_STRING'] = '"libsrtp2 2.2.0-pre"'
+DEFINES['PACKAGE_VERSION'] = '"2.2.0-pre"'
+
+# This is needed to enable the be32_to_cpu and be64_to_cpu
+# macro's in datatypes.h
+DEFINES['HAVE_CONFIG_H'] = 1
+
+# We know stdint.h will define uint8/16/32/64_t, so we don't need
+# to define SIZEOF_UNSIGNED_LONG/SIZEOF_UNSIGNED_LONG_LONG
+for var in ('HAVE_STDLIB_H', 'HAVE_UINT8_T', 'HAVE_UINT16_T',
+ 'HAVE_INT32_T', 'HAVE_UINT32_T', 'HAVE_UINT64_T'):
+ DEFINES[var] = 1
+
+# Enable AES-GCM cipher suite in libsrtp
+DEFINES['GCM'] = 1
+# Let libsrtp use NSS instead of build-in crypto
+DEFINES['NSS'] = 1
+
+# XXX while arm is not a CISC architecture, the code guarded by CPU_RISC makes
+# (at least) the AES ciphers fail their self-tests on ARM, so for now we're
+# falling back to the (presumably) slower-on-this-architecture but working
+# code path. https://bugzilla.mozilla.org/show_bug.cgi?id=822380 has been filed
+# to make the right and more performant fix and push it back upstream.
+if CONFIG['CPU_ARCH'] in ('arm', 'x86', 'x86_64', 'mips', 'mips64'):
+ DEFINES['CPU_CISC'] = 1
+else:
+ # best guess
+ DEFINES['CPU_RISC'] = 1
+
+if CONFIG['CPU_ARCH'] in ('x86', 'x86_64'):
+ DEFINES['HAVE_X86'] = True
+
+if CONFIG['OS_TARGET'] == 'WINNT':
+ DEFINES['HAVE_WINSOCK2_H'] = True
+ DEFINES['inline'] = '__inline'
+else:
+ DEFINES['HAVE_NETINET_IN_H'] = 1
diff --git a/netwerk/srtp/src/srtp/ekt.c b/netwerk/srtp/src/srtp/ekt.c
new file mode 100644
index 0000000000..f09dc58580
--- /dev/null
+++ b/netwerk/srtp/src/srtp/ekt.c
@@ -0,0 +1,281 @@
+/*
+ * ekt.c
+ *
+ * Encrypted Key Transport for SRTP
+ *
+ * David McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 "srtp_priv.h"
+#include "err.h"
+#include "ekt.h"
+
+extern srtp_debug_module_t mod_srtp;
+
+/*
+ * The EKT Authentication Tag format.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * : Base Authentication Tag :
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * : Encrypted Master Key :
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Rollover Counter |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Initial Sequence Number | Security Parameter Index |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ */
+
+#define EKT_OCTETS_AFTER_BASE_TAG 24
+#define EKT_OCTETS_AFTER_EMK 8
+#define EKT_OCTETS_AFTER_ROC 4
+#define EKT_SPI_LEN 2
+
+unsigned srtp_ekt_octets_after_base_tag(srtp_ekt_stream_t ekt)
+{
+ /*
+ * if the pointer ekt is NULL, then EKT is not in effect, so we
+ * indicate this by returning zero
+ */
+ if (!ekt)
+ return 0;
+
+ switch (ekt->data->ekt_cipher_type) {
+ case SRTP_EKT_CIPHER_AES_128_ECB:
+ return 16 + EKT_OCTETS_AFTER_EMK;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static inline srtp_ekt_spi_t srtcp_packet_get_ekt_spi(
+ const uint8_t *packet_start,
+ unsigned pkt_octet_len)
+{
+ const uint8_t *spi_location;
+
+ spi_location = packet_start + (pkt_octet_len - EKT_SPI_LEN);
+
+ return *((const srtp_ekt_spi_t *)spi_location);
+}
+
+static inline uint32_t srtcp_packet_get_ekt_roc(const uint8_t *packet_start,
+ unsigned pkt_octet_len)
+{
+ const uint8_t *roc_location;
+
+ roc_location = packet_start + (pkt_octet_len - EKT_OCTETS_AFTER_ROC);
+
+ return *((const uint32_t *)roc_location);
+}
+
+static inline const uint8_t *srtcp_packet_get_emk_location(
+ const uint8_t *packet_start,
+ unsigned pkt_octet_len)
+{
+ const uint8_t *location;
+
+ location = packet_start + (pkt_octet_len - EKT_OCTETS_AFTER_BASE_TAG);
+
+ return location;
+}
+
+srtp_err_status_t srtp_ekt_alloc(srtp_ekt_stream_t *stream_data,
+ srtp_ekt_policy_t policy)
+{
+ /*
+ * if the policy pointer is NULL, then EKT is not in use
+ * so we just set the EKT stream data pointer to NULL
+ */
+ if (!policy) {
+ *stream_data = NULL;
+ return srtp_err_status_ok;
+ }
+
+ /* TODO */
+ *stream_data = NULL;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_ekt_stream_init_from_policy(
+ srtp_ekt_stream_t stream_data,
+ srtp_ekt_policy_t policy)
+{
+ if (!stream_data)
+ return srtp_err_status_ok;
+
+ return srtp_err_status_ok;
+}
+
+void aes_decrypt_with_raw_key(void *ciphertext, const void *key, int key_len)
+{
+#ifndef GCM
+ // FIXME: need to get this working through the crypto module interface
+ srtp_aes_expanded_key_t expanded_key;
+
+ srtp_aes_expand_decryption_key(key, key_len, &expanded_key);
+ srtp_aes_decrypt(ciphertext, &expanded_key);
+#endif
+}
+
+/*
+ * The function srtp_stream_init_from_ekt() initializes a stream using
+ * the EKT data from an SRTCP trailer.
+ */
+
+srtp_err_status_t srtp_stream_init_from_ekt(srtp_stream_t stream,
+ const void *srtcp_hdr,
+ unsigned pkt_octet_len)
+{
+ srtp_err_status_t err;
+ const uint8_t *master_key;
+ srtp_policy_t srtp_policy;
+ uint32_t roc;
+
+ /*
+ * NOTE: at present, we only support a single ekt_policy at a time.
+ */
+ if (stream->ekt->data->spi !=
+ srtcp_packet_get_ekt_spi(srtcp_hdr, pkt_octet_len))
+ return srtp_err_status_no_ctx;
+
+ if (stream->ekt->data->ekt_cipher_type != SRTP_EKT_CIPHER_AES_128_ECB)
+ return srtp_err_status_bad_param;
+
+ /* decrypt the Encrypted Master Key field */
+ master_key = srtcp_packet_get_emk_location(srtcp_hdr, pkt_octet_len);
+ /* FIX!? This decrypts the master key in-place, and never uses it */
+ /* FIX!? It's also passing to ekt_dec_key (which is an aes_expanded_key_t)
+ * to a function which expects a raw (unexpanded) key */
+ aes_decrypt_with_raw_key((void *)master_key,
+ &stream->ekt->data->ekt_dec_key, 16);
+
+ /* set the SRTP ROC */
+ roc = srtcp_packet_get_ekt_roc(srtcp_hdr, pkt_octet_len);
+ err = srtp_rdbx_set_roc(&stream->rtp_rdbx, roc);
+ if (err)
+ return err;
+
+ err = srtp_stream_init(stream, &srtp_policy);
+ if (err)
+ return err;
+
+ return srtp_err_status_ok;
+}
+
+void srtp_ekt_write_data(srtp_ekt_stream_t ekt,
+ uint8_t *base_tag,
+ unsigned base_tag_len,
+ int *packet_len,
+ srtp_xtd_seq_num_t pkt_index)
+{
+ uint32_t roc;
+ uint16_t isn;
+ unsigned emk_len;
+ uint8_t *packet;
+
+ /* if the pointer ekt is NULL, then EKT is not in effect */
+ if (!ekt) {
+ debug_print(mod_srtp, "EKT not in use", NULL);
+ return;
+ }
+
+ /* write zeros into the location of the base tag */
+ octet_string_set_to_zero(base_tag, base_tag_len);
+ packet = base_tag + base_tag_len;
+
+ /* copy encrypted master key into packet */
+ emk_len = srtp_ekt_octets_after_base_tag(ekt);
+ memcpy(packet, ekt->encrypted_master_key, emk_len);
+ debug_print(mod_srtp, "writing EKT EMK: %s,",
+ srtp_octet_string_hex_string(packet, emk_len));
+ packet += emk_len;
+
+ /* copy ROC into packet */
+ roc = (uint32_t)(pkt_index >> 16);
+ *((uint32_t *)packet) = be32_to_cpu(roc);
+ debug_print(mod_srtp, "writing EKT ROC: %s,",
+ srtp_octet_string_hex_string(packet, sizeof(roc)));
+ packet += sizeof(roc);
+
+ /* copy ISN into packet */
+ isn = (uint16_t)pkt_index;
+ *((uint16_t *)packet) = htons(isn);
+ debug_print(mod_srtp, "writing EKT ISN: %s,",
+ srtp_octet_string_hex_string(packet, sizeof(isn)));
+ packet += sizeof(isn);
+
+ /* copy SPI into packet */
+ *((uint16_t *)packet) = htons(ekt->data->spi);
+ debug_print(mod_srtp, "writing EKT SPI: %s,",
+ srtp_octet_string_hex_string(packet, sizeof(ekt->data->spi)));
+
+ /* increase packet length appropriately */
+ *packet_len += EKT_OCTETS_AFTER_EMK + emk_len;
+}
+
+/*
+ * The function call srtcp_ekt_trailer(ekt, auth_len, auth_tag )
+ *
+ * If the pointer ekt is NULL, then the other inputs are unaffected.
+ *
+ * auth_tag is a pointer to the pointer to the location of the
+ * authentication tag in the packet. If EKT is in effect, then the
+ * auth_tag pointer is set to the location
+ */
+
+void srtcp_ekt_trailer(srtp_ekt_stream_t ekt,
+ unsigned *auth_len,
+ void **auth_tag,
+ void *tag_copy)
+{
+ /*
+ * if there is no EKT policy, then the other inputs are unaffected
+ */
+ if (!ekt)
+ return;
+
+ /* copy auth_tag into temporary location */
+}
diff --git a/netwerk/srtp/src/srtp/srtp.c b/netwerk/srtp/src/srtp/srtp.c
new file mode 100644
index 0000000000..5d9da7b2ec
--- /dev/null
+++ b/netwerk/srtp/src/srtp/srtp.c
@@ -0,0 +1,4730 @@
+/*
+ * srtp.c
+ *
+ * the secure real-time transport protocol
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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.
+ *
+ */
+
+// Leave this as the top level import. Ensures the existence of defines
+#include "config.h"
+
+#include "srtp_priv.h"
+#include "crypto_types.h"
+#include "err.h"
+#include "ekt.h" /* for SRTP Encrypted Key Transport */
+#include "alloc.h" /* for srtp_crypto_alloc() */
+
+#ifdef GCM
+#include "aes_gcm.h" /* for AES GCM mode */
+#endif
+
+#ifdef OPENSSL_KDF
+#include <openssl/kdf.h>
+#include "aes_icm_ext.h"
+#endif
+
+#include <limits.h>
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#elif defined(HAVE_WINSOCK2_H)
+#include <winsock2.h>
+#endif
+
+/* the debug module for srtp */
+srtp_debug_module_t mod_srtp = {
+ 0, /* debugging is off by default */
+ "srtp" /* printable name for module */
+};
+
+#define octets_in_rtp_header 12
+#define uint32s_in_rtp_header 3
+#define octets_in_rtcp_header 8
+#define uint32s_in_rtcp_header 2
+#define octets_in_rtp_extn_hdr 4
+
+static srtp_err_status_t srtp_validate_rtp_header(void *rtp_hdr,
+ int *pkt_octet_len)
+{
+ if (*pkt_octet_len < octets_in_rtp_header)
+ return srtp_err_status_bad_param;
+
+ srtp_hdr_t *hdr = (srtp_hdr_t *)rtp_hdr;
+
+ /* Check RTP header length */
+ int rtp_header_len = octets_in_rtp_header + 4 * hdr->cc;
+ if (hdr->x == 1)
+ rtp_header_len += octets_in_rtp_extn_hdr;
+
+ if (*pkt_octet_len < rtp_header_len)
+ return srtp_err_status_bad_param;
+
+ /* Verifing profile length. */
+ if (hdr->x == 1) {
+ srtp_hdr_xtnd_t *xtn_hdr =
+ (srtp_hdr_xtnd_t *)((uint32_t *)hdr + uint32s_in_rtp_header +
+ hdr->cc);
+ int profile_len = ntohs(xtn_hdr->length);
+ rtp_header_len += profile_len * 4;
+ /* profile length counts the number of 32-bit words */
+ if (*pkt_octet_len < rtp_header_len)
+ return srtp_err_status_bad_param;
+ }
+ return srtp_err_status_ok;
+}
+
+const char *srtp_get_version_string()
+{
+ /*
+ * Simply return the autotools generated string
+ */
+ return SRTP_VER_STRING;
+}
+
+unsigned int srtp_get_version()
+{
+ unsigned int major = 0, minor = 0, micro = 0;
+ unsigned int rv = 0;
+ int parse_rv;
+
+ /*
+ * Parse the autotools generated version
+ */
+ parse_rv = sscanf(SRTP_VERSION, "%u.%u.%u", &major, &minor, &micro);
+ if (parse_rv != 3) {
+ /*
+ * We're expected to parse all 3 version levels.
+ * If not, then this must not be an official release.
+ * Return all zeros on the version
+ */
+ return (0);
+ }
+
+ /*
+ * We allow 8 bits for the major and minor, while
+ * allowing 16 bits for the micro. 16 bits for the micro
+ * may be beneficial for a continuous delivery model
+ * in the future.
+ */
+ rv |= (major & 0xFF) << 24;
+ rv |= (minor & 0xFF) << 16;
+ rv |= micro & 0xFF;
+ return rv;
+}
+
+srtp_err_status_t srtp_stream_dealloc(srtp_stream_ctx_t *stream,
+ const srtp_stream_ctx_t *stream_template)
+{
+ srtp_err_status_t status;
+ unsigned int i = 0;
+ srtp_session_keys_t *session_keys = NULL;
+ srtp_session_keys_t *template_session_keys = NULL;
+
+ /*
+ * we use a conservative deallocation strategy - if any deallocation
+ * fails, then we report that fact without trying to deallocate
+ * anything else
+ */
+ if (stream->session_keys) {
+ for (i = 0; i < stream->num_master_keys; i++) {
+ session_keys = &stream->session_keys[i];
+
+ if (stream_template &&
+ stream->num_master_keys == stream_template->num_master_keys) {
+ template_session_keys = &stream_template->session_keys[i];
+ } else {
+ template_session_keys = NULL;
+ }
+
+ /*
+ * deallocate cipher, if it is not the same as that in template
+ */
+ if (template_session_keys &&
+ session_keys->rtp_cipher == template_session_keys->rtp_cipher) {
+ /* do nothing */
+ } else if (session_keys->rtp_cipher) {
+ status = srtp_cipher_dealloc(session_keys->rtp_cipher);
+ if (status)
+ return status;
+ }
+
+ /*
+ * deallocate auth function, if it is not the same as that in
+ * template
+ */
+ if (template_session_keys &&
+ session_keys->rtp_auth == template_session_keys->rtp_auth) {
+ /* do nothing */
+ } else if (session_keys->rtp_auth) {
+ status = srtp_auth_dealloc(session_keys->rtp_auth);
+ if (status)
+ return status;
+ }
+
+ if (template_session_keys &&
+ session_keys->rtp_xtn_hdr_cipher ==
+ template_session_keys->rtp_xtn_hdr_cipher) {
+ /* do nothing */
+ } else if (session_keys->rtp_xtn_hdr_cipher) {
+ status = srtp_cipher_dealloc(session_keys->rtp_xtn_hdr_cipher);
+ if (status)
+ return status;
+ }
+
+ /*
+ * deallocate rtcp cipher, if it is not the same as that in
+ * template
+ */
+ if (template_session_keys &&
+ session_keys->rtcp_cipher ==
+ template_session_keys->rtcp_cipher) {
+ /* do nothing */
+ } else if (session_keys->rtcp_cipher) {
+ status = srtp_cipher_dealloc(session_keys->rtcp_cipher);
+ if (status)
+ return status;
+ }
+
+ /*
+ * deallocate rtcp auth function, if it is not the same as that in
+ * template
+ */
+ if (template_session_keys &&
+ session_keys->rtcp_auth == template_session_keys->rtcp_auth) {
+ /* do nothing */
+ } else if (session_keys->rtcp_auth) {
+ status = srtp_auth_dealloc(session_keys->rtcp_auth);
+ if (status)
+ return status;
+ }
+
+ /*
+ * zeroize the salt value
+ */
+ octet_string_set_to_zero(session_keys->salt, SRTP_AEAD_SALT_LEN);
+ octet_string_set_to_zero(session_keys->c_salt, SRTP_AEAD_SALT_LEN);
+
+ if (session_keys->mki_id) {
+ octet_string_set_to_zero(session_keys->mki_id,
+ session_keys->mki_size);
+ srtp_crypto_free(session_keys->mki_id);
+ session_keys->mki_id = NULL;
+ }
+
+ /*
+ * deallocate key usage limit, if it is not the same as that in
+ * template
+ */
+ if (template_session_keys &&
+ session_keys->limit == template_session_keys->limit) {
+ /* do nothing */
+ } else if (session_keys->limit) {
+ srtp_crypto_free(session_keys->limit);
+ }
+ }
+ srtp_crypto_free(stream->session_keys);
+ }
+
+ status = srtp_rdbx_dealloc(&stream->rtp_rdbx);
+ if (status)
+ return status;
+
+ /* DAM - need to deallocate EKT here */
+
+ if (stream_template &&
+ stream->enc_xtn_hdr == stream_template->enc_xtn_hdr) {
+ /* do nothing */
+ } else if (stream->enc_xtn_hdr) {
+ srtp_crypto_free(stream->enc_xtn_hdr);
+ }
+
+ /* deallocate srtp stream context */
+ srtp_crypto_free(stream);
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_stream_alloc(srtp_stream_ctx_t **str_ptr,
+ const srtp_policy_t *p)
+{
+ srtp_stream_ctx_t *str;
+ srtp_err_status_t stat;
+ unsigned int i = 0;
+ srtp_session_keys_t *session_keys = NULL;
+
+ /*
+ * This function allocates the stream context, rtp and rtcp ciphers
+ * and auth functions, and key limit structure. If there is a
+ * failure during allocation, we free all previously allocated
+ * memory and return a failure code. The code could probably
+ * be improved, but it works and should be clear.
+ */
+
+ /* allocate srtp stream and set str_ptr */
+ str = (srtp_stream_ctx_t *)srtp_crypto_alloc(sizeof(srtp_stream_ctx_t));
+ if (str == NULL)
+ return srtp_err_status_alloc_fail;
+
+ *str_ptr = str;
+
+ /*
+ *To keep backwards API compatible if someone is using multiple master
+ * keys then key should be set to NULL
+ */
+ if (p->key != NULL) {
+ str->num_master_keys = 1;
+ } else {
+ str->num_master_keys = p->num_master_keys;
+ }
+
+ str->session_keys = (srtp_session_keys_t *)srtp_crypto_alloc(
+ sizeof(srtp_session_keys_t) * str->num_master_keys);
+
+ if (str->session_keys == NULL) {
+ srtp_stream_dealloc(str, NULL);
+ return srtp_err_status_alloc_fail;
+ }
+
+ for (i = 0; i < str->num_master_keys; i++) {
+ session_keys = &str->session_keys[i];
+
+ /* allocate cipher */
+ stat = srtp_crypto_kernel_alloc_cipher(
+ p->rtp.cipher_type, &session_keys->rtp_cipher,
+ p->rtp.cipher_key_len, p->rtp.auth_tag_len);
+ if (stat) {
+ srtp_stream_dealloc(str, NULL);
+ return stat;
+ }
+
+ /* allocate auth function */
+ stat = srtp_crypto_kernel_alloc_auth(
+ p->rtp.auth_type, &session_keys->rtp_auth, p->rtp.auth_key_len,
+ p->rtp.auth_tag_len);
+ if (stat) {
+ srtp_stream_dealloc(str, NULL);
+ return stat;
+ }
+
+ /*
+ * ...and now the RTCP-specific initialization - first, allocate
+ * the cipher
+ */
+ stat = srtp_crypto_kernel_alloc_cipher(
+ p->rtcp.cipher_type, &session_keys->rtcp_cipher,
+ p->rtcp.cipher_key_len, p->rtcp.auth_tag_len);
+ if (stat) {
+ srtp_stream_dealloc(str, NULL);
+ return stat;
+ }
+
+ /* allocate auth function */
+ stat = srtp_crypto_kernel_alloc_auth(
+ p->rtcp.auth_type, &session_keys->rtcp_auth, p->rtcp.auth_key_len,
+ p->rtcp.auth_tag_len);
+ if (stat) {
+ srtp_stream_dealloc(str, NULL);
+ return stat;
+ }
+
+ session_keys->mki_id = NULL;
+
+ /* allocate key limit structure */
+ session_keys->limit = (srtp_key_limit_ctx_t *)srtp_crypto_alloc(
+ sizeof(srtp_key_limit_ctx_t));
+ if (session_keys->limit == NULL) {
+ srtp_stream_dealloc(str, NULL);
+ return srtp_err_status_alloc_fail;
+ }
+ }
+
+ /* allocate ekt data associated with stream */
+ stat = srtp_ekt_alloc(&str->ekt, p->ekt);
+ if (stat) {
+ srtp_stream_dealloc(str, NULL);
+ return stat;
+ }
+
+ if (p->enc_xtn_hdr && p->enc_xtn_hdr_count > 0) {
+ srtp_cipher_type_id_t enc_xtn_hdr_cipher_type;
+ int enc_xtn_hdr_cipher_key_len;
+
+ str->enc_xtn_hdr = (int *)srtp_crypto_alloc(p->enc_xtn_hdr_count *
+ sizeof(p->enc_xtn_hdr[0]));
+ if (!str->enc_xtn_hdr) {
+ srtp_stream_dealloc(str, NULL);
+ return srtp_err_status_alloc_fail;
+ }
+ memcpy(str->enc_xtn_hdr, p->enc_xtn_hdr,
+ p->enc_xtn_hdr_count * sizeof(p->enc_xtn_hdr[0]));
+ str->enc_xtn_hdr_count = p->enc_xtn_hdr_count;
+
+ /*
+ * For GCM ciphers, the corresponding ICM cipher is used for header
+ * extensions encryption.
+ */
+ switch (p->rtp.cipher_type) {
+ case SRTP_AES_GCM_128:
+ enc_xtn_hdr_cipher_type = SRTP_AES_ICM_128;
+ enc_xtn_hdr_cipher_key_len = SRTP_AES_ICM_128_KEY_LEN_WSALT;
+ break;
+ case SRTP_AES_GCM_256:
+ enc_xtn_hdr_cipher_type = SRTP_AES_ICM_256;
+ enc_xtn_hdr_cipher_key_len = SRTP_AES_ICM_256_KEY_LEN_WSALT;
+ break;
+ default:
+ enc_xtn_hdr_cipher_type = p->rtp.cipher_type;
+ enc_xtn_hdr_cipher_key_len = p->rtp.cipher_key_len;
+ break;
+ }
+
+ for (i = 0; i < str->num_master_keys; i++) {
+ session_keys = &str->session_keys[i];
+
+ /* allocate cipher for extensions header encryption */
+ stat = srtp_crypto_kernel_alloc_cipher(
+ enc_xtn_hdr_cipher_type, &session_keys->rtp_xtn_hdr_cipher,
+ enc_xtn_hdr_cipher_key_len, 0);
+ if (stat) {
+ srtp_stream_dealloc(str, NULL);
+ return stat;
+ }
+ }
+ } else {
+ for (i = 0; i < str->num_master_keys; i++) {
+ session_keys = &str->session_keys[i];
+ session_keys->rtp_xtn_hdr_cipher = NULL;
+ }
+
+ str->enc_xtn_hdr = NULL;
+ str->enc_xtn_hdr_count = 0;
+ }
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp_stream_clone(stream_template, new) allocates a new stream and
+ * initializes it using the cipher and auth of the stream_template
+ *
+ * the only unique data in a cloned stream is the replay database and
+ * the SSRC
+ */
+
+srtp_err_status_t srtp_stream_clone(const srtp_stream_ctx_t *stream_template,
+ uint32_t ssrc,
+ srtp_stream_ctx_t **str_ptr)
+{
+ srtp_err_status_t status;
+ srtp_stream_ctx_t *str;
+ unsigned int i = 0;
+ srtp_session_keys_t *session_keys = NULL;
+ const srtp_session_keys_t *template_session_keys = NULL;
+
+ debug_print(mod_srtp, "cloning stream (SSRC: 0x%08x)", ntohl(ssrc));
+
+ /* allocate srtp stream and set str_ptr */
+ str = (srtp_stream_ctx_t *)srtp_crypto_alloc(sizeof(srtp_stream_ctx_t));
+ if (str == NULL)
+ return srtp_err_status_alloc_fail;
+ *str_ptr = str;
+
+ str->num_master_keys = stream_template->num_master_keys;
+ str->session_keys = (srtp_session_keys_t *)srtp_crypto_alloc(
+ sizeof(srtp_session_keys_t) * str->num_master_keys);
+
+ if (str->session_keys == NULL) {
+ srtp_stream_dealloc(*str_ptr, stream_template);
+ *str_ptr = NULL;
+ return srtp_err_status_alloc_fail;
+ }
+
+ for (i = 0; i < stream_template->num_master_keys; i++) {
+ session_keys = &str->session_keys[i];
+ template_session_keys = &stream_template->session_keys[i];
+
+ /* set cipher and auth pointers to those of the template */
+ session_keys->rtp_cipher = template_session_keys->rtp_cipher;
+ session_keys->rtp_auth = template_session_keys->rtp_auth;
+ session_keys->rtp_xtn_hdr_cipher =
+ template_session_keys->rtp_xtn_hdr_cipher;
+ session_keys->rtcp_cipher = template_session_keys->rtcp_cipher;
+ session_keys->rtcp_auth = template_session_keys->rtcp_auth;
+ session_keys->mki_size = template_session_keys->mki_size;
+
+ if (template_session_keys->mki_size == 0) {
+ session_keys->mki_id = NULL;
+ } else {
+ session_keys->mki_id =
+ srtp_crypto_alloc(template_session_keys->mki_size);
+
+ if (session_keys->mki_id == NULL) {
+ srtp_stream_dealloc(*str_ptr, stream_template);
+ *str_ptr = NULL;
+ return srtp_err_status_init_fail;
+ }
+ memcpy(session_keys->mki_id, template_session_keys->mki_id,
+ session_keys->mki_size);
+ }
+ /* Copy the salt values */
+ memcpy(session_keys->salt, template_session_keys->salt,
+ SRTP_AEAD_SALT_LEN);
+ memcpy(session_keys->c_salt, template_session_keys->c_salt,
+ SRTP_AEAD_SALT_LEN);
+
+ /* set key limit to point to that of the template */
+ status = srtp_key_limit_clone(template_session_keys->limit,
+ &session_keys->limit);
+ if (status) {
+ srtp_stream_dealloc(*str_ptr, stream_template);
+ *str_ptr = NULL;
+ return status;
+ }
+ }
+
+ /* initialize replay databases */
+ status = srtp_rdbx_init(
+ &str->rtp_rdbx, srtp_rdbx_get_window_size(&stream_template->rtp_rdbx));
+ if (status) {
+ srtp_stream_dealloc(*str_ptr, stream_template);
+ *str_ptr = NULL;
+ return status;
+ }
+ srtp_rdb_init(&str->rtcp_rdb);
+ str->allow_repeat_tx = stream_template->allow_repeat_tx;
+
+ /* set ssrc to that provided */
+ str->ssrc = ssrc;
+
+ /* reset pending ROC */
+ str->pending_roc = 0;
+
+ /* set direction and security services */
+ str->direction = stream_template->direction;
+ str->rtp_services = stream_template->rtp_services;
+ str->rtcp_services = stream_template->rtcp_services;
+
+ /* set pointer to EKT data associated with stream */
+ str->ekt = stream_template->ekt;
+
+ /* copy information about extensions header encryption */
+ str->enc_xtn_hdr = stream_template->enc_xtn_hdr;
+ str->enc_xtn_hdr_count = stream_template->enc_xtn_hdr_count;
+
+ /* defensive coding */
+ str->next = NULL;
+ return srtp_err_status_ok;
+}
+
+/*
+ * key derivation functions, internal to libSRTP
+ *
+ * srtp_kdf_t is a key derivation context
+ *
+ * srtp_kdf_init(&kdf, cipher_id, k, keylen) initializes kdf to use cipher
+ * described by cipher_id, with the master key k with length in octets keylen.
+ *
+ * srtp_kdf_generate(&kdf, l, kl, keylen) derives the key
+ * corresponding to label l and puts it into kl; the length
+ * of the key in octets is provided as keylen. this function
+ * should be called once for each subkey that is derived.
+ *
+ * srtp_kdf_clear(&kdf) zeroizes and deallocates the kdf state
+ */
+
+typedef enum {
+ label_rtp_encryption = 0x00,
+ label_rtp_msg_auth = 0x01,
+ label_rtp_salt = 0x02,
+ label_rtcp_encryption = 0x03,
+ label_rtcp_msg_auth = 0x04,
+ label_rtcp_salt = 0x05,
+ label_rtp_header_encryption = 0x06,
+ label_rtp_header_salt = 0x07
+} srtp_prf_label;
+
+#define MAX_SRTP_KEY_LEN 256
+
+#if defined(OPENSSL) && defined(OPENSSL_KDF)
+#define MAX_SRTP_AESKEY_LEN 32
+#define MAX_SRTP_SALT_LEN 14
+
+/*
+ * srtp_kdf_t represents a key derivation function. The SRTP
+ * default KDF is the only one implemented at present.
+ */
+typedef struct {
+ uint8_t master_key[MAX_SRTP_AESKEY_LEN];
+ uint8_t master_salt[MAX_SRTP_SALT_LEN];
+ const EVP_CIPHER *evp;
+} srtp_kdf_t;
+
+static srtp_err_status_t srtp_kdf_init(srtp_kdf_t *kdf,
+ const uint8_t *key,
+ int key_len,
+ int salt_len)
+{
+ memset(kdf, 0x0, sizeof(srtp_kdf_t));
+
+ /* The NULL cipher has zero key length */
+ if (key_len == 0)
+ return srtp_err_status_ok;
+
+ if ((key_len > MAX_SRTP_AESKEY_LEN) || (salt_len > MAX_SRTP_SALT_LEN)) {
+ return srtp_err_status_bad_param;
+ }
+ switch (key_len) {
+ case SRTP_AES_256_KEYSIZE:
+ kdf->evp = EVP_aes_256_ctr();
+ break;
+ case SRTP_AES_192_KEYSIZE:
+ kdf->evp = EVP_aes_192_ctr();
+ break;
+ case SRTP_AES_128_KEYSIZE:
+ kdf->evp = EVP_aes_128_ctr();
+ break;
+ default:
+ return srtp_err_status_bad_param;
+ break;
+ }
+ memcpy(kdf->master_key, key, key_len);
+ memcpy(kdf->master_salt, key + key_len, salt_len);
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_kdf_generate(srtp_kdf_t *kdf,
+ srtp_prf_label label,
+ uint8_t *key,
+ unsigned int length)
+{
+ int ret;
+
+ /* The NULL cipher will not have an EVP */
+ if (!kdf->evp)
+ return srtp_err_status_ok;
+ octet_string_set_to_zero(key, length);
+
+ /*
+ * Invoke the OpenSSL SRTP KDF function
+ * This is useful if OpenSSL is in FIPS mode and FIP
+ * compliance is required for SRTP.
+ */
+ ret = kdf_srtp(kdf->evp, (char *)&kdf->master_key,
+ (char *)&kdf->master_salt, NULL, NULL, label, (char *)key);
+ if (ret == -1) {
+ return (srtp_err_status_algo_fail);
+ }
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_kdf_clear(srtp_kdf_t *kdf)
+{
+ octet_string_set_to_zero(kdf->master_key, MAX_SRTP_AESKEY_LEN);
+ octet_string_set_to_zero(kdf->master_salt, MAX_SRTP_SALT_LEN);
+ kdf->evp = NULL;
+
+ return srtp_err_status_ok;
+}
+
+#else /* if OPENSSL_KDF */
+
+/*
+ * srtp_kdf_t represents a key derivation function. The SRTP
+ * default KDF is the only one implemented at present.
+ */
+typedef struct {
+ srtp_cipher_t *cipher; /* cipher used for key derivation */
+} srtp_kdf_t;
+
+static srtp_err_status_t srtp_kdf_init(srtp_kdf_t *kdf,
+ const uint8_t *key,
+ int key_len)
+{
+ srtp_cipher_type_id_t cipher_id;
+ switch (key_len) {
+ case SRTP_AES_ICM_256_KEY_LEN_WSALT:
+ cipher_id = SRTP_AES_ICM_256;
+ break;
+ case SRTP_AES_ICM_192_KEY_LEN_WSALT:
+ cipher_id = SRTP_AES_ICM_192;
+ break;
+ case SRTP_AES_ICM_128_KEY_LEN_WSALT:
+ cipher_id = SRTP_AES_ICM_128;
+ break;
+ default:
+ return srtp_err_status_bad_param;
+ break;
+ }
+
+ srtp_err_status_t stat;
+ stat = srtp_crypto_kernel_alloc_cipher(cipher_id, &kdf->cipher, key_len, 0);
+ if (stat)
+ return stat;
+
+ stat = srtp_cipher_init(kdf->cipher, key);
+ if (stat) {
+ srtp_cipher_dealloc(kdf->cipher);
+ return stat;
+ }
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_kdf_generate(srtp_kdf_t *kdf,
+ srtp_prf_label label,
+ uint8_t *key,
+ unsigned int length)
+{
+ srtp_err_status_t status;
+ v128_t nonce;
+
+ /* set eigth octet of nonce to <label>, set the rest of it to zero */
+ v128_set_to_zero(&nonce);
+ nonce.v8[7] = label;
+
+ status = srtp_cipher_set_iv(kdf->cipher, (uint8_t *)&nonce,
+ srtp_direction_encrypt);
+ if (status)
+ return status;
+
+ /* generate keystream output */
+ octet_string_set_to_zero(key, length);
+ status = srtp_cipher_encrypt(kdf->cipher, key, &length);
+ if (status)
+ return status;
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_kdf_clear(srtp_kdf_t *kdf)
+{
+ srtp_err_status_t status;
+ status = srtp_cipher_dealloc(kdf->cipher);
+ if (status)
+ return status;
+ kdf->cipher = NULL;
+ return srtp_err_status_ok;
+}
+#endif /* else OPENSSL_KDF */
+
+/*
+ * end of key derivation functions
+ */
+
+/* Get the base key length corresponding to a given combined key+salt
+ * length for the given cipher.
+ * TODO: key and salt lengths should be separate fields in the policy. */
+static inline int base_key_length(const srtp_cipher_type_t *cipher,
+ int key_length)
+{
+ switch (cipher->id) {
+ case SRTP_AES_ICM_128:
+ case SRTP_AES_ICM_192:
+ case SRTP_AES_ICM_256:
+ /* The legacy modes are derived from
+ * the configured key length on the policy */
+ return key_length - SRTP_SALT_LEN;
+ break;
+ case SRTP_AES_GCM_128:
+ return key_length - SRTP_AEAD_SALT_LEN;
+ break;
+ case SRTP_AES_GCM_256:
+ return key_length - SRTP_AEAD_SALT_LEN;
+ break;
+ default:
+ return key_length;
+ break;
+ }
+}
+
+unsigned int srtp_validate_policy_master_keys(const srtp_policy_t *policy)
+{
+ unsigned long i = 0;
+
+ if (policy->key == NULL) {
+ if (policy->num_master_keys <= 0)
+ return 0;
+
+ if (policy->num_master_keys > SRTP_MAX_NUM_MASTER_KEYS)
+ return 0;
+
+ for (i = 0; i < policy->num_master_keys; i++) {
+ if (policy->keys[i]->key == NULL)
+ return 0;
+ if (policy->keys[i]->mki_size > SRTP_MAX_MKI_LEN)
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+srtp_session_keys_t *srtp_get_session_keys_with_mki_index(
+ srtp_stream_ctx_t *stream,
+ unsigned int use_mki,
+ unsigned int mki_index)
+{
+ if (use_mki) {
+ if (mki_index >= stream->num_master_keys) {
+ return NULL;
+ }
+ return &stream->session_keys[mki_index];
+ }
+
+ return &stream->session_keys[0];
+}
+
+unsigned int srtp_inject_mki(uint8_t *mki_tag_location,
+ srtp_session_keys_t *session_keys,
+ unsigned int use_mki)
+{
+ unsigned int mki_size = 0;
+
+ if (use_mki) {
+ mki_size = session_keys->mki_size;
+
+ if (mki_size != 0) {
+ // Write MKI into memory
+ memcpy(mki_tag_location, session_keys->mki_id, mki_size);
+ }
+ }
+
+ return mki_size;
+}
+
+srtp_err_status_t srtp_stream_init_all_master_keys(
+ srtp_stream_ctx_t *srtp,
+ unsigned char *key,
+ srtp_master_key_t **keys,
+ const unsigned int max_master_keys)
+{
+ unsigned int i = 0;
+ srtp_err_status_t status = srtp_err_status_ok;
+ srtp_master_key_t single_master_key;
+
+ if (key != NULL) {
+ srtp->num_master_keys = 1;
+ single_master_key.key = key;
+ single_master_key.mki_id = NULL;
+ single_master_key.mki_size = 0;
+ status = srtp_stream_init_keys(srtp, &single_master_key, 0);
+ } else {
+ srtp->num_master_keys = max_master_keys;
+
+ for (i = 0; i < srtp->num_master_keys && i < SRTP_MAX_NUM_MASTER_KEYS;
+ i++) {
+ status = srtp_stream_init_keys(srtp, keys[i], i);
+
+ if (status) {
+ return status;
+ }
+ }
+ }
+
+ return status;
+}
+
+srtp_err_status_t srtp_stream_init_keys(srtp_stream_ctx_t *srtp,
+ srtp_master_key_t *master_key,
+ const unsigned int current_mki_index)
+{
+ srtp_err_status_t stat;
+ srtp_kdf_t kdf;
+ uint8_t tmp_key[MAX_SRTP_KEY_LEN];
+ int kdf_keylen = 30, rtp_keylen, rtcp_keylen;
+ int rtp_base_key_len, rtp_salt_len;
+ int rtcp_base_key_len, rtcp_salt_len;
+ srtp_session_keys_t *session_keys = NULL;
+ unsigned char *key = master_key->key;
+
+ /* If RTP or RTCP have a key length > AES-128, assume matching kdf. */
+ /* TODO: kdf algorithm, master key length, and master salt length should
+ * be part of srtp_policy_t.
+ */
+ session_keys = &srtp->session_keys[current_mki_index];
+
+/* initialize key limit to maximum value */
+#ifdef NO_64BIT_MATH
+ {
+ uint64_t temp;
+ temp = make64(UINT_MAX, UINT_MAX);
+ srtp_key_limit_set(session_keys->limit, temp);
+ }
+#else
+ srtp_key_limit_set(session_keys->limit, 0xffffffffffffLL);
+#endif
+
+ if (master_key->mki_size != 0) {
+ session_keys->mki_id = srtp_crypto_alloc(master_key->mki_size);
+
+ if (session_keys->mki_id == NULL) {
+ return srtp_err_status_init_fail;
+ }
+ memcpy(session_keys->mki_id, master_key->mki_id, master_key->mki_size);
+ } else {
+ session_keys->mki_id = NULL;
+ }
+
+ session_keys->mki_size = master_key->mki_size;
+
+ rtp_keylen = srtp_cipher_get_key_length(session_keys->rtp_cipher);
+ rtcp_keylen = srtp_cipher_get_key_length(session_keys->rtcp_cipher);
+ rtp_base_key_len =
+ base_key_length(session_keys->rtp_cipher->type, rtp_keylen);
+ rtp_salt_len = rtp_keylen - rtp_base_key_len;
+
+ if (rtp_keylen > kdf_keylen) {
+ kdf_keylen = 46; /* AES-CTR mode is always used for KDF */
+ }
+
+ if (rtcp_keylen > kdf_keylen) {
+ kdf_keylen = 46; /* AES-CTR mode is always used for KDF */
+ }
+
+ debug_print(mod_srtp, "srtp key len: %d", rtp_keylen);
+ debug_print(mod_srtp, "srtcp key len: %d", rtcp_keylen);
+ debug_print(mod_srtp, "base key len: %d", rtp_base_key_len);
+ debug_print(mod_srtp, "kdf key len: %d", kdf_keylen);
+ debug_print(mod_srtp, "rtp salt len: %d", rtp_salt_len);
+
+ /*
+ * Make sure the key given to us is 'zero' appended. GCM
+ * mode uses a shorter master SALT (96 bits), but still relies on
+ * the legacy CTR mode KDF, which uses a 112 bit master SALT.
+ */
+ memset(tmp_key, 0x0, MAX_SRTP_KEY_LEN);
+ memcpy(tmp_key, key, (rtp_base_key_len + rtp_salt_len));
+
+/* initialize KDF state */
+#if defined(OPENSSL) && defined(OPENSSL_KDF)
+ stat = srtp_kdf_init(&kdf, (const uint8_t *)tmp_key, rtp_base_key_len,
+ rtp_salt_len);
+#else
+ stat = srtp_kdf_init(&kdf, (const uint8_t *)tmp_key, kdf_keylen);
+#endif
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+
+ /* generate encryption key */
+ stat = srtp_kdf_generate(&kdf, label_rtp_encryption, tmp_key,
+ rtp_base_key_len);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+ debug_print(mod_srtp, "cipher key: %s",
+ srtp_octet_string_hex_string(tmp_key, rtp_base_key_len));
+
+ /*
+ * if the cipher in the srtp context uses a salt, then we need
+ * to generate the salt value
+ */
+ if (rtp_salt_len > 0) {
+ debug_print(mod_srtp, "found rtp_salt_len > 0, generating salt", NULL);
+
+ /* generate encryption salt, put after encryption key */
+ stat = srtp_kdf_generate(&kdf, label_rtp_salt,
+ tmp_key + rtp_base_key_len, rtp_salt_len);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+ memcpy(session_keys->salt, tmp_key + rtp_base_key_len,
+ SRTP_AEAD_SALT_LEN);
+ }
+ if (rtp_salt_len > 0) {
+ debug_print(mod_srtp, "cipher salt: %s",
+ srtp_octet_string_hex_string(tmp_key + rtp_base_key_len,
+ rtp_salt_len));
+ }
+
+ /* initialize cipher */
+ stat = srtp_cipher_init(session_keys->rtp_cipher, tmp_key);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+
+ if (session_keys->rtp_xtn_hdr_cipher) {
+ /* generate extensions header encryption key */
+ int rtp_xtn_hdr_keylen;
+ int rtp_xtn_hdr_base_key_len;
+ int rtp_xtn_hdr_salt_len;
+ srtp_kdf_t tmp_kdf;
+ srtp_kdf_t *xtn_hdr_kdf;
+
+ if (session_keys->rtp_xtn_hdr_cipher->type !=
+ session_keys->rtp_cipher->type) {
+ /*
+ * With GCM ciphers, the header extensions are still encrypted using
+ * the corresponding ICM cipher.
+ * See https://tools.ietf.org/html/rfc7714#section-8.3
+ */
+ uint8_t tmp_xtn_hdr_key[MAX_SRTP_KEY_LEN];
+ rtp_xtn_hdr_keylen =
+ srtp_cipher_get_key_length(session_keys->rtp_xtn_hdr_cipher);
+ rtp_xtn_hdr_base_key_len = base_key_length(
+ session_keys->rtp_xtn_hdr_cipher->type, rtp_xtn_hdr_keylen);
+ rtp_xtn_hdr_salt_len =
+ rtp_xtn_hdr_keylen - rtp_xtn_hdr_base_key_len;
+ if (rtp_xtn_hdr_salt_len > rtp_salt_len) {
+ switch (session_keys->rtp_cipher->type->id) {
+ case SRTP_AES_GCM_128:
+ case SRTP_AES_GCM_256:
+ /*
+ * The shorter GCM salt is padded to the required ICM salt
+ * length.
+ */
+ rtp_xtn_hdr_salt_len = rtp_salt_len;
+ break;
+ default:
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_bad_param;
+ }
+ }
+ memset(tmp_xtn_hdr_key, 0x0, MAX_SRTP_KEY_LEN);
+ memcpy(tmp_xtn_hdr_key, key,
+ (rtp_xtn_hdr_base_key_len + rtp_xtn_hdr_salt_len));
+ xtn_hdr_kdf = &tmp_kdf;
+
+/* initialize KDF state */
+#if defined(OPENSSL) && defined(OPENSSL_KDF)
+ stat =
+ srtp_kdf_init(xtn_hdr_kdf, (const uint8_t *)tmp_xtn_hdr_key,
+ rtp_xtn_hdr_base_key_len, rtp_xtn_hdr_salt_len);
+#else
+ stat = srtp_kdf_init(xtn_hdr_kdf, (const uint8_t *)tmp_xtn_hdr_key,
+ kdf_keylen);
+#endif
+ octet_string_set_to_zero(tmp_xtn_hdr_key, MAX_SRTP_KEY_LEN);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+ } else {
+ /* Reuse main KDF. */
+ rtp_xtn_hdr_keylen = rtp_keylen;
+ rtp_xtn_hdr_base_key_len = rtp_base_key_len;
+ rtp_xtn_hdr_salt_len = rtp_salt_len;
+ xtn_hdr_kdf = &kdf;
+ }
+
+ stat = srtp_kdf_generate(xtn_hdr_kdf, label_rtp_header_encryption,
+ tmp_key, rtp_xtn_hdr_base_key_len);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+ debug_print(
+ mod_srtp, "extensions cipher key: %s",
+ srtp_octet_string_hex_string(tmp_key, rtp_xtn_hdr_base_key_len));
+
+ /*
+ * if the cipher in the srtp context uses a salt, then we need
+ * to generate the salt value
+ */
+ if (rtp_xtn_hdr_salt_len > 0) {
+ debug_print(mod_srtp,
+ "found rtp_xtn_hdr_salt_len > 0, generating salt",
+ NULL);
+
+ /* generate encryption salt, put after encryption key */
+ stat = srtp_kdf_generate(xtn_hdr_kdf, label_rtp_header_salt,
+ tmp_key + rtp_xtn_hdr_base_key_len,
+ rtp_xtn_hdr_salt_len);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+ }
+ if (rtp_xtn_hdr_salt_len > 0) {
+ debug_print(
+ mod_srtp, "extensions cipher salt: %s",
+ srtp_octet_string_hex_string(tmp_key + rtp_xtn_hdr_base_key_len,
+ rtp_xtn_hdr_salt_len));
+ }
+
+ /* initialize extensions header cipher */
+ stat = srtp_cipher_init(session_keys->rtp_xtn_hdr_cipher, tmp_key);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+
+ if (xtn_hdr_kdf != &kdf) {
+ /* release memory for custom header extension encryption kdf */
+ stat = srtp_kdf_clear(xtn_hdr_kdf);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+ }
+ }
+
+ /* generate authentication key */
+ stat = srtp_kdf_generate(&kdf, label_rtp_msg_auth, tmp_key,
+ srtp_auth_get_key_length(session_keys->rtp_auth));
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+ debug_print(mod_srtp, "auth key: %s",
+ srtp_octet_string_hex_string(
+ tmp_key, srtp_auth_get_key_length(session_keys->rtp_auth)));
+
+ /* initialize auth function */
+ stat = srtp_auth_init(session_keys->rtp_auth, tmp_key);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+
+ /*
+ * ...now initialize SRTCP keys
+ */
+
+ rtcp_base_key_len =
+ base_key_length(session_keys->rtcp_cipher->type, rtcp_keylen);
+ rtcp_salt_len = rtcp_keylen - rtcp_base_key_len;
+ debug_print(mod_srtp, "rtcp salt len: %d", rtcp_salt_len);
+
+ /* generate encryption key */
+ stat = srtp_kdf_generate(&kdf, label_rtcp_encryption, tmp_key,
+ rtcp_base_key_len);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+
+ /*
+ * if the cipher in the srtp context uses a salt, then we need
+ * to generate the salt value
+ */
+ if (rtcp_salt_len > 0) {
+ debug_print(mod_srtp, "found rtcp_salt_len > 0, generating rtcp salt",
+ NULL);
+
+ /* generate encryption salt, put after encryption key */
+ stat = srtp_kdf_generate(&kdf, label_rtcp_salt,
+ tmp_key + rtcp_base_key_len, rtcp_salt_len);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+ memcpy(session_keys->c_salt, tmp_key + rtcp_base_key_len,
+ SRTP_AEAD_SALT_LEN);
+ }
+ debug_print(mod_srtp, "rtcp cipher key: %s",
+ srtp_octet_string_hex_string(tmp_key, rtcp_base_key_len));
+ if (rtcp_salt_len > 0) {
+ debug_print(mod_srtp, "rtcp cipher salt: %s",
+ srtp_octet_string_hex_string(tmp_key + rtcp_base_key_len,
+ rtcp_salt_len));
+ }
+
+ /* initialize cipher */
+ stat = srtp_cipher_init(session_keys->rtcp_cipher, tmp_key);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+
+ /* generate authentication key */
+ stat = srtp_kdf_generate(&kdf, label_rtcp_msg_auth, tmp_key,
+ srtp_auth_get_key_length(session_keys->rtcp_auth));
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+
+ debug_print(
+ mod_srtp, "rtcp auth key: %s",
+ srtp_octet_string_hex_string(
+ tmp_key, srtp_auth_get_key_length(session_keys->rtcp_auth)));
+
+ /* initialize auth function */
+ stat = srtp_auth_init(session_keys->rtcp_auth, tmp_key);
+ if (stat) {
+ /* zeroize temp buffer */
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ return srtp_err_status_init_fail;
+ }
+
+ /* clear memory then return */
+ stat = srtp_kdf_clear(&kdf);
+ octet_string_set_to_zero(tmp_key, MAX_SRTP_KEY_LEN);
+ if (stat)
+ return srtp_err_status_init_fail;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_stream_init(srtp_stream_ctx_t *srtp,
+ const srtp_policy_t *p)
+{
+ srtp_err_status_t err;
+
+ debug_print(mod_srtp, "initializing stream (SSRC: 0x%08x)", p->ssrc.value);
+
+ /* initialize replay database */
+ /*
+ * window size MUST be at least 64. MAY be larger. Values more than
+ * 2^15 aren't meaningful due to how extended sequence numbers are
+ * calculated.
+ * Let a window size of 0 imply the default value.
+ */
+
+ if (p->window_size != 0 &&
+ (p->window_size < 64 || p->window_size >= 0x8000))
+ return srtp_err_status_bad_param;
+
+ if (p->window_size != 0)
+ err = srtp_rdbx_init(&srtp->rtp_rdbx, p->window_size);
+ else
+ err = srtp_rdbx_init(&srtp->rtp_rdbx, 128);
+ if (err)
+ return err;
+
+ /* set the SSRC value */
+ srtp->ssrc = htonl(p->ssrc.value);
+
+ /* reset pending ROC */
+ srtp->pending_roc = 0;
+
+ /* set the security service flags */
+ srtp->rtp_services = p->rtp.sec_serv;
+ srtp->rtcp_services = p->rtcp.sec_serv;
+
+ /*
+ * set direction to unknown - this flag gets checked in srtp_protect(),
+ * srtp_unprotect(), srtp_protect_rtcp(), and srtp_unprotect_rtcp(), and
+ * gets set appropriately if it is set to unknown.
+ */
+ srtp->direction = dir_unknown;
+
+ /* initialize SRTCP replay database */
+ srtp_rdb_init(&srtp->rtcp_rdb);
+
+ /* initialize allow_repeat_tx */
+ /* guard against uninitialized memory: allow only 0 or 1 here */
+ if (p->allow_repeat_tx != 0 && p->allow_repeat_tx != 1) {
+ srtp_rdbx_dealloc(&srtp->rtp_rdbx);
+ return srtp_err_status_bad_param;
+ }
+ srtp->allow_repeat_tx = p->allow_repeat_tx;
+
+ /* DAM - no RTCP key limit at present */
+
+ /* initialize keys */
+ err = srtp_stream_init_all_master_keys(srtp, p->key, p->keys,
+ p->num_master_keys);
+ if (err) {
+ srtp_rdbx_dealloc(&srtp->rtp_rdbx);
+ return err;
+ }
+
+ /*
+ * if EKT is in use, then initialize the EKT data associated with
+ * the stream
+ */
+ err = srtp_ekt_stream_init_from_policy(srtp->ekt, p->ekt);
+ if (err) {
+ srtp_rdbx_dealloc(&srtp->rtp_rdbx);
+ return err;
+ }
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp_event_reporter is an event handler function that merely
+ * reports the events that are reported by the callbacks
+ */
+
+void srtp_event_reporter(srtp_event_data_t *data)
+{
+ srtp_err_report(srtp_err_level_warning, "srtp: in stream 0x%x: ",
+ data->ssrc);
+
+ switch (data->event) {
+ case event_ssrc_collision:
+ srtp_err_report(srtp_err_level_warning, "\tSSRC collision\n");
+ break;
+ case event_key_soft_limit:
+ srtp_err_report(srtp_err_level_warning,
+ "\tkey usage soft limit reached\n");
+ break;
+ case event_key_hard_limit:
+ srtp_err_report(srtp_err_level_warning,
+ "\tkey usage hard limit reached\n");
+ break;
+ case event_packet_index_limit:
+ srtp_err_report(srtp_err_level_warning,
+ "\tpacket index limit reached\n");
+ break;
+ default:
+ srtp_err_report(srtp_err_level_warning,
+ "\tunknown event reported to handler\n");
+ }
+}
+
+/*
+ * srtp_event_handler is a global variable holding a pointer to the
+ * event handler function; this function is called for any unexpected
+ * event that needs to be handled out of the SRTP data path. see
+ * srtp_event_t in srtp.h for more info
+ *
+ * it is okay to set srtp_event_handler to NULL, but we set
+ * it to the srtp_event_reporter.
+ */
+
+static srtp_event_handler_func_t *srtp_event_handler = srtp_event_reporter;
+
+srtp_err_status_t srtp_install_event_handler(srtp_event_handler_func_t func)
+{
+ /*
+ * note that we accept NULL arguments intentionally - calling this
+ * function with a NULL arguments removes an event handler that's
+ * been previously installed
+ */
+
+ /* set global event handling function */
+ srtp_event_handler = func;
+ return srtp_err_status_ok;
+}
+
+/*
+ * Check if the given extension header id is / should be encrypted.
+ * Returns 1 if yes, otherwise 0.
+ */
+static int srtp_protect_extension_header(srtp_stream_ctx_t *stream, int id)
+{
+ int *enc_xtn_hdr = stream->enc_xtn_hdr;
+ int count = stream->enc_xtn_hdr_count;
+
+ if (!enc_xtn_hdr || count <= 0) {
+ return 0;
+ }
+
+ while (count > 0) {
+ if (*enc_xtn_hdr == id) {
+ return 1;
+ }
+
+ enc_xtn_hdr++;
+ count--;
+ }
+ return 0;
+}
+
+/*
+ * extensions header encryption RFC 6904
+ */
+static srtp_err_status_t srtp_process_header_encryption(
+ srtp_stream_ctx_t *stream,
+ srtp_hdr_xtnd_t *xtn_hdr,
+ srtp_session_keys_t *session_keys)
+{
+ srtp_err_status_t status;
+ uint8_t keystream[257]; /* Maximum 2 bytes header + 255 bytes data. */
+ int keystream_pos;
+ uint8_t *xtn_hdr_data = ((uint8_t *)xtn_hdr) + octets_in_rtp_extn_hdr;
+ uint8_t *xtn_hdr_end =
+ xtn_hdr_data + (ntohs(xtn_hdr->length) * sizeof(uint32_t));
+
+ if (ntohs(xtn_hdr->profile_specific) == 0xbede) {
+ /* RFC 5285, section 4.2. One-Byte Header */
+ while (xtn_hdr_data < xtn_hdr_end) {
+ uint8_t xid = (*xtn_hdr_data & 0xf0) >> 4;
+ unsigned int xlen = (*xtn_hdr_data & 0x0f) + 1;
+ uint32_t xlen_with_header = 1 + xlen;
+ xtn_hdr_data++;
+
+ if (xtn_hdr_data + xlen > xtn_hdr_end)
+ return srtp_err_status_parse_err;
+
+ if (xid == 15) {
+ /* found header 15, stop further processing. */
+ break;
+ }
+
+ status = srtp_cipher_output(session_keys->rtp_xtn_hdr_cipher,
+ keystream, &xlen_with_header);
+ if (status)
+ return srtp_err_status_cipher_fail;
+
+ if (srtp_protect_extension_header(stream, xid)) {
+ keystream_pos = 1;
+ while (xlen > 0) {
+ *xtn_hdr_data ^= keystream[keystream_pos++];
+ xtn_hdr_data++;
+ xlen--;
+ }
+ } else {
+ xtn_hdr_data += xlen;
+ }
+
+ /* skip padding bytes. */
+ while (xtn_hdr_data < xtn_hdr_end && *xtn_hdr_data == 0) {
+ xtn_hdr_data++;
+ }
+ }
+ } else if ((ntohs(xtn_hdr->profile_specific) & 0x1fff) == 0x100) {
+ /* RFC 5285, section 4.3. Two-Byte Header */
+ while (xtn_hdr_data + 1 < xtn_hdr_end) {
+ uint8_t xid = *xtn_hdr_data;
+ unsigned int xlen = *(xtn_hdr_data + 1);
+ uint32_t xlen_with_header = 2 + xlen;
+ xtn_hdr_data += 2;
+
+ if (xtn_hdr_data + xlen > xtn_hdr_end)
+ return srtp_err_status_parse_err;
+
+ status = srtp_cipher_output(session_keys->rtp_xtn_hdr_cipher,
+ keystream, &xlen_with_header);
+ if (status)
+ return srtp_err_status_cipher_fail;
+
+ if (xlen > 0 && srtp_protect_extension_header(stream, xid)) {
+ keystream_pos = 2;
+ while (xlen > 0) {
+ *xtn_hdr_data ^= keystream[keystream_pos++];
+ xtn_hdr_data++;
+ xlen--;
+ }
+ } else {
+ xtn_hdr_data += xlen;
+ }
+
+ /* skip padding bytes. */
+ while (xtn_hdr_data < xtn_hdr_end && *xtn_hdr_data == 0) {
+ xtn_hdr_data++;
+ }
+ }
+ } else {
+ /* unsupported extension header format. */
+ return srtp_err_status_parse_err;
+ }
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * AEAD uses a new IV formation method. This function implements
+ * section 8.1. (SRTP IV Formation for AES-GCM) of RFC7714.
+ * The calculation is defined as, where (+) is the xor operation:
+ *
+ *
+ * 0 0 0 0 0 0 0 0 0 0 1 1
+ * 0 1 2 3 4 5 6 7 8 9 0 1
+ * +--+--+--+--+--+--+--+--+--+--+--+--+
+ * |00|00| SSRC | ROC | SEQ |---+
+ * +--+--+--+--+--+--+--+--+--+--+--+--+ |
+ * |
+ * +--+--+--+--+--+--+--+--+--+--+--+--+ |
+ * | Encryption Salt |->(+)
+ * +--+--+--+--+--+--+--+--+--+--+--+--+ |
+ * |
+ * +--+--+--+--+--+--+--+--+--+--+--+--+ |
+ * | Initialization Vector |<--+
+ * +--+--+--+--+--+--+--+--+--+--+--+--+*
+ *
+ * Input: *session_keys - pointer to SRTP stream context session keys,
+ * used to retrieve the SALT
+ * *iv - Pointer to receive the calculated IV
+ * *seq - The ROC and SEQ value to use for the
+ * IV calculation.
+ * *hdr - The RTP header, used to get the SSRC value
+ *
+ */
+
+static void srtp_calc_aead_iv(srtp_session_keys_t *session_keys,
+ v128_t *iv,
+ srtp_xtd_seq_num_t *seq,
+ srtp_hdr_t *hdr)
+{
+ v128_t in;
+ v128_t salt;
+
+#ifdef NO_64BIT_MATH
+ uint32_t local_roc = ((high32(*seq) << 16) | (low32(*seq) >> 16));
+ uint16_t local_seq = (uint16_t)(low32(*seq));
+#else
+ uint32_t local_roc = (uint32_t)(*seq >> 16);
+ uint16_t local_seq = (uint16_t)*seq;
+#endif
+
+ memset(&in, 0, sizeof(v128_t));
+ memset(&salt, 0, sizeof(v128_t));
+
+ in.v16[5] = htons(local_seq);
+ local_roc = htonl(local_roc);
+ memcpy(&in.v16[3], &local_roc, sizeof(local_roc));
+
+ /*
+ * Copy in the RTP SSRC value
+ */
+ memcpy(&in.v8[2], &hdr->ssrc, 4);
+ debug_print(mod_srtp, "Pre-salted RTP IV = %s\n", v128_hex_string(&in));
+
+ /*
+ * Get the SALT value from the context
+ */
+ memcpy(salt.v8, session_keys->salt, SRTP_AEAD_SALT_LEN);
+ debug_print(mod_srtp, "RTP SALT = %s\n", v128_hex_string(&salt));
+
+ /*
+ * Finally, apply tyhe SALT to the input
+ */
+ v128_xor(iv, &in, &salt);
+}
+
+srtp_session_keys_t *srtp_get_session_keys(srtp_stream_ctx_t *stream,
+ uint8_t *hdr,
+ const unsigned int *pkt_octet_len,
+ unsigned int *mki_size)
+{
+ unsigned int base_mki_start_location = *pkt_octet_len;
+ unsigned int mki_start_location = 0;
+ unsigned int tag_len = 0;
+ unsigned int i = 0;
+
+ // Determine the authentication tag size
+ if (stream->session_keys[0].rtp_cipher->algorithm == SRTP_AES_GCM_128 ||
+ stream->session_keys[0].rtp_cipher->algorithm == SRTP_AES_GCM_256) {
+ tag_len = 0;
+ } else {
+ tag_len = srtp_auth_get_tag_length(stream->session_keys[0].rtp_auth);
+ }
+
+ if (tag_len > base_mki_start_location) {
+ *mki_size = 0;
+ return NULL;
+ }
+
+ base_mki_start_location -= tag_len;
+
+ for (i = 0; i < stream->num_master_keys; i++) {
+ if (stream->session_keys[i].mki_size != 0 &&
+ stream->session_keys[i].mki_size <= base_mki_start_location) {
+ *mki_size = stream->session_keys[i].mki_size;
+ mki_start_location = base_mki_start_location - *mki_size;
+
+ if (memcmp(hdr + mki_start_location, stream->session_keys[i].mki_id,
+ *mki_size) == 0) {
+ return &stream->session_keys[i];
+ }
+ }
+ }
+
+ *mki_size = 0;
+ return NULL;
+}
+
+static srtp_err_status_t srtp_estimate_index(srtp_rdbx_t *rdbx,
+ uint32_t roc,
+ srtp_xtd_seq_num_t *est,
+ srtp_sequence_number_t seq,
+ int *delta)
+{
+#ifdef NO_64BIT_MATH
+ uint32_t internal_pkt_idx_reduced;
+ uint32_t external_pkt_idx_reduced;
+ uint32_t internal_roc;
+ uint32_t roc_difference;
+#endif
+
+#ifdef NO_64BIT_MATH
+ *est = (srtp_xtd_seq_num_t)make64(roc >> 16, (roc << 16) | seq);
+ *delta = low32(est) - rdbx->index;
+#else
+ *est = (srtp_xtd_seq_num_t)(((uint64_t)roc) << 16) | seq;
+ *delta = (int)(*est - rdbx->index);
+#endif
+
+ if (*est > rdbx->index) {
+#ifdef NO_64BIT_MATH
+ internal_roc = (uint32_t)(rdbx->index >> 16);
+ roc_difference = roc - internal_roc;
+ if (roc_difference > 1) {
+ *delta = 0;
+ return srtp_err_status_pkt_idx_adv;
+ }
+
+ internal_pkt_idx_reduced = (uint32_t)(rdbx->index & 0xFFFF);
+ external_pkt_idx_reduced = (uint32_t)((roc_difference << 16) | seq);
+
+ if (external_pkt_idx_reduced - internal_pkt_idx_reduced >
+ seq_num_median) {
+ *delta = 0;
+ return srtp_err_status_pkt_idx_adv;
+ }
+#else
+ if (*est - rdbx->index > seq_num_median) {
+ *delta = 0;
+ return srtp_err_status_pkt_idx_adv;
+ }
+#endif
+ } else if (*est < rdbx->index) {
+#ifdef NO_64BIT_MATH
+
+ internal_roc = (uint32_t)(rdbx->index >> 16);
+ roc_difference = internal_roc - roc;
+ if (roc_difference > 1) {
+ *delta = 0;
+ return srtp_err_status_pkt_idx_adv;
+ }
+
+ internal_pkt_idx_reduced =
+ (uint32_t)((roc_difference << 16) | rdbx->index & 0xFFFF);
+ external_pkt_idx_reduced = (uint32_t)(seq);
+
+ if (internal_pkt_idx_reduced - external_pkt_idx_reduced >
+ seq_num_median) {
+ *delta = 0;
+ return srtp_err_status_pkt_idx_old;
+ }
+#else
+ if (rdbx->index - *est > seq_num_median) {
+ *delta = 0;
+ return srtp_err_status_pkt_idx_old;
+ }
+#endif
+ }
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t srtp_get_est_pkt_index(srtp_hdr_t *hdr,
+ srtp_stream_ctx_t *stream,
+ srtp_xtd_seq_num_t *est,
+ int *delta)
+{
+ srtp_err_status_t result = srtp_err_status_ok;
+
+ if (stream->pending_roc) {
+ result = srtp_estimate_index(&stream->rtp_rdbx, stream->pending_roc,
+ est, ntohs(hdr->seq), delta);
+ } else {
+ /* estimate packet index from seq. num. in header */
+ *delta =
+ srtp_rdbx_estimate_index(&stream->rtp_rdbx, est, ntohs(hdr->seq));
+ }
+
+#ifdef NO_64BIT_MATH
+ debug_print2(mod_srtp, "estimated u_packet index: %08x%08x", high32(*est),
+ low32(*est));
+#else
+ debug_print(mod_srtp, "estimated u_packet index: %016llx", *est);
+#endif
+ return result;
+}
+
+/*
+ * This function handles outgoing SRTP packets while in AEAD mode,
+ * which currently supports AES-GCM encryption. All packets are
+ * encrypted and authenticated.
+ */
+static srtp_err_status_t srtp_protect_aead(srtp_ctx_t *ctx,
+ srtp_stream_ctx_t *stream,
+ void *rtp_hdr,
+ unsigned int *pkt_octet_len,
+ srtp_session_keys_t *session_keys,
+ unsigned int use_mki)
+{
+ srtp_hdr_t *hdr = (srtp_hdr_t *)rtp_hdr;
+ uint32_t *enc_start; /* pointer to start of encrypted portion */
+ int enc_octet_len = 0; /* number of octets in encrypted portion */
+ srtp_xtd_seq_num_t est; /* estimated xtd_seq_num_t of *hdr */
+ int delta; /* delta of local pkt idx and that in hdr */
+ srtp_err_status_t status;
+ uint32_t tag_len;
+ v128_t iv;
+ unsigned int aad_len;
+ srtp_hdr_xtnd_t *xtn_hdr = NULL;
+ unsigned int mki_size = 0;
+ uint8_t *mki_location = NULL;
+
+ debug_print(mod_srtp, "function srtp_protect_aead", NULL);
+
+ /*
+ * update the key usage limit, and check it to make sure that we
+ * didn't just hit either the soft limit or the hard limit, and call
+ * the event handler if we hit either.
+ */
+ switch (srtp_key_limit_update(session_keys->limit)) {
+ case srtp_key_event_normal:
+ break;
+ case srtp_key_event_hard_limit:
+ srtp_handle_event(ctx, stream, event_key_hard_limit);
+ return srtp_err_status_key_expired;
+ case srtp_key_event_soft_limit:
+ default:
+ srtp_handle_event(ctx, stream, event_key_soft_limit);
+ break;
+ }
+
+ /* get tag length from stream */
+ tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth);
+
+ /*
+ * find starting point for encryption and length of data to be
+ * encrypted - the encrypted portion starts after the rtp header
+ * extension, if present; otherwise, it starts after the last csrc,
+ * if any are present
+ */
+ enc_start = (uint32_t *)hdr + uint32s_in_rtp_header + hdr->cc;
+ if (hdr->x == 1) {
+ xtn_hdr = (srtp_hdr_xtnd_t *)enc_start;
+ enc_start += (ntohs(xtn_hdr->length) + 1);
+ }
+ /* note: the passed size is without the auth tag */
+ if (!((uint8_t *)enc_start <= (uint8_t *)hdr + *pkt_octet_len))
+ return srtp_err_status_parse_err;
+ enc_octet_len =
+ (int)(*pkt_octet_len - ((uint8_t *)enc_start - (uint8_t *)hdr));
+ if (enc_octet_len < 0)
+ return srtp_err_status_parse_err;
+
+ /*
+ * estimate the packet index using the start of the replay window
+ * and the sequence number from the header
+ */
+ delta = srtp_rdbx_estimate_index(&stream->rtp_rdbx, &est, ntohs(hdr->seq));
+ status = srtp_rdbx_check(&stream->rtp_rdbx, delta);
+ if (status) {
+ if (status != srtp_err_status_replay_fail || !stream->allow_repeat_tx) {
+ return status; /* we've been asked to reuse an index */
+ }
+ } else {
+ srtp_rdbx_add_index(&stream->rtp_rdbx, delta);
+ }
+
+#ifdef NO_64BIT_MATH
+ debug_print2(mod_srtp, "estimated packet index: %08x%08x", high32(est),
+ low32(est));
+#else
+ debug_print(mod_srtp, "estimated packet index: %016llx", est);
+#endif
+
+ /*
+ * AEAD uses a new IV formation method
+ */
+ srtp_calc_aead_iv(session_keys, &iv, &est, hdr);
+/* shift est, put into network byte order */
+#ifdef NO_64BIT_MATH
+ est = be64_to_cpu(
+ make64((high32(est) << 16) | (low32(est) >> 16), low32(est) << 16));
+#else
+ est = be64_to_cpu(est << 16);
+#endif
+
+ status = srtp_cipher_set_iv(session_keys->rtp_cipher, (uint8_t *)&iv,
+ srtp_direction_encrypt);
+ if (!status && session_keys->rtp_xtn_hdr_cipher) {
+ iv.v32[0] = 0;
+ iv.v32[1] = hdr->ssrc;
+ iv.v64[1] = est;
+ status = srtp_cipher_set_iv(session_keys->rtp_xtn_hdr_cipher,
+ (uint8_t *)&iv, srtp_direction_encrypt);
+ }
+ if (status) {
+ return srtp_err_status_cipher_fail;
+ }
+
+ if (xtn_hdr && session_keys->rtp_xtn_hdr_cipher) {
+ /*
+ * extensions header encryption RFC 6904
+ */
+ status = srtp_process_header_encryption(stream, xtn_hdr, session_keys);
+ if (status) {
+ return status;
+ }
+ }
+
+ /*
+ * Set the AAD over the RTP header
+ */
+ aad_len = (uint8_t *)enc_start - (uint8_t *)hdr;
+ status =
+ srtp_cipher_set_aad(session_keys->rtp_cipher, (uint8_t *)hdr, aad_len);
+ if (status) {
+ return (srtp_err_status_cipher_fail);
+ }
+
+ /* Encrypt the payload */
+ status = srtp_cipher_encrypt(session_keys->rtp_cipher, (uint8_t *)enc_start,
+ (unsigned int *)&enc_octet_len);
+ if (status) {
+ return srtp_err_status_cipher_fail;
+ }
+ /*
+ * If we're doing GCM, we need to get the tag
+ * and append that to the output
+ */
+ status =
+ srtp_cipher_get_tag(session_keys->rtp_cipher,
+ (uint8_t *)enc_start + enc_octet_len, &tag_len);
+ if (status) {
+ return (srtp_err_status_cipher_fail);
+ }
+
+ mki_location = (uint8_t *)hdr + *pkt_octet_len + tag_len;
+ mki_size = srtp_inject_mki(mki_location, session_keys, use_mki);
+
+ /* increase the packet length by the length of the auth tag */
+ *pkt_octet_len += tag_len;
+
+ /* increase the packet length by the length of the mki_size */
+ *pkt_octet_len += mki_size;
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * This function handles incoming SRTP packets while in AEAD mode,
+ * which currently supports AES-GCM encryption. All packets are
+ * encrypted and authenticated. Note, the auth tag is at the end
+ * of the packet stream and is automatically checked by GCM
+ * when decrypting the payload.
+ */
+static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx,
+ srtp_stream_ctx_t *stream,
+ int delta,
+ srtp_xtd_seq_num_t est,
+ void *srtp_hdr,
+ unsigned int *pkt_octet_len,
+ srtp_session_keys_t *session_keys,
+ unsigned int mki_size)
+{
+ srtp_hdr_t *hdr = (srtp_hdr_t *)srtp_hdr;
+ uint32_t *enc_start; /* pointer to start of encrypted portion */
+ unsigned int enc_octet_len = 0; /* number of octets in encrypted portion */
+ v128_t iv;
+ srtp_err_status_t status;
+ int tag_len;
+ unsigned int aad_len;
+ srtp_hdr_xtnd_t *xtn_hdr = NULL;
+
+ debug_print(mod_srtp, "function srtp_unprotect_aead", NULL);
+
+#ifdef NO_64BIT_MATH
+ debug_print2(mod_srtp, "estimated u_packet index: %08x%08x", high32(est),
+ low32(est));
+#else
+ debug_print(mod_srtp, "estimated u_packet index: %016llx", est);
+#endif
+
+ /* get tag length from stream */
+ tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth);
+
+ /*
+ * AEAD uses a new IV formation method
+ */
+ srtp_calc_aead_iv(session_keys, &iv, &est, hdr);
+ status = srtp_cipher_set_iv(session_keys->rtp_cipher, (uint8_t *)&iv,
+ srtp_direction_decrypt);
+ if (!status && session_keys->rtp_xtn_hdr_cipher) {
+ iv.v32[0] = 0;
+ iv.v32[1] = hdr->ssrc;
+#ifdef NO_64BIT_MATH
+ iv.v64[1] = be64_to_cpu(
+ make64((high32(est) << 16) | (low32(est) >> 16), low32(est) << 16));
+#else
+ iv.v64[1] = be64_to_cpu(est << 16);
+#endif
+ status = srtp_cipher_set_iv(session_keys->rtp_xtn_hdr_cipher,
+ (uint8_t *)&iv, srtp_direction_encrypt);
+ }
+ if (status) {
+ return srtp_err_status_cipher_fail;
+ }
+
+ /*
+ * find starting point for decryption and length of data to be
+ * decrypted - the encrypted portion starts after the rtp header
+ * extension, if present; otherwise, it starts after the last csrc,
+ * if any are present
+ */
+ enc_start = (uint32_t *)hdr + uint32s_in_rtp_header + hdr->cc;
+ if (hdr->x == 1) {
+ xtn_hdr = (srtp_hdr_xtnd_t *)enc_start;
+ enc_start += (ntohs(xtn_hdr->length) + 1);
+ }
+ if (!((uint8_t *)enc_start <=
+ (uint8_t *)hdr + (*pkt_octet_len - tag_len - mki_size)))
+ return srtp_err_status_parse_err;
+ /*
+ * We pass the tag down to the cipher when doing GCM mode
+ */
+ enc_octet_len = (unsigned int)(*pkt_octet_len - mki_size -
+ ((uint8_t *)enc_start - (uint8_t *)hdr));
+
+ /*
+ * Sanity check the encrypted payload length against
+ * the tag size. It must always be at least as large
+ * as the tag length.
+ */
+ if (enc_octet_len < (unsigned int)tag_len) {
+ return srtp_err_status_cipher_fail;
+ }
+
+ /*
+ * update the key usage limit, and check it to make sure that we
+ * didn't just hit either the soft limit or the hard limit, and call
+ * the event handler if we hit either.
+ */
+ switch (srtp_key_limit_update(session_keys->limit)) {
+ case srtp_key_event_normal:
+ break;
+ case srtp_key_event_soft_limit:
+ srtp_handle_event(ctx, stream, event_key_soft_limit);
+ break;
+ case srtp_key_event_hard_limit:
+ srtp_handle_event(ctx, stream, event_key_hard_limit);
+ return srtp_err_status_key_expired;
+ default:
+ break;
+ }
+
+ /*
+ * Set the AAD for AES-GCM, which is the RTP header
+ */
+ aad_len = (uint8_t *)enc_start - (uint8_t *)hdr;
+ status =
+ srtp_cipher_set_aad(session_keys->rtp_cipher, (uint8_t *)hdr, aad_len);
+ if (status) {
+ return (srtp_err_status_cipher_fail);
+ }
+
+ /* Decrypt the ciphertext. This also checks the auth tag based
+ * on the AAD we just specified above */
+ status = srtp_cipher_decrypt(session_keys->rtp_cipher, (uint8_t *)enc_start,
+ &enc_octet_len);
+ if (status) {
+ return status;
+ }
+
+ if (xtn_hdr && session_keys->rtp_xtn_hdr_cipher) {
+ /*
+ * extensions header encryption RFC 6904
+ */
+ status = srtp_process_header_encryption(stream, xtn_hdr, session_keys);
+ if (status) {
+ return status;
+ }
+ }
+
+ /*
+ * verify that stream is for received traffic - this check will
+ * detect SSRC collisions, since a stream that appears in both
+ * srtp_protect() and srtp_unprotect() will fail this test in one of
+ * those functions.
+ *
+ * we do this check *after* the authentication check, so that the
+ * latter check will catch any attempts to fool us into thinking
+ * that we've got a collision
+ */
+ if (stream->direction != dir_srtp_receiver) {
+ if (stream->direction == dir_unknown) {
+ stream->direction = dir_srtp_receiver;
+ } else {
+ srtp_handle_event(ctx, stream, event_ssrc_collision);
+ }
+ }
+
+ /*
+ * if the stream is a 'provisional' one, in which the template context
+ * is used, then we need to allocate a new stream at this point, since
+ * the authentication passed
+ */
+ if (stream == ctx->stream_template) {
+ srtp_stream_ctx_t *new_stream;
+
+ /*
+ * allocate and initialize a new stream
+ *
+ * note that we indicate failure if we can't allocate the new
+ * stream, and some implementations will want to not return
+ * failure here
+ */
+ status =
+ srtp_stream_clone(ctx->stream_template, hdr->ssrc, &new_stream);
+ if (status) {
+ return status;
+ }
+
+ /* add new stream to the head of the stream_list */
+ new_stream->next = ctx->stream_list;
+ ctx->stream_list = new_stream;
+
+ /* set stream (the pointer used in this function) */
+ stream = new_stream;
+ }
+
+ /*
+ * the message authentication function passed, so add the packet
+ * index into the replay database
+ */
+ srtp_rdbx_add_index(&stream->rtp_rdbx, delta);
+
+ /* decrease the packet length by the length of the auth tag */
+ *pkt_octet_len -= tag_len;
+
+ /* decrease the packet length by the length of the mki_size */
+ *pkt_octet_len -= mki_size;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_protect(srtp_ctx_t *ctx,
+ void *rtp_hdr,
+ int *pkt_octet_len)
+{
+ return srtp_protect_mki(ctx, rtp_hdr, pkt_octet_len, 0, 0);
+}
+
+srtp_err_status_t srtp_protect_mki(srtp_ctx_t *ctx,
+ void *rtp_hdr,
+ int *pkt_octet_len,
+ unsigned int use_mki,
+ unsigned int mki_index)
+{
+ srtp_hdr_t *hdr = (srtp_hdr_t *)rtp_hdr;
+ uint32_t *enc_start; /* pointer to start of encrypted portion */
+ uint32_t *auth_start; /* pointer to start of auth. portion */
+ int enc_octet_len = 0; /* number of octets in encrypted portion */
+ srtp_xtd_seq_num_t est; /* estimated xtd_seq_num_t of *hdr */
+ int delta; /* delta of local pkt idx and that in hdr */
+ uint8_t *auth_tag = NULL; /* location of auth_tag within packet */
+ srtp_err_status_t status;
+ int tag_len;
+ srtp_stream_ctx_t *stream;
+ uint32_t prefix_len;
+ srtp_hdr_xtnd_t *xtn_hdr = NULL;
+ unsigned int mki_size = 0;
+ srtp_session_keys_t *session_keys = NULL;
+ uint8_t *mki_location = NULL;
+ int advance_packet_index = 0;
+
+ debug_print(mod_srtp, "function srtp_protect", NULL);
+
+ /* we assume the hdr is 32-bit aligned to start */
+
+ /* Verify RTP header */
+ status = srtp_validate_rtp_header(rtp_hdr, pkt_octet_len);
+ if (status)
+ return status;
+
+ /* check the packet length - it must at least contain a full header */
+ if (*pkt_octet_len < octets_in_rtp_header)
+ return srtp_err_status_bad_param;
+
+ /*
+ * look up ssrc in srtp_stream list, and process the packet with
+ * the appropriate stream. if we haven't seen this stream before,
+ * there's a template key for this srtp_session, and the cipher
+ * supports key-sharing, then we assume that a new stream using
+ * that key has just started up
+ */
+ stream = srtp_get_stream(ctx, hdr->ssrc);
+ if (stream == NULL) {
+ if (ctx->stream_template != NULL) {
+ srtp_stream_ctx_t *new_stream;
+
+ /* allocate and initialize a new stream */
+ status =
+ srtp_stream_clone(ctx->stream_template, hdr->ssrc, &new_stream);
+ if (status)
+ return status;
+
+ /* add new stream to the head of the stream_list */
+ new_stream->next = ctx->stream_list;
+ ctx->stream_list = new_stream;
+
+ /* set direction to outbound */
+ new_stream->direction = dir_srtp_sender;
+
+ /* set stream (the pointer used in this function) */
+ stream = new_stream;
+ } else {
+ /* no template stream, so we return an error */
+ return srtp_err_status_no_ctx;
+ }
+ }
+
+ /*
+ * verify that stream is for sending traffic - this check will
+ * detect SSRC collisions, since a stream that appears in both
+ * srtp_protect() and srtp_unprotect() will fail this test in one of
+ * those functions.
+ */
+
+ if (stream->direction != dir_srtp_sender) {
+ if (stream->direction == dir_unknown) {
+ stream->direction = dir_srtp_sender;
+ } else {
+ srtp_handle_event(ctx, stream, event_ssrc_collision);
+ }
+ }
+
+ session_keys =
+ srtp_get_session_keys_with_mki_index(stream, use_mki, mki_index);
+
+ if (session_keys == NULL)
+ return srtp_err_status_bad_mki;
+
+ /*
+ * Check if this is an AEAD stream (GCM mode). If so, then dispatch
+ * the request to our AEAD handler.
+ */
+ if (session_keys->rtp_cipher->algorithm == SRTP_AES_GCM_128 ||
+ session_keys->rtp_cipher->algorithm == SRTP_AES_GCM_256) {
+ return srtp_protect_aead(ctx, stream, rtp_hdr,
+ (unsigned int *)pkt_octet_len, session_keys,
+ use_mki);
+ }
+
+ /*
+ * update the key usage limit, and check it to make sure that we
+ * didn't just hit either the soft limit or the hard limit, and call
+ * the event handler if we hit either.
+ */
+ switch (srtp_key_limit_update(session_keys->limit)) {
+ case srtp_key_event_normal:
+ break;
+ case srtp_key_event_soft_limit:
+ srtp_handle_event(ctx, stream, event_key_soft_limit);
+ break;
+ case srtp_key_event_hard_limit:
+ srtp_handle_event(ctx, stream, event_key_hard_limit);
+ return srtp_err_status_key_expired;
+ default:
+ break;
+ }
+
+ /* get tag length from stream */
+ tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth);
+
+ /*
+ * find starting point for encryption and length of data to be
+ * encrypted - the encrypted portion starts after the rtp header
+ * extension, if present; otherwise, it starts after the last csrc,
+ * if any are present
+ *
+ * if we're not providing confidentiality, set enc_start to NULL
+ */
+ if (stream->rtp_services & sec_serv_conf) {
+ enc_start = (uint32_t *)hdr + uint32s_in_rtp_header + hdr->cc;
+ if (hdr->x == 1) {
+ xtn_hdr = (srtp_hdr_xtnd_t *)enc_start;
+ enc_start += (ntohs(xtn_hdr->length) + 1);
+ }
+ /* note: the passed size is without the auth tag */
+ if (!((uint8_t *)enc_start <= (uint8_t *)hdr + *pkt_octet_len))
+ return srtp_err_status_parse_err;
+ enc_octet_len =
+ (int)(*pkt_octet_len - ((uint8_t *)enc_start - (uint8_t *)hdr));
+ if (enc_octet_len < 0)
+ return srtp_err_status_parse_err;
+ } else {
+ enc_start = NULL;
+ }
+
+ mki_location = (uint8_t *)hdr + *pkt_octet_len;
+ mki_size = srtp_inject_mki(mki_location, session_keys, use_mki);
+
+ /*
+ * if we're providing authentication, set the auth_start and auth_tag
+ * pointers to the proper locations; otherwise, set auth_start to NULL
+ * to indicate that no authentication is needed
+ */
+ if (stream->rtp_services & sec_serv_auth) {
+ auth_start = (uint32_t *)hdr;
+ auth_tag = (uint8_t *)hdr + *pkt_octet_len + mki_size;
+ } else {
+ auth_start = NULL;
+ auth_tag = NULL;
+ }
+
+ /*
+ * estimate the packet index using the start of the replay window
+ * and the sequence number from the header
+ */
+ status = srtp_get_est_pkt_index(hdr, stream, &est, &delta);
+
+ if (status && (status != srtp_err_status_pkt_idx_adv))
+ return status;
+
+ if (status == srtp_err_status_pkt_idx_adv)
+ advance_packet_index = 1;
+
+ if (advance_packet_index) {
+ srtp_rdbx_set_roc_seq(&stream->rtp_rdbx, (uint32_t)(est >> 16),
+ (uint16_t)(est & 0xFFFF));
+ stream->pending_roc = 0;
+ srtp_rdbx_add_index(&stream->rtp_rdbx, 0);
+ } else {
+ status = srtp_rdbx_check(&stream->rtp_rdbx, delta);
+ if (status) {
+ if (status != srtp_err_status_replay_fail ||
+ !stream->allow_repeat_tx)
+ return status; /* we've been asked to reuse an index */
+ }
+ srtp_rdbx_add_index(&stream->rtp_rdbx, delta);
+ }
+
+#ifdef NO_64BIT_MATH
+ debug_print2(mod_srtp, "estimated packet index: %08x%08x", high32(est),
+ low32(est));
+#else
+ debug_print(mod_srtp, "estimated packet index: %016llx", est);
+#endif
+
+ /*
+ * if we're using rindael counter mode, set nonce and seq
+ */
+ if (session_keys->rtp_cipher->type->id == SRTP_AES_ICM_128 ||
+ session_keys->rtp_cipher->type->id == SRTP_AES_ICM_192 ||
+ session_keys->rtp_cipher->type->id == SRTP_AES_ICM_256) {
+ v128_t iv;
+
+ iv.v32[0] = 0;
+ iv.v32[1] = hdr->ssrc;
+#ifdef NO_64BIT_MATH
+ iv.v64[1] = be64_to_cpu(
+ make64((high32(est) << 16) | (low32(est) >> 16), low32(est) << 16));
+#else
+ iv.v64[1] = be64_to_cpu(est << 16);
+#endif
+ status = srtp_cipher_set_iv(session_keys->rtp_cipher, (uint8_t *)&iv,
+ srtp_direction_encrypt);
+ if (!status && session_keys->rtp_xtn_hdr_cipher) {
+ status = srtp_cipher_set_iv(session_keys->rtp_xtn_hdr_cipher,
+ (uint8_t *)&iv, srtp_direction_encrypt);
+ }
+ } else {
+ v128_t iv;
+
+/* otherwise, set the index to est */
+#ifdef NO_64BIT_MATH
+ iv.v32[0] = 0;
+ iv.v32[1] = 0;
+#else
+ iv.v64[0] = 0;
+#endif
+ iv.v64[1] = be64_to_cpu(est);
+ status = srtp_cipher_set_iv(session_keys->rtp_cipher, (uint8_t *)&iv,
+ srtp_direction_encrypt);
+ if (!status && session_keys->rtp_xtn_hdr_cipher) {
+ status = srtp_cipher_set_iv(session_keys->rtp_xtn_hdr_cipher,
+ (uint8_t *)&iv, srtp_direction_encrypt);
+ }
+ }
+ if (status)
+ return srtp_err_status_cipher_fail;
+
+/* shift est, put into network byte order */
+#ifdef NO_64BIT_MATH
+ est = be64_to_cpu(
+ make64((high32(est) << 16) | (low32(est) >> 16), low32(est) << 16));
+#else
+ est = be64_to_cpu(est << 16);
+#endif
+
+ /*
+ * if we're authenticating using a universal hash, put the keystream
+ * prefix into the authentication tag
+ */
+ if (auth_start) {
+ prefix_len = srtp_auth_get_prefix_length(session_keys->rtp_auth);
+ if (prefix_len) {
+ status = srtp_cipher_output(session_keys->rtp_cipher, auth_tag,
+ &prefix_len);
+ if (status)
+ return srtp_err_status_cipher_fail;
+ debug_print(mod_srtp, "keystream prefix: %s",
+ srtp_octet_string_hex_string(auth_tag, prefix_len));
+ }
+ }
+
+ if (xtn_hdr && session_keys->rtp_xtn_hdr_cipher) {
+ /*
+ * extensions header encryption RFC 6904
+ */
+ status = srtp_process_header_encryption(stream, xtn_hdr, session_keys);
+ if (status) {
+ return status;
+ }
+ }
+
+ /* if we're encrypting, exor keystream into the message */
+ if (enc_start) {
+ status =
+ srtp_cipher_encrypt(session_keys->rtp_cipher, (uint8_t *)enc_start,
+ (unsigned int *)&enc_octet_len);
+ if (status)
+ return srtp_err_status_cipher_fail;
+ }
+
+ /*
+ * if we're authenticating, run authentication function and put result
+ * into the auth_tag
+ */
+ if (auth_start) {
+ /* initialize auth func context */
+ status = srtp_auth_start(session_keys->rtp_auth);
+ if (status)
+ return status;
+
+ /* run auth func over packet */
+ status = srtp_auth_update(session_keys->rtp_auth, (uint8_t *)auth_start,
+ *pkt_octet_len);
+ if (status)
+ return status;
+
+ /* run auth func over ROC, put result into auth_tag */
+ debug_print(mod_srtp, "estimated packet index: %016llx", est);
+ status = srtp_auth_compute(session_keys->rtp_auth, (uint8_t *)&est, 4,
+ auth_tag);
+ debug_print(mod_srtp, "srtp auth tag: %s",
+ srtp_octet_string_hex_string(auth_tag, tag_len));
+ if (status)
+ return srtp_err_status_auth_fail;
+ }
+
+ if (auth_tag) {
+ /* increase the packet length by the length of the auth tag */
+ *pkt_octet_len += tag_len;
+ }
+
+ if (use_mki) {
+ /* increate the packet length by the mki size */
+ *pkt_octet_len += mki_size;
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_unprotect(srtp_ctx_t *ctx,
+ void *srtp_hdr,
+ int *pkt_octet_len)
+{
+ return srtp_unprotect_mki(ctx, srtp_hdr, pkt_octet_len, 0);
+}
+
+srtp_err_status_t srtp_unprotect_mki(srtp_ctx_t *ctx,
+ void *srtp_hdr,
+ int *pkt_octet_len,
+ unsigned int use_mki)
+{
+ srtp_hdr_t *hdr = (srtp_hdr_t *)srtp_hdr;
+ uint32_t *enc_start; /* pointer to start of encrypted portion */
+ uint32_t *auth_start; /* pointer to start of auth. portion */
+ unsigned int enc_octet_len = 0; /* number of octets in encrypted portion */
+ uint8_t *auth_tag = NULL; /* location of auth_tag within packet */
+ srtp_xtd_seq_num_t est; /* estimated xtd_seq_num_t of *hdr */
+ int delta; /* delta of local pkt idx and that in hdr */
+ v128_t iv;
+ srtp_err_status_t status;
+ srtp_stream_ctx_t *stream;
+ uint8_t tmp_tag[SRTP_MAX_TAG_LEN];
+ uint32_t tag_len, prefix_len;
+ srtp_hdr_xtnd_t *xtn_hdr = NULL;
+ unsigned int mki_size = 0;
+ srtp_session_keys_t *session_keys = NULL;
+ int advance_packet_index = 0;
+ uint32_t roc_to_set = 0;
+ uint16_t seq_to_set = 0;
+
+ debug_print(mod_srtp, "function srtp_unprotect", NULL);
+
+ /* we assume the hdr is 32-bit aligned to start */
+
+ /* Verify RTP header */
+ status = srtp_validate_rtp_header(srtp_hdr, pkt_octet_len);
+ if (status)
+ return status;
+
+ /* check the packet length - it must at least contain a full header */
+ if (*pkt_octet_len < octets_in_rtp_header)
+ return srtp_err_status_bad_param;
+
+ /*
+ * look up ssrc in srtp_stream list, and process the packet with
+ * the appropriate stream. if we haven't seen this stream before,
+ * there's only one key for this srtp_session, and the cipher
+ * supports key-sharing, then we assume that a new stream using
+ * that key has just started up
+ */
+ stream = srtp_get_stream(ctx, hdr->ssrc);
+ if (stream == NULL) {
+ if (ctx->stream_template != NULL) {
+ stream = ctx->stream_template;
+ debug_print(mod_srtp, "using provisional stream (SSRC: 0x%08x)",
+ ntohl(hdr->ssrc));
+
+/*
+ * set estimated packet index to sequence number from header,
+ * and set delta equal to the same value
+ */
+#ifdef NO_64BIT_MATH
+ est = (srtp_xtd_seq_num_t)make64(0, ntohs(hdr->seq));
+ delta = low32(est);
+#else
+ est = (srtp_xtd_seq_num_t)ntohs(hdr->seq);
+ delta = (int)est;
+#endif
+ } else {
+ /*
+ * no stream corresponding to SSRC found, and we don't do
+ * key-sharing, so return an error
+ */
+ return srtp_err_status_no_ctx;
+ }
+ } else {
+ status = srtp_get_est_pkt_index(hdr, stream, &est, &delta);
+
+ if (status && (status != srtp_err_status_pkt_idx_adv))
+ return status;
+
+ if (status == srtp_err_status_pkt_idx_adv) {
+ advance_packet_index = 1;
+ roc_to_set = (uint32_t)(est >> 16);
+ seq_to_set = (uint16_t)(est & 0xFFFF);
+ }
+
+ /* check replay database */
+ if (!advance_packet_index) {
+ status = srtp_rdbx_check(&stream->rtp_rdbx, delta);
+ if (status)
+ return status;
+ }
+ }
+
+#ifdef NO_64BIT_MATH
+ debug_print2(mod_srtp, "estimated u_packet index: %08x%08x", high32(est),
+ low32(est));
+#else
+ debug_print(mod_srtp, "estimated u_packet index: %016llx", est);
+#endif
+
+ /* Determine if MKI is being used and what session keys should be used */
+ if (use_mki) {
+ session_keys = srtp_get_session_keys(
+ stream, (uint8_t *)hdr, (const unsigned int *)pkt_octet_len,
+ &mki_size);
+
+ if (session_keys == NULL)
+ return srtp_err_status_bad_mki;
+ } else {
+ session_keys = &stream->session_keys[0];
+ }
+
+ /*
+ * Check if this is an AEAD stream (GCM mode). If so, then dispatch
+ * the request to our AEAD handler.
+ */
+ if (session_keys->rtp_cipher->algorithm == SRTP_AES_GCM_128 ||
+ session_keys->rtp_cipher->algorithm == SRTP_AES_GCM_256) {
+ return srtp_unprotect_aead(ctx, stream, delta, est, srtp_hdr,
+ (unsigned int *)pkt_octet_len, session_keys,
+ mki_size);
+ }
+
+ /* get tag length from stream */
+ tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth);
+
+ /*
+ * set the cipher's IV properly, depending on whatever cipher we
+ * happen to be using
+ */
+ if (session_keys->rtp_cipher->type->id == SRTP_AES_ICM_128 ||
+ session_keys->rtp_cipher->type->id == SRTP_AES_ICM_192 ||
+ session_keys->rtp_cipher->type->id == SRTP_AES_ICM_256) {
+ /* aes counter mode */
+ iv.v32[0] = 0;
+ iv.v32[1] = hdr->ssrc; /* still in network order */
+#ifdef NO_64BIT_MATH
+ iv.v64[1] = be64_to_cpu(
+ make64((high32(est) << 16) | (low32(est) >> 16), low32(est) << 16));
+#else
+ iv.v64[1] = be64_to_cpu(est << 16);
+#endif
+ status = srtp_cipher_set_iv(session_keys->rtp_cipher, (uint8_t *)&iv,
+ srtp_direction_decrypt);
+ if (!status && session_keys->rtp_xtn_hdr_cipher) {
+ status = srtp_cipher_set_iv(session_keys->rtp_xtn_hdr_cipher,
+ (uint8_t *)&iv, srtp_direction_decrypt);
+ }
+ } else {
+/* no particular format - set the iv to the pakcet index */
+#ifdef NO_64BIT_MATH
+ iv.v32[0] = 0;
+ iv.v32[1] = 0;
+#else
+ iv.v64[0] = 0;
+#endif
+ iv.v64[1] = be64_to_cpu(est);
+ status = srtp_cipher_set_iv(session_keys->rtp_cipher, (uint8_t *)&iv,
+ srtp_direction_decrypt);
+ if (!status && session_keys->rtp_xtn_hdr_cipher) {
+ status = srtp_cipher_set_iv(session_keys->rtp_xtn_hdr_cipher,
+ (uint8_t *)&iv, srtp_direction_decrypt);
+ }
+ }
+ if (status)
+ return srtp_err_status_cipher_fail;
+
+/* shift est, put into network byte order */
+#ifdef NO_64BIT_MATH
+ est = be64_to_cpu(
+ make64((high32(est) << 16) | (low32(est) >> 16), low32(est) << 16));
+#else
+ est = be64_to_cpu(est << 16);
+#endif
+
+ /*
+ * find starting point for decryption and length of data to be
+ * decrypted - the encrypted portion starts after the rtp header
+ * extension, if present; otherwise, it starts after the last csrc,
+ * if any are present
+ *
+ * if we're not providing confidentiality, set enc_start to NULL
+ */
+ if (stream->rtp_services & sec_serv_conf) {
+ enc_start = (uint32_t *)hdr + uint32s_in_rtp_header + hdr->cc;
+ if (hdr->x == 1) {
+ xtn_hdr = (srtp_hdr_xtnd_t *)enc_start;
+ enc_start += (ntohs(xtn_hdr->length) + 1);
+ }
+ if (!((uint8_t *)enc_start <=
+ (uint8_t *)hdr + (*pkt_octet_len - tag_len - mki_size)))
+ return srtp_err_status_parse_err;
+ enc_octet_len = (uint32_t)(*pkt_octet_len - tag_len - mki_size -
+ ((uint8_t *)enc_start - (uint8_t *)hdr));
+ } else {
+ enc_start = NULL;
+ }
+
+ /*
+ * if we're providing authentication, set the auth_start and auth_tag
+ * pointers to the proper locations; otherwise, set auth_start to NULL
+ * to indicate that no authentication is needed
+ */
+ if (stream->rtp_services & sec_serv_auth) {
+ auth_start = (uint32_t *)hdr;
+ auth_tag = (uint8_t *)hdr + *pkt_octet_len - tag_len;
+ } else {
+ auth_start = NULL;
+ auth_tag = NULL;
+ }
+
+ /*
+ * if we expect message authentication, run the authentication
+ * function and compare the result with the value of the auth_tag
+ */
+ if (auth_start) {
+ /*
+ * if we're using a universal hash, then we need to compute the
+ * keystream prefix for encrypting the universal hash output
+ *
+ * if the keystream prefix length is zero, then we know that
+ * the authenticator isn't using a universal hash function
+ */
+ if (session_keys->rtp_auth->prefix_len != 0) {
+ prefix_len = srtp_auth_get_prefix_length(session_keys->rtp_auth);
+ status = srtp_cipher_output(session_keys->rtp_cipher, tmp_tag,
+ &prefix_len);
+ debug_print(mod_srtp, "keystream prefix: %s",
+ srtp_octet_string_hex_string(tmp_tag, prefix_len));
+ if (status)
+ return srtp_err_status_cipher_fail;
+ }
+
+ /* initialize auth func context */
+ status = srtp_auth_start(session_keys->rtp_auth);
+ if (status)
+ return status;
+
+ /* now compute auth function over packet */
+ status = srtp_auth_update(session_keys->rtp_auth, (uint8_t *)auth_start,
+ *pkt_octet_len - tag_len - mki_size);
+
+ /* run auth func over ROC, then write tmp tag */
+ status = srtp_auth_compute(session_keys->rtp_auth, (uint8_t *)&est, 4,
+ tmp_tag);
+
+ debug_print(mod_srtp, "computed auth tag: %s",
+ srtp_octet_string_hex_string(tmp_tag, tag_len));
+ debug_print(mod_srtp, "packet auth tag: %s",
+ srtp_octet_string_hex_string(auth_tag, tag_len));
+ if (status)
+ return srtp_err_status_auth_fail;
+
+ if (octet_string_is_eq(tmp_tag, auth_tag, tag_len))
+ return srtp_err_status_auth_fail;
+ }
+
+ /*
+ * update the key usage limit, and check it to make sure that we
+ * didn't just hit either the soft limit or the hard limit, and call
+ * the event handler if we hit either.
+ */
+ switch (srtp_key_limit_update(session_keys->limit)) {
+ case srtp_key_event_normal:
+ break;
+ case srtp_key_event_soft_limit:
+ srtp_handle_event(ctx, stream, event_key_soft_limit);
+ break;
+ case srtp_key_event_hard_limit:
+ srtp_handle_event(ctx, stream, event_key_hard_limit);
+ return srtp_err_status_key_expired;
+ default:
+ break;
+ }
+
+ if (xtn_hdr && session_keys->rtp_xtn_hdr_cipher) {
+ /* extensions header encryption RFC 6904 */
+ status = srtp_process_header_encryption(stream, xtn_hdr, session_keys);
+ if (status) {
+ return status;
+ }
+ }
+
+ /* if we're decrypting, add keystream into ciphertext */
+ if (enc_start) {
+ status = srtp_cipher_decrypt(session_keys->rtp_cipher,
+ (uint8_t *)enc_start, &enc_octet_len);
+ if (status)
+ return srtp_err_status_cipher_fail;
+ }
+
+ /*
+ * verify that stream is for received traffic - this check will
+ * detect SSRC collisions, since a stream that appears in both
+ * srtp_protect() and srtp_unprotect() will fail this test in one of
+ * those functions.
+ *
+ * we do this check *after* the authentication check, so that the
+ * latter check will catch any attempts to fool us into thinking
+ * that we've got a collision
+ */
+ if (stream->direction != dir_srtp_receiver) {
+ if (stream->direction == dir_unknown) {
+ stream->direction = dir_srtp_receiver;
+ } else {
+ srtp_handle_event(ctx, stream, event_ssrc_collision);
+ }
+ }
+
+ /*
+ * if the stream is a 'provisional' one, in which the template context
+ * is used, then we need to allocate a new stream at this point, since
+ * the authentication passed
+ */
+ if (stream == ctx->stream_template) {
+ srtp_stream_ctx_t *new_stream;
+
+ /*
+ * allocate and initialize a new stream
+ *
+ * note that we indicate failure if we can't allocate the new
+ * stream, and some implementations will want to not return
+ * failure here
+ */
+ status =
+ srtp_stream_clone(ctx->stream_template, hdr->ssrc, &new_stream);
+ if (status)
+ return status;
+
+ /* add new stream to the head of the stream_list */
+ new_stream->next = ctx->stream_list;
+ ctx->stream_list = new_stream;
+
+ /* set stream (the pointer used in this function) */
+ stream = new_stream;
+ }
+
+ /*
+ * the message authentication function passed, so add the packet
+ * index into the replay database
+ */
+ if (advance_packet_index) {
+ srtp_rdbx_set_roc_seq(&stream->rtp_rdbx, roc_to_set, seq_to_set);
+ stream->pending_roc = 0;
+ srtp_rdbx_add_index(&stream->rtp_rdbx, 0);
+ } else {
+ srtp_rdbx_add_index(&stream->rtp_rdbx, delta);
+ }
+
+ /* decrease the packet length by the length of the auth tag */
+ *pkt_octet_len -= tag_len;
+
+ /* decrease the packet length by the mki size */
+ *pkt_octet_len -= mki_size;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_init()
+{
+ srtp_err_status_t status;
+
+ /* initialize crypto kernel */
+ status = srtp_crypto_kernel_init();
+ if (status)
+ return status;
+
+ /* load srtp debug module into the kernel */
+ status = srtp_crypto_kernel_load_debug_module(&mod_srtp);
+ if (status)
+ return status;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_shutdown()
+{
+ srtp_err_status_t status;
+
+ /* shut down crypto kernel */
+ status = srtp_crypto_kernel_shutdown();
+ if (status)
+ return status;
+
+ /* shutting down crypto kernel frees the srtp debug module as well */
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * The following code is under consideration for removal. See
+ * SRTP_MAX_TRAILER_LEN
+ */
+#if 0
+
+/*
+ * srtp_get_trailer_length(&a) returns the number of octets that will
+ * be added to an RTP packet by the SRTP processing. This value
+ * is constant for a given srtp_stream_t (i.e. between initializations).
+ */
+
+int
+srtp_get_trailer_length(const srtp_stream_t s) {
+ return srtp_auth_get_tag_length(s->rtp_auth);
+}
+
+#endif
+
+/*
+ * srtp_get_stream(ssrc) returns a pointer to the stream corresponding
+ * to ssrc, or NULL if no stream exists for that ssrc
+ *
+ * this is an internal function
+ */
+
+srtp_stream_ctx_t *srtp_get_stream(srtp_t srtp, uint32_t ssrc)
+{
+ srtp_stream_ctx_t *stream;
+
+ /* walk down list until ssrc is found */
+ stream = srtp->stream_list;
+ while (stream != NULL) {
+ if (stream->ssrc == ssrc)
+ return stream;
+ stream = stream->next;
+ }
+
+ /* we haven't found our ssrc, so return a null */
+ return NULL;
+}
+
+srtp_err_status_t srtp_dealloc(srtp_t session)
+{
+ srtp_stream_ctx_t *stream;
+ srtp_err_status_t status;
+
+ /*
+ * we take a conservative deallocation strategy - if we encounter an
+ * error deallocating a stream, then we stop trying to deallocate
+ * memory and just return an error
+ */
+
+ /* walk list of streams, deallocating as we go */
+ stream = session->stream_list;
+ while (stream != NULL) {
+ srtp_stream_t next = stream->next;
+ status = srtp_stream_dealloc(stream, session->stream_template);
+ if (status)
+ return status;
+ stream = next;
+ }
+
+ /* deallocate stream template, if there is one */
+ if (session->stream_template != NULL) {
+ status = srtp_stream_dealloc(session->stream_template, NULL);
+ if (status)
+ return status;
+ }
+
+ /* deallocate session context */
+ srtp_crypto_free(session);
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_add_stream(srtp_t session, const srtp_policy_t *policy)
+{
+ srtp_err_status_t status;
+ srtp_stream_t tmp;
+
+ /* sanity check arguments */
+ if ((session == NULL) || (policy == NULL) ||
+ (!srtp_validate_policy_master_keys(policy)))
+ return srtp_err_status_bad_param;
+
+ /* allocate stream */
+ status = srtp_stream_alloc(&tmp, policy);
+ if (status) {
+ return status;
+ }
+
+ /* initialize stream */
+ status = srtp_stream_init(tmp, policy);
+ if (status) {
+ srtp_stream_dealloc(tmp, NULL);
+ return status;
+ }
+
+ /*
+ * set the head of the stream list or the template to point to the
+ * stream that we've just alloced and init'ed, depending on whether
+ * or not it has a wildcard SSRC value or not
+ *
+ * if the template stream has already been set, then the policy is
+ * inconsistent, so we return a bad_param error code
+ */
+ switch (policy->ssrc.type) {
+ case (ssrc_any_outbound):
+ if (session->stream_template) {
+ srtp_stream_dealloc(tmp, NULL);
+ return srtp_err_status_bad_param;
+ }
+ session->stream_template = tmp;
+ session->stream_template->direction = dir_srtp_sender;
+ break;
+ case (ssrc_any_inbound):
+ if (session->stream_template) {
+ srtp_stream_dealloc(tmp, NULL);
+ return srtp_err_status_bad_param;
+ }
+ session->stream_template = tmp;
+ session->stream_template->direction = dir_srtp_receiver;
+ break;
+ case (ssrc_specific):
+ tmp->next = session->stream_list;
+ session->stream_list = tmp;
+ break;
+ case (ssrc_undefined):
+ default:
+ srtp_stream_dealloc(tmp, NULL);
+ return srtp_err_status_bad_param;
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_create(srtp_t *session, /* handle for session */
+ const srtp_policy_t *policy)
+{ /* SRTP policy (list) */
+ srtp_err_status_t stat;
+ srtp_ctx_t *ctx;
+
+ /* sanity check arguments */
+ if (session == NULL)
+ return srtp_err_status_bad_param;
+
+ /* allocate srtp context and set ctx_ptr */
+ ctx = (srtp_ctx_t *)srtp_crypto_alloc(sizeof(srtp_ctx_t));
+ if (ctx == NULL)
+ return srtp_err_status_alloc_fail;
+ *session = ctx;
+
+ /*
+ * loop over elements in the policy list, allocating and
+ * initializing a stream for each element
+ */
+ ctx->stream_template = NULL;
+ ctx->stream_list = NULL;
+ ctx->user_data = NULL;
+ while (policy != NULL) {
+ stat = srtp_add_stream(ctx, policy);
+ if (stat) {
+ /* clean up everything */
+ srtp_dealloc(*session);
+ *session = NULL;
+ return stat;
+ }
+
+ /* set policy to next item in list */
+ policy = policy->next;
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_remove_stream(srtp_t session, uint32_t ssrc)
+{
+ srtp_stream_ctx_t *stream, *last_stream;
+ srtp_err_status_t status;
+
+ /* sanity check arguments */
+ if (session == NULL)
+ return srtp_err_status_bad_param;
+
+ /* find stream in list; complain if not found */
+ last_stream = stream = session->stream_list;
+ while ((stream != NULL) && (ssrc != stream->ssrc)) {
+ last_stream = stream;
+ stream = stream->next;
+ }
+ if (stream == NULL)
+ return srtp_err_status_no_ctx;
+
+ /* remove stream from the list */
+ if (last_stream == stream)
+ /* stream was first in list */
+ session->stream_list = stream->next;
+ else
+ last_stream->next = stream->next;
+
+ /* deallocate the stream */
+ status = srtp_stream_dealloc(stream, session->stream_template);
+ if (status)
+ return status;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_update(srtp_t session, const srtp_policy_t *policy)
+{
+ srtp_err_status_t stat;
+
+ /* sanity check arguments */
+ if ((session == NULL) || (policy == NULL) ||
+ (!srtp_validate_policy_master_keys(policy))) {
+ return srtp_err_status_bad_param;
+ }
+
+ while (policy != NULL) {
+ stat = srtp_update_stream(session, policy);
+ if (stat) {
+ return stat;
+ }
+
+ /* set policy to next item in list */
+ policy = policy->next;
+ }
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t update_template_streams(srtp_t session,
+ const srtp_policy_t *policy)
+{
+ srtp_err_status_t status;
+ srtp_stream_t new_stream_template;
+ srtp_stream_t new_stream_list = NULL;
+
+ if (session->stream_template == NULL) {
+ return srtp_err_status_bad_param;
+ }
+
+ /* allocate new template stream */
+ status = srtp_stream_alloc(&new_stream_template, policy);
+ if (status) {
+ return status;
+ }
+
+ /* initialize new template stream */
+ status = srtp_stream_init(new_stream_template, policy);
+ if (status) {
+ srtp_crypto_free(new_stream_template);
+ return status;
+ }
+
+ /* for all old templated streams */
+ for (;;) {
+ srtp_stream_t stream;
+ uint32_t ssrc;
+ srtp_xtd_seq_num_t old_index;
+ srtp_rdb_t old_rtcp_rdb;
+
+ stream = session->stream_list;
+ while ((stream != NULL) &&
+ (stream->session_keys[0].rtp_auth !=
+ session->stream_template->session_keys[0].rtp_auth)) {
+ stream = stream->next;
+ }
+ if (stream == NULL) {
+ /* no more templated streams */
+ break;
+ }
+
+ /* save old extendard seq */
+ ssrc = stream->ssrc;
+ old_index = stream->rtp_rdbx.index;
+ old_rtcp_rdb = stream->rtcp_rdb;
+
+ /* remove stream */
+ status = srtp_remove_stream(session, ssrc);
+ if (status) {
+ /* free new allocations */
+ while (new_stream_list != NULL) {
+ srtp_stream_t next = new_stream_list->next;
+ srtp_stream_dealloc(new_stream_list, new_stream_template);
+ new_stream_list = next;
+ }
+ srtp_stream_dealloc(new_stream_template, NULL);
+ return status;
+ }
+
+ /* allocate and initialize a new stream */
+ status = srtp_stream_clone(new_stream_template, ssrc, &stream);
+ if (status) {
+ /* free new allocations */
+ while (new_stream_list != NULL) {
+ srtp_stream_t next = new_stream_list->next;
+ srtp_stream_dealloc(new_stream_list, new_stream_template);
+ new_stream_list = next;
+ }
+ srtp_stream_dealloc(new_stream_template, NULL);
+ return status;
+ }
+
+ /* add new stream to the head of the new_stream_list */
+ stream->next = new_stream_list;
+ new_stream_list = stream;
+
+ /* restore old extended seq */
+ stream->rtp_rdbx.index = old_index;
+ stream->rtcp_rdb = old_rtcp_rdb;
+ }
+ /* dealloc old template */
+ srtp_stream_dealloc(session->stream_template, NULL);
+ /* set new template */
+ session->stream_template = new_stream_template;
+ /* add new list */
+ if (new_stream_list) {
+ srtp_stream_t tail = new_stream_list;
+ while (tail->next) {
+ tail = tail->next;
+ }
+ tail->next = session->stream_list;
+ session->stream_list = new_stream_list;
+ }
+ return status;
+}
+
+static srtp_err_status_t update_stream(srtp_t session,
+ const srtp_policy_t *policy)
+{
+ srtp_err_status_t status;
+ srtp_xtd_seq_num_t old_index;
+ srtp_rdb_t old_rtcp_rdb;
+ srtp_stream_t stream;
+
+ stream = srtp_get_stream(session, htonl(policy->ssrc.value));
+ if (stream == NULL) {
+ return srtp_err_status_bad_param;
+ }
+
+ /* save old extendard seq */
+ old_index = stream->rtp_rdbx.index;
+ old_rtcp_rdb = stream->rtcp_rdb;
+
+ status = srtp_remove_stream(session, htonl(policy->ssrc.value));
+ if (status) {
+ return status;
+ }
+
+ status = srtp_add_stream(session, policy);
+ if (status) {
+ return status;
+ }
+
+ stream = srtp_get_stream(session, htonl(policy->ssrc.value));
+ if (stream == NULL) {
+ return srtp_err_status_fail;
+ }
+
+ /* restore old extended seq */
+ stream->rtp_rdbx.index = old_index;
+ stream->rtcp_rdb = old_rtcp_rdb;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_update_stream(srtp_t session,
+ const srtp_policy_t *policy)
+{
+ srtp_err_status_t status;
+
+ /* sanity check arguments */
+ if ((session == NULL) || (policy == NULL) ||
+ (!srtp_validate_policy_master_keys(policy)))
+ return srtp_err_status_bad_param;
+
+ switch (policy->ssrc.type) {
+ case (ssrc_any_outbound):
+ case (ssrc_any_inbound):
+ status = update_template_streams(session, policy);
+ break;
+ case (ssrc_specific):
+ status = update_stream(session, policy);
+ break;
+ case (ssrc_undefined):
+ default:
+ return srtp_err_status_bad_param;
+ }
+
+ return status;
+}
+
+/*
+ * The default policy - provides a convenient way for callers to use
+ * the default security policy
+ *
+ * The default policy is defined in RFC 3711
+ * (Section 5. Default and mandatory-to-implement Transforms)
+ *
+ */
+
+/*
+ * NOTE: cipher_key_len is really key len (128 bits) plus salt len
+ * (112 bits)
+ */
+/* There are hard-coded 16's for base_key_len in the key generation code */
+
+void srtp_crypto_policy_set_rtp_default(srtp_crypto_policy_t *p)
+{
+ p->cipher_type = SRTP_AES_ICM_128;
+ p->cipher_key_len =
+ SRTP_AES_ICM_128_KEY_LEN_WSALT; /* default 128 bits per RFC 3711 */
+ p->auth_type = SRTP_HMAC_SHA1;
+ p->auth_key_len = 20; /* default 160 bits per RFC 3711 */
+ p->auth_tag_len = 10; /* default 80 bits per RFC 3711 */
+ p->sec_serv = sec_serv_conf_and_auth;
+}
+
+void srtp_crypto_policy_set_rtcp_default(srtp_crypto_policy_t *p)
+{
+ p->cipher_type = SRTP_AES_ICM_128;
+ p->cipher_key_len =
+ SRTP_AES_ICM_128_KEY_LEN_WSALT; /* default 128 bits per RFC 3711 */
+ p->auth_type = SRTP_HMAC_SHA1;
+ p->auth_key_len = 20; /* default 160 bits per RFC 3711 */
+ p->auth_tag_len = 10; /* default 80 bits per RFC 3711 */
+ p->sec_serv = sec_serv_conf_and_auth;
+}
+
+void srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(srtp_crypto_policy_t *p)
+{
+ /*
+ * corresponds to RFC 4568
+ *
+ * note that this crypto policy is intended for SRTP, but not SRTCP
+ */
+
+ p->cipher_type = SRTP_AES_ICM_128;
+ p->cipher_key_len =
+ SRTP_AES_ICM_128_KEY_LEN_WSALT; /* 128 bit key, 112 bit salt */
+ p->auth_type = SRTP_HMAC_SHA1;
+ p->auth_key_len = 20; /* 160 bit key */
+ p->auth_tag_len = 4; /* 32 bit tag */
+ p->sec_serv = sec_serv_conf_and_auth;
+}
+
+void srtp_crypto_policy_set_aes_cm_128_null_auth(srtp_crypto_policy_t *p)
+{
+ /*
+ * corresponds to RFC 4568
+ *
+ * note that this crypto policy is intended for SRTP, but not SRTCP
+ */
+
+ p->cipher_type = SRTP_AES_ICM_128;
+ p->cipher_key_len =
+ SRTP_AES_ICM_128_KEY_LEN_WSALT; /* 128 bit key, 112 bit salt */
+ p->auth_type = SRTP_NULL_AUTH;
+ p->auth_key_len = 0;
+ p->auth_tag_len = 0;
+ p->sec_serv = sec_serv_conf;
+}
+
+void srtp_crypto_policy_set_null_cipher_hmac_sha1_80(srtp_crypto_policy_t *p)
+{
+ /*
+ * corresponds to RFC 4568
+ */
+
+ p->cipher_type = SRTP_NULL_CIPHER;
+ p->cipher_key_len = 0;
+ p->auth_type = SRTP_HMAC_SHA1;
+ p->auth_key_len = 20;
+ p->auth_tag_len = 10;
+ p->sec_serv = sec_serv_auth;
+}
+
+void srtp_crypto_policy_set_null_cipher_hmac_null(srtp_crypto_policy_t *p)
+{
+ /*
+ * Should only be used for testing
+ */
+
+ p->cipher_type = SRTP_NULL_CIPHER;
+ p->cipher_key_len = 0;
+ p->auth_type = SRTP_NULL_AUTH;
+ p->auth_key_len = 0;
+ p->auth_tag_len = 0;
+ p->sec_serv = sec_serv_none;
+}
+
+void srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(srtp_crypto_policy_t *p)
+{
+ /*
+ * corresponds to RFC 6188
+ */
+
+ p->cipher_type = SRTP_AES_ICM_256;
+ p->cipher_key_len = SRTP_AES_ICM_256_KEY_LEN_WSALT;
+ p->auth_type = SRTP_HMAC_SHA1;
+ p->auth_key_len = 20; /* default 160 bits per RFC 3711 */
+ p->auth_tag_len = 10; /* default 80 bits per RFC 3711 */
+ p->sec_serv = sec_serv_conf_and_auth;
+}
+
+void srtp_crypto_policy_set_aes_cm_256_hmac_sha1_32(srtp_crypto_policy_t *p)
+{
+ /*
+ * corresponds to RFC 6188
+ *
+ * note that this crypto policy is intended for SRTP, but not SRTCP
+ */
+
+ p->cipher_type = SRTP_AES_ICM_256;
+ p->cipher_key_len = SRTP_AES_ICM_256_KEY_LEN_WSALT;
+ p->auth_type = SRTP_HMAC_SHA1;
+ p->auth_key_len = 20; /* default 160 bits per RFC 3711 */
+ p->auth_tag_len = 4; /* default 80 bits per RFC 3711 */
+ p->sec_serv = sec_serv_conf_and_auth;
+}
+
+/*
+ * AES-256 with no authentication.
+ */
+void srtp_crypto_policy_set_aes_cm_256_null_auth(srtp_crypto_policy_t *p)
+{
+ p->cipher_type = SRTP_AES_ICM_256;
+ p->cipher_key_len = SRTP_AES_ICM_256_KEY_LEN_WSALT;
+ p->auth_type = SRTP_NULL_AUTH;
+ p->auth_key_len = 0;
+ p->auth_tag_len = 0;
+ p->sec_serv = sec_serv_conf;
+}
+
+#ifdef GCM
+void srtp_crypto_policy_set_aes_cm_192_hmac_sha1_80(srtp_crypto_policy_t *p)
+{
+ /*
+ * corresponds to RFC 6188
+ */
+
+ p->cipher_type = SRTP_AES_ICM_192;
+ p->cipher_key_len = SRTP_AES_ICM_192_KEY_LEN_WSALT;
+ p->auth_type = SRTP_HMAC_SHA1;
+ p->auth_key_len = 20; /* default 160 bits per RFC 3711 */
+ p->auth_tag_len = 10; /* default 80 bits per RFC 3711 */
+ p->sec_serv = sec_serv_conf_and_auth;
+}
+
+void srtp_crypto_policy_set_aes_cm_192_hmac_sha1_32(srtp_crypto_policy_t *p)
+{
+ /*
+ * corresponds to RFC 6188
+ *
+ * note that this crypto policy is intended for SRTP, but not SRTCP
+ */
+
+ p->cipher_type = SRTP_AES_ICM_192;
+ p->cipher_key_len = SRTP_AES_ICM_192_KEY_LEN_WSALT;
+ p->auth_type = SRTP_HMAC_SHA1;
+ p->auth_key_len = 20; /* default 160 bits per RFC 3711 */
+ p->auth_tag_len = 4; /* default 80 bits per RFC 3711 */
+ p->sec_serv = sec_serv_conf_and_auth;
+}
+
+/*
+ * AES-192 with no authentication.
+ */
+void srtp_crypto_policy_set_aes_cm_192_null_auth(srtp_crypto_policy_t *p)
+{
+ p->cipher_type = SRTP_AES_ICM_192;
+ p->cipher_key_len = SRTP_AES_ICM_192_KEY_LEN_WSALT;
+ p->auth_type = SRTP_NULL_AUTH;
+ p->auth_key_len = 0;
+ p->auth_tag_len = 0;
+ p->sec_serv = sec_serv_conf;
+}
+
+/*
+ * AES-128 GCM mode with 8 octet auth tag.
+ */
+void srtp_crypto_policy_set_aes_gcm_128_8_auth(srtp_crypto_policy_t *p)
+{
+ p->cipher_type = SRTP_AES_GCM_128;
+ p->cipher_key_len = SRTP_AES_GCM_128_KEY_LEN_WSALT;
+ p->auth_type = SRTP_NULL_AUTH; /* GCM handles the auth for us */
+ p->auth_key_len = 0;
+ p->auth_tag_len = 8; /* 8 octet tag length */
+ p->sec_serv = sec_serv_conf_and_auth;
+}
+
+/*
+ * AES-256 GCM mode with 8 octet auth tag.
+ */
+void srtp_crypto_policy_set_aes_gcm_256_8_auth(srtp_crypto_policy_t *p)
+{
+ p->cipher_type = SRTP_AES_GCM_256;
+ p->cipher_key_len = SRTP_AES_GCM_256_KEY_LEN_WSALT;
+ p->auth_type = SRTP_NULL_AUTH; /* GCM handles the auth for us */
+ p->auth_key_len = 0;
+ p->auth_tag_len = 8; /* 8 octet tag length */
+ p->sec_serv = sec_serv_conf_and_auth;
+}
+
+/*
+ * AES-128 GCM mode with 8 octet auth tag, no RTCP encryption.
+ */
+void srtp_crypto_policy_set_aes_gcm_128_8_only_auth(srtp_crypto_policy_t *p)
+{
+ p->cipher_type = SRTP_AES_GCM_128;
+ p->cipher_key_len = SRTP_AES_GCM_128_KEY_LEN_WSALT;
+ p->auth_type = SRTP_NULL_AUTH; /* GCM handles the auth for us */
+ p->auth_key_len = 0;
+ p->auth_tag_len = 8; /* 8 octet tag length */
+ p->sec_serv = sec_serv_auth; /* This only applies to RTCP */
+}
+
+/*
+ * AES-256 GCM mode with 8 octet auth tag, no RTCP encryption.
+ */
+void srtp_crypto_policy_set_aes_gcm_256_8_only_auth(srtp_crypto_policy_t *p)
+{
+ p->cipher_type = SRTP_AES_GCM_256;
+ p->cipher_key_len = SRTP_AES_GCM_256_KEY_LEN_WSALT;
+ p->auth_type = SRTP_NULL_AUTH; /* GCM handles the auth for us */
+ p->auth_key_len = 0;
+ p->auth_tag_len = 8; /* 8 octet tag length */
+ p->sec_serv = sec_serv_auth; /* This only applies to RTCP */
+}
+
+/*
+ * AES-128 GCM mode with 16 octet auth tag.
+ */
+void srtp_crypto_policy_set_aes_gcm_128_16_auth(srtp_crypto_policy_t *p)
+{
+ p->cipher_type = SRTP_AES_GCM_128;
+ p->cipher_key_len = SRTP_AES_GCM_128_KEY_LEN_WSALT;
+ p->auth_type = SRTP_NULL_AUTH; /* GCM handles the auth for us */
+ p->auth_key_len = 0;
+ p->auth_tag_len = 16; /* 16 octet tag length */
+ p->sec_serv = sec_serv_conf_and_auth;
+}
+
+/*
+ * AES-256 GCM mode with 16 octet auth tag.
+ */
+void srtp_crypto_policy_set_aes_gcm_256_16_auth(srtp_crypto_policy_t *p)
+{
+ p->cipher_type = SRTP_AES_GCM_256;
+ p->cipher_key_len = SRTP_AES_GCM_256_KEY_LEN_WSALT;
+ p->auth_type = SRTP_NULL_AUTH; /* GCM handles the auth for us */
+ p->auth_key_len = 0;
+ p->auth_tag_len = 16; /* 16 octet tag length */
+ p->sec_serv = sec_serv_conf_and_auth;
+}
+
+#endif
+
+/*
+ * secure rtcp functions
+ */
+
+/*
+ * AEAD uses a new IV formation method. This function implements
+ * section 9.1 (SRTCP IV Formation for AES-GCM) from RFC7714.
+ * The calculation is defined as, where (+) is the xor operation:
+ *
+ * 0 1 2 3 4 5 6 7 8 9 10 11
+ * +--+--+--+--+--+--+--+--+--+--+--+--+
+ * |00|00| SSRC |00|00|0+SRTCP Idx|---+
+ * +--+--+--+--+--+--+--+--+--+--+--+--+ |
+ * |
+ * +--+--+--+--+--+--+--+--+--+--+--+--+ |
+ * | Encryption Salt |->(+)
+ * +--+--+--+--+--+--+--+--+--+--+--+--+ |
+ * |
+ * +--+--+--+--+--+--+--+--+--+--+--+--+ |
+ * | Initialization Vector |<--+
+ * +--+--+--+--+--+--+--+--+--+--+--+--+*
+ *
+ * Input: *session_keys - pointer to SRTP stream context session keys,
+ * used to retrieve the SALT
+ * *iv - Pointer to recieve the calculated IV
+ * seq_num - The SEQ value to use for the IV calculation.
+ * *hdr - The RTP header, used to get the SSRC value
+ *
+ * Returns: srtp_err_status_ok if no error or srtp_err_status_bad_param
+ * if seq_num is invalid
+ *
+ */
+static srtp_err_status_t srtp_calc_aead_iv_srtcp(
+ srtp_session_keys_t *session_keys,
+ v128_t *iv,
+ uint32_t seq_num,
+ srtcp_hdr_t *hdr)
+{
+ v128_t in;
+ v128_t salt;
+
+ memset(&in, 0, sizeof(v128_t));
+ memset(&salt, 0, sizeof(v128_t));
+
+ in.v16[0] = 0;
+ memcpy(&in.v16[1], &hdr->ssrc, 4); /* still in network order! */
+ in.v16[3] = 0;
+
+ /*
+ * The SRTCP index (seq_num) spans bits 0 through 30 inclusive.
+ * The most significant bit should be zero.
+ */
+ if (seq_num & 0x80000000UL) {
+ return srtp_err_status_bad_param;
+ }
+ in.v32[2] = htonl(seq_num);
+
+ debug_print(mod_srtp, "Pre-salted RTCP IV = %s\n", v128_hex_string(&in));
+
+ /*
+ * Get the SALT value from the context
+ */
+ memcpy(salt.v8, session_keys->c_salt, 12);
+ debug_print(mod_srtp, "RTCP SALT = %s\n", v128_hex_string(&salt));
+
+ /*
+ * Finally, apply the SALT to the input
+ */
+ v128_xor(iv, &in, &salt);
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * This code handles AEAD ciphers for outgoing RTCP. We currently support
+ * AES-GCM mode with 128 or 256 bit keys.
+ */
+static srtp_err_status_t srtp_protect_rtcp_aead(
+ srtp_t ctx,
+ srtp_stream_ctx_t *stream,
+ void *rtcp_hdr,
+ unsigned int *pkt_octet_len,
+ srtp_session_keys_t *session_keys,
+ unsigned int use_mki)
+{
+ srtcp_hdr_t *hdr = (srtcp_hdr_t *)rtcp_hdr;
+ uint32_t *enc_start; /* pointer to start of encrypted portion */
+ uint32_t *trailer_p; /* pointer to start of trailer */
+ uint32_t trailer; /* trailer value */
+ unsigned int enc_octet_len = 0; /* number of octets in encrypted portion */
+ uint8_t *auth_tag = NULL; /* location of auth_tag within packet */
+ srtp_err_status_t status;
+ uint32_t tag_len;
+ uint32_t seq_num;
+ v128_t iv;
+ uint32_t tseq;
+ unsigned int mki_size = 0;
+
+ /* get tag length from stream context */
+ tag_len = srtp_auth_get_tag_length(session_keys->rtcp_auth);
+
+ /*
+ * set encryption start and encryption length - if we're not
+ * providing confidentiality, set enc_start to NULL
+ */
+ enc_start = (uint32_t *)hdr + uint32s_in_rtcp_header;
+ enc_octet_len = *pkt_octet_len - octets_in_rtcp_header;
+
+ /* NOTE: hdr->length is not usable - it refers to only the first
+ * RTCP report in the compound packet!
+ */
+ trailer_p = (uint32_t *)((char *)enc_start + enc_octet_len + tag_len);
+
+ if (stream->rtcp_services & sec_serv_conf) {
+ trailer = htonl(SRTCP_E_BIT); /* set encrypt bit */
+ } else {
+ enc_start = NULL;
+ enc_octet_len = 0;
+ /* 0 is network-order independant */
+ trailer = 0x00000000; /* set encrypt bit */
+ }
+
+ mki_size = srtp_inject_mki((uint8_t *)hdr + *pkt_octet_len + tag_len +
+ sizeof(srtcp_trailer_t),
+ session_keys, use_mki);
+
+ /*
+ * set the auth_tag pointer to the proper location, which is after
+ * the payload, but before the trailer
+ * (note that srtpc *always* provides authentication, unlike srtp)
+ */
+ /* Note: This would need to change for optional mikey data */
+ auth_tag = (uint8_t *)hdr + *pkt_octet_len;
+
+ /*
+ * check sequence number for overruns, and copy it into the packet
+ * if its value isn't too big
+ */
+ status = srtp_rdb_increment(&stream->rtcp_rdb);
+ if (status) {
+ return status;
+ }
+ seq_num = srtp_rdb_get_value(&stream->rtcp_rdb);
+ trailer |= htonl(seq_num);
+ debug_print(mod_srtp, "srtcp index: %x", seq_num);
+
+ memcpy(trailer_p, &trailer, sizeof(trailer));
+
+ /*
+ * Calculate and set the IV
+ */
+ status = srtp_calc_aead_iv_srtcp(session_keys, &iv, seq_num, hdr);
+ if (status) {
+ return srtp_err_status_cipher_fail;
+ }
+ status = srtp_cipher_set_iv(session_keys->rtcp_cipher, (uint8_t *)&iv,
+ srtp_direction_encrypt);
+ if (status) {
+ return srtp_err_status_cipher_fail;
+ }
+
+ /*
+ * Set the AAD for GCM mode
+ */
+ if (enc_start) {
+ /*
+ * If payload encryption is enabled, then the AAD consist of
+ * the RTCP header and the seq# at the end of the packet
+ */
+ status = srtp_cipher_set_aad(session_keys->rtcp_cipher, (uint8_t *)hdr,
+ octets_in_rtcp_header);
+ if (status) {
+ return (srtp_err_status_cipher_fail);
+ }
+ } else {
+ /*
+ * Since payload encryption is not enabled, we must authenticate
+ * the entire packet as described in RFC 7714 (Section 9.3. Data
+ * Types in Unencrypted SRTCP Compound Packets)
+ */
+ status = srtp_cipher_set_aad(session_keys->rtcp_cipher, (uint8_t *)hdr,
+ *pkt_octet_len);
+ if (status) {
+ return (srtp_err_status_cipher_fail);
+ }
+ }
+ /*
+ * Process the sequence# as AAD
+ */
+ tseq = trailer;
+ status = srtp_cipher_set_aad(session_keys->rtcp_cipher, (uint8_t *)&tseq,
+ sizeof(srtcp_trailer_t));
+ if (status) {
+ return (srtp_err_status_cipher_fail);
+ }
+
+ /* if we're encrypting, exor keystream into the message */
+ if (enc_start) {
+ status = srtp_cipher_encrypt(session_keys->rtcp_cipher,
+ (uint8_t *)enc_start, &enc_octet_len);
+ if (status) {
+ return srtp_err_status_cipher_fail;
+ }
+ /*
+ * Get the tag and append that to the output
+ */
+ status = srtp_cipher_get_tag(session_keys->rtcp_cipher,
+ (uint8_t *)auth_tag, &tag_len);
+ if (status) {
+ return (srtp_err_status_cipher_fail);
+ }
+ enc_octet_len += tag_len;
+ } else {
+ /*
+ * Even though we're not encrypting the payload, we need
+ * to run the cipher to get the auth tag.
+ */
+ unsigned int nolen = 0;
+ status = srtp_cipher_encrypt(session_keys->rtcp_cipher, NULL, &nolen);
+ if (status) {
+ return srtp_err_status_cipher_fail;
+ }
+ /*
+ * Get the tag and append that to the output
+ */
+ status = srtp_cipher_get_tag(session_keys->rtcp_cipher,
+ (uint8_t *)auth_tag, &tag_len);
+ if (status) {
+ return (srtp_err_status_cipher_fail);
+ }
+ enc_octet_len += tag_len;
+ }
+
+ /* increase the packet length by the length of the auth tag and seq_num*/
+ *pkt_octet_len += (tag_len + sizeof(srtcp_trailer_t));
+
+ /* increase the packet by the mki_size */
+ *pkt_octet_len += mki_size;
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * This function handles incoming SRTCP packets while in AEAD mode,
+ * which currently supports AES-GCM encryption. Note, the auth tag is
+ * at the end of the packet stream and is automatically checked by GCM
+ * when decrypting the payload.
+ */
+static srtp_err_status_t srtp_unprotect_rtcp_aead(
+ srtp_t ctx,
+ srtp_stream_ctx_t *stream,
+ void *srtcp_hdr,
+ unsigned int *pkt_octet_len,
+ srtp_session_keys_t *session_keys,
+ unsigned int use_mki)
+{
+ srtcp_hdr_t *hdr = (srtcp_hdr_t *)srtcp_hdr;
+ uint32_t *enc_start; /* pointer to start of encrypted portion */
+ uint32_t *trailer_p; /* pointer to start of trailer */
+ uint32_t trailer; /* trailer value */
+ unsigned int enc_octet_len = 0; /* number of octets in encrypted portion */
+ uint8_t *auth_tag = NULL; /* location of auth_tag within packet */
+ srtp_err_status_t status;
+ int tag_len;
+ unsigned int tmp_len;
+ uint32_t seq_num;
+ v128_t iv;
+ uint32_t tseq;
+ unsigned int mki_size = 0;
+
+ /* get tag length from stream context */
+ tag_len = srtp_auth_get_tag_length(session_keys->rtcp_auth);
+
+ if (use_mki) {
+ mki_size = session_keys->mki_size;
+ }
+
+ /*
+ * set encryption start, encryption length, and trailer
+ */
+ /* index & E (encryption) bit follow normal data. hdr->len is the number of
+ * words (32-bit) in the normal packet minus 1
+ */
+ /* This should point trailer to the word past the end of the normal data. */
+ /* This would need to be modified for optional mikey data */
+ trailer_p = (uint32_t *)((char *)hdr + *pkt_octet_len -
+ sizeof(srtcp_trailer_t) - mki_size);
+ memcpy(&trailer, trailer_p, sizeof(trailer));
+
+ /*
+ * We pass the tag down to the cipher when doing GCM mode
+ */
+ enc_octet_len = *pkt_octet_len - (octets_in_rtcp_header +
+ sizeof(srtcp_trailer_t) + mki_size);
+ auth_tag = (uint8_t *)hdr + *pkt_octet_len - tag_len - mki_size -
+ sizeof(srtcp_trailer_t);
+
+ if (*((unsigned char *)trailer_p) & SRTCP_E_BYTE_BIT) {
+ enc_start = (uint32_t *)hdr + uint32s_in_rtcp_header;
+ } else {
+ enc_octet_len = 0;
+ enc_start = NULL; /* this indicates that there's no encryption */
+ }
+
+ /*
+ * check the sequence number for replays
+ */
+ /* this is easier than dealing with bitfield access */
+ seq_num = ntohl(trailer) & SRTCP_INDEX_MASK;
+ debug_print(mod_srtp, "srtcp index: %x", seq_num);
+ status = srtp_rdb_check(&stream->rtcp_rdb, seq_num);
+ if (status) {
+ return status;
+ }
+
+ /*
+ * Calculate and set the IV
+ */
+ status = srtp_calc_aead_iv_srtcp(session_keys, &iv, seq_num, hdr);
+ if (status) {
+ return srtp_err_status_cipher_fail;
+ }
+ status = srtp_cipher_set_iv(session_keys->rtcp_cipher, (uint8_t *)&iv,
+ srtp_direction_decrypt);
+ if (status) {
+ return srtp_err_status_cipher_fail;
+ }
+
+ /*
+ * Set the AAD for GCM mode
+ */
+ if (enc_start) {
+ /*
+ * If payload encryption is enabled, then the AAD consist of
+ * the RTCP header and the seq# at the end of the packet
+ */
+ status = srtp_cipher_set_aad(session_keys->rtcp_cipher, (uint8_t *)hdr,
+ octets_in_rtcp_header);
+ if (status) {
+ return (srtp_err_status_cipher_fail);
+ }
+ } else {
+ /*
+ * Since payload encryption is not enabled, we must authenticate
+ * the entire packet as described in RFC 7714 (Section 9.3. Data
+ * Types in Unencrypted SRTCP Compound Packets)
+ */
+ status = srtp_cipher_set_aad(
+ session_keys->rtcp_cipher, (uint8_t *)hdr,
+ (*pkt_octet_len - tag_len - sizeof(srtcp_trailer_t) - mki_size));
+ if (status) {
+ return (srtp_err_status_cipher_fail);
+ }
+ }
+
+ /*
+ * Process the sequence# as AAD
+ */
+ tseq = trailer;
+ status = srtp_cipher_set_aad(session_keys->rtcp_cipher, (uint8_t *)&tseq,
+ sizeof(srtcp_trailer_t));
+ if (status) {
+ return (srtp_err_status_cipher_fail);
+ }
+
+ /* if we're decrypting, exor keystream into the message */
+ if (enc_start) {
+ status = srtp_cipher_decrypt(session_keys->rtcp_cipher,
+ (uint8_t *)enc_start, &enc_octet_len);
+ if (status) {
+ return status;
+ }
+ } else {
+ /*
+ * Still need to run the cipher to check the tag
+ */
+ tmp_len = tag_len;
+ status = srtp_cipher_decrypt(session_keys->rtcp_cipher,
+ (uint8_t *)auth_tag, &tmp_len);
+ if (status) {
+ return status;
+ }
+ }
+
+ /* decrease the packet length by the length of the auth tag and seq_num*/
+ *pkt_octet_len -= (tag_len + sizeof(srtcp_trailer_t) + mki_size);
+
+ /*
+ * verify that stream is for received traffic - this check will
+ * detect SSRC collisions, since a stream that appears in both
+ * srtp_protect() and srtp_unprotect() will fail this test in one of
+ * those functions.
+ *
+ * we do this check *after* the authentication check, so that the
+ * latter check will catch any attempts to fool us into thinking
+ * that we've got a collision
+ */
+ if (stream->direction != dir_srtp_receiver) {
+ if (stream->direction == dir_unknown) {
+ stream->direction = dir_srtp_receiver;
+ } else {
+ srtp_handle_event(ctx, stream, event_ssrc_collision);
+ }
+ }
+
+ /*
+ * if the stream is a 'provisional' one, in which the template context
+ * is used, then we need to allocate a new stream at this point, since
+ * the authentication passed
+ */
+ if (stream == ctx->stream_template) {
+ srtp_stream_ctx_t *new_stream;
+
+ /*
+ * allocate and initialize a new stream
+ *
+ * note that we indicate failure if we can't allocate the new
+ * stream, and some implementations will want to not return
+ * failure here
+ */
+ status =
+ srtp_stream_clone(ctx->stream_template, hdr->ssrc, &new_stream);
+ if (status) {
+ return status;
+ }
+
+ /* add new stream to the head of the stream_list */
+ new_stream->next = ctx->stream_list;
+ ctx->stream_list = new_stream;
+
+ /* set stream (the pointer used in this function) */
+ stream = new_stream;
+ }
+
+ /* we've passed the authentication check, so add seq_num to the rdb */
+ srtp_rdb_add_index(&stream->rtcp_rdb, seq_num);
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_protect_rtcp(srtp_t ctx,
+ void *rtcp_hdr,
+ int *pkt_octet_len)
+{
+ return srtp_protect_rtcp_mki(ctx, rtcp_hdr, pkt_octet_len, 0, 0);
+}
+
+srtp_err_status_t srtp_protect_rtcp_mki(srtp_t ctx,
+ void *rtcp_hdr,
+ int *pkt_octet_len,
+ unsigned int use_mki,
+ unsigned int mki_index)
+{
+ srtcp_hdr_t *hdr = (srtcp_hdr_t *)rtcp_hdr;
+ uint32_t *enc_start; /* pointer to start of encrypted portion */
+ uint32_t *auth_start; /* pointer to start of auth. portion */
+ uint32_t *trailer_p; /* pointer to start of trailer */
+ uint32_t trailer; /* trailer value */
+ unsigned int enc_octet_len = 0; /* number of octets in encrypted portion */
+ uint8_t *auth_tag = NULL; /* location of auth_tag within packet */
+ srtp_err_status_t status;
+ int tag_len;
+ srtp_stream_ctx_t *stream;
+ uint32_t prefix_len;
+ uint32_t seq_num;
+ unsigned int mki_size = 0;
+ srtp_session_keys_t *session_keys = NULL;
+
+ /* we assume the hdr is 32-bit aligned to start */
+
+ /* check the packet length - it must at least contain a full header */
+ if (*pkt_octet_len < octets_in_rtcp_header)
+ return srtp_err_status_bad_param;
+
+ /*
+ * look up ssrc in srtp_stream list, and process the packet with
+ * the appropriate stream. if we haven't seen this stream before,
+ * there's only one key for this srtp_session, and the cipher
+ * supports key-sharing, then we assume that a new stream using
+ * that key has just started up
+ */
+ stream = srtp_get_stream(ctx, hdr->ssrc);
+ if (stream == NULL) {
+ if (ctx->stream_template != NULL) {
+ srtp_stream_ctx_t *new_stream;
+
+ /* allocate and initialize a new stream */
+ status =
+ srtp_stream_clone(ctx->stream_template, hdr->ssrc, &new_stream);
+ if (status)
+ return status;
+
+ /* add new stream to the head of the stream_list */
+ new_stream->next = ctx->stream_list;
+ ctx->stream_list = new_stream;
+
+ /* set stream (the pointer used in this function) */
+ stream = new_stream;
+ } else {
+ /* no template stream, so we return an error */
+ return srtp_err_status_no_ctx;
+ }
+ }
+
+ /*
+ * verify that stream is for sending traffic - this check will
+ * detect SSRC collisions, since a stream that appears in both
+ * srtp_protect() and srtp_unprotect() will fail this test in one of
+ * those functions.
+ */
+ if (stream->direction != dir_srtp_sender) {
+ if (stream->direction == dir_unknown) {
+ stream->direction = dir_srtp_sender;
+ } else {
+ srtp_handle_event(ctx, stream, event_ssrc_collision);
+ }
+ }
+
+ session_keys =
+ srtp_get_session_keys_with_mki_index(stream, use_mki, mki_index);
+
+ if (session_keys == NULL)
+ return srtp_err_status_bad_mki;
+
+ /*
+ * Check if this is an AEAD stream (GCM mode). If so, then dispatch
+ * the request to our AEAD handler.
+ */
+ if (session_keys->rtp_cipher->algorithm == SRTP_AES_GCM_128 ||
+ session_keys->rtp_cipher->algorithm == SRTP_AES_GCM_256) {
+ return srtp_protect_rtcp_aead(ctx, stream, rtcp_hdr,
+ (unsigned int *)pkt_octet_len,
+ session_keys, use_mki);
+ }
+
+ /* get tag length from stream context */
+ tag_len = srtp_auth_get_tag_length(session_keys->rtcp_auth);
+
+ /*
+ * set encryption start and encryption length - if we're not
+ * providing confidentiality, set enc_start to NULL
+ */
+ enc_start = (uint32_t *)hdr + uint32s_in_rtcp_header;
+ enc_octet_len = *pkt_octet_len - octets_in_rtcp_header;
+
+ /* all of the packet, except the header, gets encrypted */
+ /*
+ * NOTE: hdr->length is not usable - it refers to only the first RTCP report
+ * in the compound packet!
+ */
+ trailer_p = (uint32_t *)((char *)enc_start + enc_octet_len);
+
+ if (stream->rtcp_services & sec_serv_conf) {
+ trailer = htonl(SRTCP_E_BIT); /* set encrypt bit */
+ } else {
+ enc_start = NULL;
+ enc_octet_len = 0;
+ /* 0 is network-order independant */
+ trailer = 0x00000000; /* set encrypt bit */
+ }
+
+ mki_size = srtp_inject_mki((uint8_t *)hdr + *pkt_octet_len +
+ sizeof(srtcp_trailer_t),
+ session_keys, use_mki);
+
+ /*
+ * set the auth_start and auth_tag pointers to the proper locations
+ * (note that srtpc *always* provides authentication, unlike srtp)
+ */
+ /* Note: This would need to change for optional mikey data */
+ auth_start = (uint32_t *)hdr;
+ auth_tag =
+ (uint8_t *)hdr + *pkt_octet_len + sizeof(srtcp_trailer_t) + mki_size;
+
+ /* perform EKT processing if needed */
+ srtp_ekt_write_data(stream->ekt, auth_tag, tag_len, pkt_octet_len,
+ srtp_rdbx_get_packet_index(&stream->rtp_rdbx));
+
+ /*
+ * check sequence number for overruns, and copy it into the packet
+ * if its value isn't too big
+ */
+ status = srtp_rdb_increment(&stream->rtcp_rdb);
+ if (status)
+ return status;
+ seq_num = srtp_rdb_get_value(&stream->rtcp_rdb);
+ trailer |= htonl(seq_num);
+ debug_print(mod_srtp, "srtcp index: %x", seq_num);
+
+ memcpy(trailer_p, &trailer, sizeof(trailer));
+
+ /*
+ * if we're using rindael counter mode, set nonce and seq
+ */
+ if (session_keys->rtcp_cipher->type->id == SRTP_AES_ICM_128 ||
+ session_keys->rtcp_cipher->type->id == SRTP_AES_ICM_192 ||
+ session_keys->rtcp_cipher->type->id == SRTP_AES_ICM_256) {
+ v128_t iv;
+
+ iv.v32[0] = 0;
+ iv.v32[1] = hdr->ssrc; /* still in network order! */
+ iv.v32[2] = htonl(seq_num >> 16);
+ iv.v32[3] = htonl(seq_num << 16);
+ status = srtp_cipher_set_iv(session_keys->rtcp_cipher, (uint8_t *)&iv,
+ srtp_direction_encrypt);
+
+ } else {
+ v128_t iv;
+
+ /* otherwise, just set the index to seq_num */
+ iv.v32[0] = 0;
+ iv.v32[1] = 0;
+ iv.v32[2] = 0;
+ iv.v32[3] = htonl(seq_num);
+ status = srtp_cipher_set_iv(session_keys->rtcp_cipher, (uint8_t *)&iv,
+ srtp_direction_encrypt);
+ }
+ if (status)
+ return srtp_err_status_cipher_fail;
+
+ /*
+ * if we're authenticating using a universal hash, put the keystream
+ * prefix into the authentication tag
+ */
+
+ /* if auth_start is non-null, then put keystream into tag */
+ if (auth_start) {
+ /* put keystream prefix into auth_tag */
+ prefix_len = srtp_auth_get_prefix_length(session_keys->rtcp_auth);
+ status = srtp_cipher_output(session_keys->rtcp_cipher, auth_tag,
+ &prefix_len);
+
+ debug_print(mod_srtp, "keystream prefix: %s",
+ srtp_octet_string_hex_string(auth_tag, prefix_len));
+
+ if (status)
+ return srtp_err_status_cipher_fail;
+ }
+
+ /* if we're encrypting, exor keystream into the message */
+ if (enc_start) {
+ status = srtp_cipher_encrypt(session_keys->rtcp_cipher,
+ (uint8_t *)enc_start, &enc_octet_len);
+ if (status)
+ return srtp_err_status_cipher_fail;
+ }
+
+ /* initialize auth func context */
+ srtp_auth_start(session_keys->rtcp_auth);
+
+ /*
+ * run auth func over packet (including trailer), and write the
+ * result at auth_tag
+ */
+ status =
+ srtp_auth_compute(session_keys->rtcp_auth, (uint8_t *)auth_start,
+ (*pkt_octet_len) + sizeof(srtcp_trailer_t), auth_tag);
+ debug_print(mod_srtp, "srtcp auth tag: %s",
+ srtp_octet_string_hex_string(auth_tag, tag_len));
+ if (status)
+ return srtp_err_status_auth_fail;
+
+ /* increase the packet length by the length of the auth tag and seq_num*/
+ *pkt_octet_len += (tag_len + sizeof(srtcp_trailer_t));
+
+ /* increase the packet by the mki_size */
+ *pkt_octet_len += mki_size;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_unprotect_rtcp(srtp_t ctx,
+ void *srtcp_hdr,
+ int *pkt_octet_len)
+{
+ return srtp_unprotect_rtcp_mki(ctx, srtcp_hdr, pkt_octet_len, 0);
+}
+
+srtp_err_status_t srtp_unprotect_rtcp_mki(srtp_t ctx,
+ void *srtcp_hdr,
+ int *pkt_octet_len,
+ unsigned int use_mki)
+{
+ srtcp_hdr_t *hdr = (srtcp_hdr_t *)srtcp_hdr;
+ uint32_t *enc_start; /* pointer to start of encrypted portion */
+ uint32_t *auth_start; /* pointer to start of auth. portion */
+ uint32_t *trailer_p; /* pointer to start of trailer */
+ uint32_t trailer; /* trailer value */
+ unsigned int enc_octet_len = 0; /* number of octets in encrypted portion */
+ uint8_t *auth_tag = NULL; /* location of auth_tag within packet */
+ uint8_t tmp_tag[SRTP_MAX_TAG_LEN];
+ uint8_t tag_copy[SRTP_MAX_TAG_LEN];
+ srtp_err_status_t status;
+ unsigned int auth_len;
+ int tag_len;
+ srtp_stream_ctx_t *stream;
+ uint32_t prefix_len;
+ uint32_t seq_num;
+ int e_bit_in_packet; /* whether the E-bit was found in the packet */
+ int sec_serv_confidentiality; /* whether confidentiality was requested */
+ unsigned int mki_size = 0;
+ srtp_session_keys_t *session_keys = NULL;
+
+ /* we assume the hdr is 32-bit aligned to start */
+
+ if (*pkt_octet_len < 0)
+ return srtp_err_status_bad_param;
+
+ /*
+ * check that the length value is sane; we'll check again once we
+ * know the tag length, but we at least want to know that it is
+ * a positive value
+ */
+ if ((unsigned int)(*pkt_octet_len) <
+ octets_in_rtcp_header + sizeof(srtcp_trailer_t))
+ return srtp_err_status_bad_param;
+
+ /*
+ * look up ssrc in srtp_stream list, and process the packet with
+ * the appropriate stream. if we haven't seen this stream before,
+ * there's only one key for this srtp_session, and the cipher
+ * supports key-sharing, then we assume that a new stream using
+ * that key has just started up
+ */
+ stream = srtp_get_stream(ctx, hdr->ssrc);
+ if (stream == NULL) {
+ if (ctx->stream_template != NULL) {
+ stream = ctx->stream_template;
+
+ /*
+ * check to see if stream_template has an EKT data structure, in
+ * which case we initialize the template using the EKT policy
+ * referenced by that data (which consists of decrypting the
+ * master key from the EKT field)
+ *
+ * this function initializes a *provisional* stream, and this
+ * stream should not be accepted until and unless the packet
+ * passes its authentication check
+ */
+ if (stream->ekt != NULL) {
+ status = srtp_stream_init_from_ekt(stream, srtcp_hdr,
+ *pkt_octet_len);
+ if (status)
+ return status;
+ }
+
+ debug_print(mod_srtp,
+ "srtcp using provisional stream (SSRC: 0x%08x)",
+ ntohl(hdr->ssrc));
+ } else {
+ /* no template stream, so we return an error */
+ return srtp_err_status_no_ctx;
+ }
+ }
+
+ /*
+ * Determine if MKI is being used and what session keys should be used
+ */
+ if (use_mki) {
+ session_keys = srtp_get_session_keys(
+ stream, (uint8_t *)hdr, (const unsigned int *)pkt_octet_len,
+ &mki_size);
+
+ if (session_keys == NULL)
+ return srtp_err_status_bad_mki;
+ } else {
+ session_keys = &stream->session_keys[0];
+ }
+
+ /* get tag length from stream context */
+ tag_len = srtp_auth_get_tag_length(session_keys->rtcp_auth);
+
+ /* check the packet length - it must contain at least a full RTCP
+ header, an auth tag (if applicable), and the SRTCP encrypted flag
+ and 31-bit index value */
+ if (*pkt_octet_len < (int)(octets_in_rtcp_header + tag_len + mki_size +
+ sizeof(srtcp_trailer_t))) {
+ return srtp_err_status_bad_param;
+ }
+
+ /*
+ * Check if this is an AEAD stream (GCM mode). If so, then dispatch
+ * the request to our AEAD handler.
+ */
+ if (session_keys->rtp_cipher->algorithm == SRTP_AES_GCM_128 ||
+ session_keys->rtp_cipher->algorithm == SRTP_AES_GCM_256) {
+ return srtp_unprotect_rtcp_aead(ctx, stream, srtcp_hdr,
+ (unsigned int *)pkt_octet_len,
+ session_keys, mki_size);
+ }
+
+ sec_serv_confidentiality = stream->rtcp_services == sec_serv_conf ||
+ stream->rtcp_services == sec_serv_conf_and_auth;
+
+ /*
+ * set encryption start, encryption length, and trailer
+ */
+ enc_octet_len = *pkt_octet_len - (octets_in_rtcp_header + tag_len +
+ mki_size + sizeof(srtcp_trailer_t));
+ /*
+ *index & E (encryption) bit follow normal data. hdr->len is the number of
+ * words (32-bit) in the normal packet minus 1
+ */
+ /* This should point trailer to the word past the end of the normal data. */
+ /* This would need to be modified for optional mikey data */
+ trailer_p = (uint32_t *)((char *)hdr + *pkt_octet_len -
+ (tag_len + mki_size + sizeof(srtcp_trailer_t)));
+ memcpy(&trailer, trailer_p, sizeof(trailer));
+
+ e_bit_in_packet =
+ (*((unsigned char *)trailer_p) & SRTCP_E_BYTE_BIT) == SRTCP_E_BYTE_BIT;
+ if (e_bit_in_packet != sec_serv_confidentiality) {
+ return srtp_err_status_cant_check;
+ }
+ if (sec_serv_confidentiality) {
+ enc_start = (uint32_t *)hdr + uint32s_in_rtcp_header;
+ } else {
+ enc_octet_len = 0;
+ enc_start = NULL; /* this indicates that there's no encryption */
+ }
+
+ /*
+ * set the auth_start and auth_tag pointers to the proper locations
+ * (note that srtcp *always* uses authentication, unlike srtp)
+ */
+ auth_start = (uint32_t *)hdr;
+
+ /*
+ * The location of the auth tag in the packet needs to know MKI
+ * could be present. The data needed to calculate the Auth tag
+ * must not include the MKI
+ */
+ auth_len = *pkt_octet_len - tag_len - mki_size;
+ auth_tag = (uint8_t *)hdr + auth_len + mki_size;
+
+ /*
+ * if EKT is in use, then we make a copy of the tag from the packet,
+ * and then zeroize the location of the base tag
+ *
+ * we first re-position the auth_tag pointer so that it points to
+ * the base tag
+ */
+ if (stream->ekt) {
+ auth_tag -= srtp_ekt_octets_after_base_tag(stream->ekt);
+ memcpy(tag_copy, auth_tag, tag_len);
+ octet_string_set_to_zero(auth_tag, tag_len);
+ auth_tag = tag_copy;
+ auth_len += tag_len;
+ }
+
+ /*
+ * check the sequence number for replays
+ */
+ /* this is easier than dealing with bitfield access */
+ seq_num = ntohl(trailer) & SRTCP_INDEX_MASK;
+ debug_print(mod_srtp, "srtcp index: %x", seq_num);
+ status = srtp_rdb_check(&stream->rtcp_rdb, seq_num);
+ if (status)
+ return status;
+
+ /*
+ * if we're using aes counter mode, set nonce and seq
+ */
+ if (session_keys->rtcp_cipher->type->id == SRTP_AES_ICM_128 ||
+ session_keys->rtcp_cipher->type->id == SRTP_AES_ICM_192 ||
+ session_keys->rtcp_cipher->type->id == SRTP_AES_ICM_256) {
+ v128_t iv;
+
+ iv.v32[0] = 0;
+ iv.v32[1] = hdr->ssrc; /* still in network order! */
+ iv.v32[2] = htonl(seq_num >> 16);
+ iv.v32[3] = htonl(seq_num << 16);
+ status = srtp_cipher_set_iv(session_keys->rtcp_cipher, (uint8_t *)&iv,
+ srtp_direction_decrypt);
+
+ } else {
+ v128_t iv;
+
+ /* otherwise, just set the index to seq_num */
+ iv.v32[0] = 0;
+ iv.v32[1] = 0;
+ iv.v32[2] = 0;
+ iv.v32[3] = htonl(seq_num);
+ status = srtp_cipher_set_iv(session_keys->rtcp_cipher, (uint8_t *)&iv,
+ srtp_direction_decrypt);
+ }
+ if (status)
+ return srtp_err_status_cipher_fail;
+
+ /* initialize auth func context */
+ srtp_auth_start(session_keys->rtcp_auth);
+
+ /* run auth func over packet, put result into tmp_tag */
+ status = srtp_auth_compute(session_keys->rtcp_auth, (uint8_t *)auth_start,
+ auth_len, tmp_tag);
+ debug_print(mod_srtp, "srtcp computed tag: %s",
+ srtp_octet_string_hex_string(tmp_tag, tag_len));
+ if (status)
+ return srtp_err_status_auth_fail;
+
+ /* compare the tag just computed with the one in the packet */
+ debug_print(mod_srtp, "srtcp tag from packet: %s",
+ srtp_octet_string_hex_string(auth_tag, tag_len));
+ if (octet_string_is_eq(tmp_tag, auth_tag, tag_len))
+ return srtp_err_status_auth_fail;
+
+ /*
+ * if we're authenticating using a universal hash, put the keystream
+ * prefix into the authentication tag
+ */
+ prefix_len = srtp_auth_get_prefix_length(session_keys->rtcp_auth);
+ if (prefix_len) {
+ status = srtp_cipher_output(session_keys->rtcp_cipher, auth_tag,
+ &prefix_len);
+ debug_print(mod_srtp, "keystream prefix: %s",
+ srtp_octet_string_hex_string(auth_tag, prefix_len));
+ if (status)
+ return srtp_err_status_cipher_fail;
+ }
+
+ /* if we're decrypting, exor keystream into the message */
+ if (enc_start) {
+ status = srtp_cipher_decrypt(session_keys->rtcp_cipher,
+ (uint8_t *)enc_start, &enc_octet_len);
+ if (status)
+ return srtp_err_status_cipher_fail;
+ }
+
+ /* decrease the packet length by the length of the auth tag and seq_num */
+ *pkt_octet_len -= (tag_len + sizeof(srtcp_trailer_t));
+
+ /* decrease the packet length by the length of the mki_size */
+ *pkt_octet_len -= mki_size;
+
+ /*
+ * if EKT is in effect, subtract the EKT data out of the packet
+ * length
+ */
+ *pkt_octet_len -= srtp_ekt_octets_after_base_tag(stream->ekt);
+
+ /*
+ * verify that stream is for received traffic - this check will
+ * detect SSRC collisions, since a stream that appears in both
+ * srtp_protect() and srtp_unprotect() will fail this test in one of
+ * those functions.
+ *
+ * we do this check *after* the authentication check, so that the
+ * latter check will catch any attempts to fool us into thinking
+ * that we've got a collision
+ */
+ if (stream->direction != dir_srtp_receiver) {
+ if (stream->direction == dir_unknown) {
+ stream->direction = dir_srtp_receiver;
+ } else {
+ srtp_handle_event(ctx, stream, event_ssrc_collision);
+ }
+ }
+
+ /*
+ * if the stream is a 'provisional' one, in which the template context
+ * is used, then we need to allocate a new stream at this point, since
+ * the authentication passed
+ */
+ if (stream == ctx->stream_template) {
+ srtp_stream_ctx_t *new_stream;
+
+ /*
+ * allocate and initialize a new stream
+ *
+ * note that we indicate failure if we can't allocate the new
+ * stream, and some implementations will want to not return
+ * failure here
+ */
+ status =
+ srtp_stream_clone(ctx->stream_template, hdr->ssrc, &new_stream);
+ if (status)
+ return status;
+
+ /* add new stream to the head of the stream_list */
+ new_stream->next = ctx->stream_list;
+ ctx->stream_list = new_stream;
+
+ /* set stream (the pointer used in this function) */
+ stream = new_stream;
+ }
+
+ /* we've passed the authentication check, so add seq_num to the rdb */
+ srtp_rdb_add_index(&stream->rtcp_rdb, seq_num);
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * user data within srtp_t context
+ */
+
+void srtp_set_user_data(srtp_t ctx, void *data)
+{
+ ctx->user_data = data;
+}
+
+void *srtp_get_user_data(srtp_t ctx)
+{
+ return ctx->user_data;
+}
+
+/*
+ * dtls keying for srtp
+ */
+
+srtp_err_status_t srtp_crypto_policy_set_from_profile_for_rtp(
+ srtp_crypto_policy_t *policy,
+ srtp_profile_t profile)
+{
+ /* set SRTP policy from the SRTP profile in the key set */
+ switch (profile) {
+ case srtp_profile_aes128_cm_sha1_80:
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(policy);
+ break;
+ case srtp_profile_aes128_cm_sha1_32:
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(policy);
+ break;
+ case srtp_profile_null_sha1_80:
+ srtp_crypto_policy_set_null_cipher_hmac_sha1_80(policy);
+ break;
+#ifdef GCM
+ case srtp_profile_aead_aes_128_gcm:
+ srtp_crypto_policy_set_aes_gcm_128_16_auth(policy);
+ break;
+ case srtp_profile_aead_aes_256_gcm:
+ srtp_crypto_policy_set_aes_gcm_256_16_auth(policy);
+ break;
+#endif
+ /* the following profiles are not (yet) supported */
+ case srtp_profile_null_sha1_32:
+ default:
+ return srtp_err_status_bad_param;
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_crypto_policy_set_from_profile_for_rtcp(
+ srtp_crypto_policy_t *policy,
+ srtp_profile_t profile)
+{
+ /* set SRTP policy from the SRTP profile in the key set */
+ switch (profile) {
+ case srtp_profile_aes128_cm_sha1_80:
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(policy);
+ break;
+ case srtp_profile_aes128_cm_sha1_32:
+ /* We do not honor the 32-bit auth tag request since
+ * this is not compliant with RFC 3711 */
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(policy);
+ break;
+ case srtp_profile_null_sha1_80:
+ srtp_crypto_policy_set_null_cipher_hmac_sha1_80(policy);
+ break;
+#ifdef GCM
+ case srtp_profile_aead_aes_128_gcm:
+ srtp_crypto_policy_set_aes_gcm_128_16_auth(policy);
+ break;
+ case srtp_profile_aead_aes_256_gcm:
+ srtp_crypto_policy_set_aes_gcm_256_16_auth(policy);
+ break;
+#endif
+ /* the following profiles are not (yet) supported */
+ case srtp_profile_null_sha1_32:
+ default:
+ return srtp_err_status_bad_param;
+ }
+
+ return srtp_err_status_ok;
+}
+
+void srtp_append_salt_to_key(uint8_t *key,
+ unsigned int bytes_in_key,
+ uint8_t *salt,
+ unsigned int bytes_in_salt)
+{
+ memcpy(key + bytes_in_key, salt, bytes_in_salt);
+}
+
+unsigned int srtp_profile_get_master_key_length(srtp_profile_t profile)
+{
+ switch (profile) {
+ case srtp_profile_aes128_cm_sha1_80:
+ return SRTP_AES_128_KEY_LEN;
+ break;
+ case srtp_profile_aes128_cm_sha1_32:
+ return SRTP_AES_128_KEY_LEN;
+ break;
+ case srtp_profile_null_sha1_80:
+ return SRTP_AES_128_KEY_LEN;
+ break;
+ case srtp_profile_aead_aes_128_gcm:
+ return SRTP_AES_128_KEY_LEN;
+ break;
+ case srtp_profile_aead_aes_256_gcm:
+ return SRTP_AES_256_KEY_LEN;
+ break;
+ /* the following profiles are not (yet) supported */
+ case srtp_profile_null_sha1_32:
+ default:
+ return 0; /* indicate error by returning a zero */
+ }
+}
+
+unsigned int srtp_profile_get_master_salt_length(srtp_profile_t profile)
+{
+ switch (profile) {
+ case srtp_profile_aes128_cm_sha1_80:
+ return SRTP_SALT_LEN;
+ break;
+ case srtp_profile_aes128_cm_sha1_32:
+ return SRTP_SALT_LEN;
+ break;
+ case srtp_profile_null_sha1_80:
+ return SRTP_SALT_LEN;
+ break;
+ case srtp_profile_aead_aes_128_gcm:
+ return SRTP_AEAD_SALT_LEN;
+ break;
+ case srtp_profile_aead_aes_256_gcm:
+ return SRTP_AEAD_SALT_LEN;
+ break;
+ /* the following profiles are not (yet) supported */
+ case srtp_profile_null_sha1_32:
+ default:
+ return 0; /* indicate error by returning a zero */
+ }
+}
+
+srtp_err_status_t stream_get_protect_trailer_length(srtp_stream_ctx_t *stream,
+ uint32_t is_rtp,
+ uint32_t use_mki,
+ uint32_t mki_index,
+ uint32_t *length)
+{
+ *length = 0;
+
+ srtp_session_keys_t *session_key;
+
+ if (use_mki) {
+ if (mki_index >= stream->num_master_keys) {
+ return srtp_err_status_bad_mki;
+ }
+ session_key = &stream->session_keys[mki_index];
+
+ *length += session_key->mki_size;
+
+ } else {
+ session_key = &stream->session_keys[0];
+ }
+ if (is_rtp) {
+ *length += srtp_auth_get_tag_length(session_key->rtp_auth);
+ } else {
+ *length += srtp_auth_get_tag_length(session_key->rtcp_auth);
+ *length += sizeof(srtcp_trailer_t);
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t get_protect_trailer_length(srtp_t session,
+ uint32_t is_rtp,
+ uint32_t use_mki,
+ uint32_t mki_index,
+ uint32_t *length)
+{
+ srtp_stream_ctx_t *stream;
+
+ if (session == NULL) {
+ return srtp_err_status_bad_param;
+ }
+
+ if (session->stream_template == NULL && session->stream_list == NULL) {
+ return srtp_err_status_bad_param;
+ }
+
+ *length = 0;
+
+ stream = session->stream_template;
+
+ if (stream != NULL) {
+ stream_get_protect_trailer_length(stream, is_rtp, use_mki, mki_index,
+ length);
+ }
+
+ stream = session->stream_list;
+
+ while (stream != NULL) {
+ uint32_t temp_length;
+ if (stream_get_protect_trailer_length(stream, is_rtp, use_mki,
+ mki_index, &temp_length) ==
+ srtp_err_status_ok) {
+ if (temp_length > *length) {
+ *length = temp_length;
+ }
+ }
+ stream = stream->next;
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_get_protect_trailer_length(srtp_t session,
+ uint32_t use_mki,
+ uint32_t mki_index,
+ uint32_t *length)
+{
+ return get_protect_trailer_length(session, 1, use_mki, mki_index, length);
+}
+
+srtp_err_status_t srtp_get_protect_rtcp_trailer_length(srtp_t session,
+ uint32_t use_mki,
+ uint32_t mki_index,
+ uint32_t *length)
+{
+ return get_protect_trailer_length(session, 0, use_mki, mki_index, length);
+}
+
+/*
+ * SRTP debug interface
+ */
+srtp_err_status_t srtp_set_debug_module(const char *mod_name, int v)
+{
+ return srtp_crypto_kernel_set_debug_module(mod_name, v);
+}
+
+srtp_err_status_t srtp_list_debug_modules(void)
+{
+ return srtp_crypto_kernel_list_debug_modules();
+}
+
+/*
+ * srtp_log_handler is a global variable holding a pointer to the
+ * log handler function; this function is called for any log
+ * output.
+ */
+
+static srtp_log_handler_func_t *srtp_log_handler = NULL;
+static void *srtp_log_handler_data = NULL;
+
+void srtp_err_handler(srtp_err_reporting_level_t level, const char *msg)
+{
+ if (srtp_log_handler) {
+ srtp_log_level_t log_level = srtp_log_level_error;
+ switch (level) {
+ case srtp_err_level_error:
+ log_level = srtp_log_level_error;
+ break;
+ case srtp_err_level_warning:
+ log_level = srtp_log_level_warning;
+ break;
+ case srtp_err_level_info:
+ log_level = srtp_log_level_info;
+ break;
+ case srtp_err_level_debug:
+ log_level = srtp_log_level_debug;
+ break;
+ }
+
+ srtp_log_handler(log_level, msg, srtp_log_handler_data);
+ }
+}
+
+srtp_err_status_t srtp_install_log_handler(srtp_log_handler_func_t func,
+ void *data)
+{
+ /*
+ * note that we accept NULL arguments intentionally - calling this
+ * function with a NULL arguments removes a log handler that's
+ * been previously installed
+ */
+
+ if (srtp_log_handler) {
+ srtp_install_err_report_handler(NULL);
+ }
+ srtp_log_handler = func;
+ srtp_log_handler_data = data;
+ if (srtp_log_handler) {
+ srtp_install_err_report_handler(srtp_err_handler);
+ }
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_set_stream_roc(srtp_t session,
+ uint32_t ssrc,
+ uint32_t roc)
+{
+ srtp_stream_t stream;
+
+ stream = srtp_get_stream(session, htonl(ssrc));
+ if (stream == NULL)
+ return srtp_err_status_bad_param;
+
+ stream->pending_roc = roc;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_get_stream_roc(srtp_t session,
+ uint32_t ssrc,
+ uint32_t *roc)
+{
+ srtp_stream_t stream;
+
+ stream = srtp_get_stream(session, htonl(ssrc));
+ if (stream == NULL)
+ return srtp_err_status_bad_param;
+
+ *roc = srtp_rdbx_get_roc(&stream->rtp_rdbx);
+
+ return srtp_err_status_ok;
+}
diff --git a/netwerk/srtp/src/test/cutest.h b/netwerk/srtp/src/test/cutest.h
new file mode 100644
index 0000000000..f46626d395
--- /dev/null
+++ b/netwerk/srtp/src/test/cutest.h
@@ -0,0 +1,713 @@
+/*
+ * CUTest -- C/C++ Unit Test facility
+ * <http://github.com/mity/cutest>
+ *
+ * Copyright (c) 2013-2017 Martin Mitas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef CUTEST_H__
+#define CUTEST_H__
+
+/************************
+ *** Public interface ***
+ ************************/
+
+/* By default, <cutest.h> provides the main program entry point (function
+ * main()). However, if the test suite is composed of multiple source files
+ * which include <cutest.h>, then this causes a problem of multiple main()
+ * definitions. To avoid this problem, #define macro TEST_NO_MAIN in all
+ * compilation units but one.
+ */
+
+/* Macro to specify list of unit tests in the suite.
+ * The unit test implementation MUST provide list of unit tests it implements
+ * with this macro:
+ *
+ * TEST_LIST = {
+ * { "test1_name", test1_func_ptr },
+ * { "test2_name", test2_func_ptr },
+ * ...
+ * { 0 }
+ * };
+ *
+ * The list specifies names of each test (must be unique) and pointer to
+ * a function implementing it. The function does not take any arguments
+ * and has no return values, i.e. every test function has tp be compatible
+ * with this prototype:
+ *
+ * void test_func(void);
+ */
+#define TEST_LIST const struct test__ test_list__[]
+
+/* Macros for testing whether an unit test succeeds or fails. These macros
+ * can be used arbitrarily in functions implementing the unit tests.
+ *
+ * If any condition fails throughout execution of a test, the test fails.
+ *
+ * TEST_CHECK takes only one argument (the condition), TEST_CHECK_ allows
+ * also to specify an error message to print out if the condition fails.
+ * (It expects printf-like format string and its parameters). The macros
+ * return non-zero (condition passes) or 0 (condition fails).
+ *
+ * That can be useful when more conditions should be checked only if some
+ * preceding condition passes, as illustrated in this code snippet:
+ *
+ * SomeStruct* ptr = allocate_some_struct();
+ * if(TEST_CHECK(ptr != NULL)) {
+ * TEST_CHECK(ptr->member1 < 100);
+ * TEST_CHECK(ptr->member2 > 200);
+ * }
+ */
+#define TEST_CHECK_(cond, ...) \
+ test_check__((cond), __FILE__, __LINE__, __VA_ARGS__)
+#define TEST_CHECK(cond) test_check__((cond), __FILE__, __LINE__, "%s", #cond)
+
+/**********************
+ *** Implementation ***
+ **********************/
+
+/* The unit test files should not rely on anything below. */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__)
+#define CUTEST_UNIX__ 1
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#endif
+
+#if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__)
+#define CUTEST_WIN__ 1
+#include <windows.h>
+#include <io.h>
+#endif
+
+#ifdef __cplusplus
+#include <exception>
+#endif
+
+/* Note our global private identifiers end with '__' to mitigate risk of clash
+ * with the unit tests implementation. */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct test__ {
+ const char *name;
+ void (*func)(void);
+};
+
+extern const struct test__ test_list__[];
+
+int test_check__(int cond, const char *file, int line, const char *fmt, ...);
+
+#ifndef TEST_NO_MAIN
+
+static char *test_argv0__ = NULL;
+static int test_count__ = 0;
+static int test_no_exec__ = 0;
+static int test_no_summary__ = 0;
+static int test_skip_mode__ = 0;
+
+static int test_stat_failed_units__ = 0;
+static int test_stat_run_units__ = 0;
+
+static const struct test__ *test_current_unit__ = NULL;
+static int test_current_already_logged__ = 0;
+static int test_verbose_level__ = 2;
+static int test_current_failures__ = 0;
+static int test_colorize__ = 0;
+
+#define CUTEST_COLOR_DEFAULT__ 0
+#define CUTEST_COLOR_GREEN__ 1
+#define CUTEST_COLOR_RED__ 2
+#define CUTEST_COLOR_DEFAULT_INTENSIVE__ 3
+#define CUTEST_COLOR_GREEN_INTENSIVE__ 4
+#define CUTEST_COLOR_RED_INTENSIVE__ 5
+
+static size_t test_print_in_color__(int color, const char *fmt, ...)
+{
+ va_list args;
+ char buffer[256];
+ size_t n;
+
+ va_start(args, fmt);
+ vsnprintf(buffer, sizeof(buffer), fmt, args);
+ va_end(args);
+ buffer[sizeof(buffer) - 1] = '\0';
+
+ if (!test_colorize__) {
+ return printf("%s", buffer);
+ }
+
+#if defined CUTEST_UNIX__
+ {
+ const char *col_str;
+ switch (color) {
+ case CUTEST_COLOR_GREEN__:
+ col_str = "\033[0;32m";
+ break;
+ case CUTEST_COLOR_RED__:
+ col_str = "\033[0;31m";
+ break;
+ case CUTEST_COLOR_GREEN_INTENSIVE__:
+ col_str = "\033[1;32m";
+ break;
+ case CUTEST_COLOR_RED_INTENSIVE__:
+ col_str = "\033[1;30m";
+ break;
+ case CUTEST_COLOR_DEFAULT_INTENSIVE__:
+ col_str = "\033[1m";
+ break;
+ default:
+ col_str = "\033[0m";
+ break;
+ }
+ printf("%s", col_str);
+ n = printf("%s", buffer);
+ printf("\033[0m");
+ return n;
+ }
+#elif defined CUTEST_WIN__
+ {
+ HANDLE h;
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ WORD attr;
+
+ h = GetStdHandle(STD_OUTPUT_HANDLE);
+ GetConsoleScreenBufferInfo(h, &info);
+
+ switch (color) {
+ case CUTEST_COLOR_GREEN__:
+ attr = FOREGROUND_GREEN;
+ break;
+ case CUTEST_COLOR_RED__:
+ attr = FOREGROUND_RED;
+ break;
+ case CUTEST_COLOR_GREEN_INTENSIVE__:
+ attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY;
+ break;
+ case CUTEST_COLOR_RED_INTENSIVE__:
+ attr = FOREGROUND_RED | FOREGROUND_INTENSITY;
+ break;
+ case CUTEST_COLOR_DEFAULT_INTENSIVE__:
+ attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED |
+ FOREGROUND_INTENSITY;
+ break;
+ default:
+ attr = 0;
+ break;
+ }
+ if (attr != 0)
+ SetConsoleTextAttribute(h, attr);
+ n = printf("%s", buffer);
+ SetConsoleTextAttribute(h, info.wAttributes);
+ return n;
+ }
+#else
+ n = printf("%s", buffer);
+ return n;
+#endif
+}
+
+int test_check__(int cond, const char *file, int line, const char *fmt, ...)
+{
+ const char *result_str;
+ int result_color;
+ int verbose_level;
+
+ if (cond) {
+ result_str = "ok";
+ result_color = CUTEST_COLOR_GREEN__;
+ verbose_level = 3;
+ } else {
+ if (!test_current_already_logged__ && test_current_unit__ != NULL) {
+ printf("[ ");
+ test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__, "FAILED");
+ printf(" ]\n");
+ }
+ result_str = "failed";
+ result_color = CUTEST_COLOR_RED__;
+ verbose_level = 2;
+ test_current_failures__++;
+ test_current_already_logged__++;
+ }
+
+ if (test_verbose_level__ >= verbose_level) {
+ size_t n = 0;
+ va_list args;
+
+ printf(" ");
+
+ if (file != NULL)
+ n += printf("%s:%d: Check ", file, line);
+
+ va_start(args, fmt);
+ n += vprintf(fmt, args);
+ va_end(args);
+
+ printf("... ");
+ test_print_in_color__(result_color, result_str);
+ printf("\n");
+ test_current_already_logged__++;
+ }
+
+ return (cond != 0);
+}
+
+static void test_list_names__(void)
+{
+ const struct test__ *test;
+
+ printf("Unit tests:\n");
+ for (test = &test_list__[0]; test->func != NULL; test++)
+ printf(" %s\n", test->name);
+}
+
+static const struct test__ *test_by_name__(const char *name)
+{
+ const struct test__ *test;
+
+ for (test = &test_list__[0]; test->func != NULL; test++) {
+ if (strcmp(test->name, name) == 0)
+ return test;
+ }
+
+ return NULL;
+}
+
+/* Call directly the given test unit function. */
+static int test_do_run__(const struct test__ *test)
+{
+ test_current_unit__ = test;
+ test_current_failures__ = 0;
+ test_current_already_logged__ = 0;
+
+ if (test_verbose_level__ >= 3) {
+ test_print_in_color__(CUTEST_COLOR_DEFAULT_INTENSIVE__, "Test %s:\n",
+ test->name);
+ test_current_already_logged__++;
+ } else if (test_verbose_level__ >= 1) {
+ size_t n;
+ char spaces[32];
+
+ n = test_print_in_color__(CUTEST_COLOR_DEFAULT_INTENSIVE__,
+ "Test %s... ", test->name);
+ memset(spaces, ' ', sizeof(spaces));
+ if (n < sizeof(spaces))
+ printf("%.*s", (int)(sizeof(spaces) - n), spaces);
+ } else {
+ test_current_already_logged__ = 1;
+ }
+
+#ifdef __cplusplus
+ try {
+#endif
+
+ /* This is good to do for case the test unit e.g. crashes. */
+ fflush(stdout);
+ fflush(stderr);
+
+ test->func();
+
+#ifdef __cplusplus
+ } catch (std::exception &e) {
+ const char *what = e.what();
+ if (what != NULL)
+ test_check__(0, NULL, 0, "Threw std::exception: %s", what);
+ else
+ test_check__(0, NULL, 0, "Threw std::exception");
+ } catch (...) {
+ test_check__(0, NULL, 0, "Threw an exception");
+ }
+#endif
+
+ if (test_verbose_level__ >= 3) {
+ switch (test_current_failures__) {
+ case 0:
+ test_print_in_color__(CUTEST_COLOR_GREEN_INTENSIVE__,
+ " All conditions have passed.\n\n");
+ break;
+ case 1:
+ test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__,
+ " One condition has FAILED.\n\n");
+ break;
+ default:
+ test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__,
+ " %d conditions have FAILED.\n\n",
+ test_current_failures__);
+ break;
+ }
+ } else if (test_verbose_level__ >= 1 && test_current_failures__ == 0) {
+ printf("[ ");
+ test_print_in_color__(CUTEST_COLOR_GREEN_INTENSIVE__, "OK");
+ printf(" ]\n");
+ }
+
+ test_current_unit__ = NULL;
+ return (test_current_failures__ == 0) ? 0 : -1;
+}
+
+#if defined(CUTEST_UNIX__) || defined(CUTEST_WIN__)
+/* Called if anything goes bad in cutest, or if the unit test ends in other
+ * way then by normal returning from its function (e.g. exception or some
+ * abnormal child process termination). */
+static void test_error__(const char *fmt, ...)
+{
+ va_list args;
+
+ if (test_verbose_level__ == 0)
+ return;
+
+ if (test_verbose_level__ <= 2 && !test_current_already_logged__ &&
+ test_current_unit__ != NULL) {
+ printf("[ ");
+ test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__, "FAILED");
+ printf(" ]\n");
+ }
+
+ if (test_verbose_level__ >= 2) {
+ test_print_in_color__(CUTEST_COLOR_RED_INTENSIVE__, " Error: ");
+ va_start(args, fmt);
+ vprintf(fmt, args);
+ va_end(args);
+ printf("\n");
+ }
+}
+#endif
+
+/* Trigger the unit test. If possible (and not suppressed) it starts a child
+ * process who calls test_do_run__(), otherwise it calls test_do_run__()
+ * directly. */
+static void test_run__(const struct test__ *test)
+{
+ int failed = 1;
+
+ test_current_unit__ = test;
+ test_current_already_logged__ = 0;
+
+ if (!test_no_exec__) {
+#if defined(CUTEST_UNIX__)
+
+ pid_t pid;
+ int exit_code;
+
+ pid = fork();
+ if (pid == (pid_t)-1) {
+ test_error__("Cannot fork. %s [%d]", strerror(errno), errno);
+ failed = 1;
+ } else if (pid == 0) {
+ /* Child: Do the test. */
+ failed = (test_do_run__(test) != 0);
+ exit(failed ? 1 : 0);
+ } else {
+ /* Parent: Wait until child terminates and analyze its exit code. */
+ waitpid(pid, &exit_code, 0);
+ if (WIFEXITED(exit_code)) {
+ switch (WEXITSTATUS(exit_code)) {
+ case 0:
+ failed = 0;
+ break; /* test has passed. */
+ case 1: /* noop */
+ break; /* "normal" failure. */
+ default:
+ test_error__("Unexpected exit code [%d]",
+ WEXITSTATUS(exit_code));
+ }
+ } else if (WIFSIGNALED(exit_code)) {
+ char tmp[32];
+ const char *signame;
+ switch (WTERMSIG(exit_code)) {
+ case SIGINT:
+ signame = "SIGINT";
+ break;
+ case SIGHUP:
+ signame = "SIGHUP";
+ break;
+ case SIGQUIT:
+ signame = "SIGQUIT";
+ break;
+ case SIGABRT:
+ signame = "SIGABRT";
+ break;
+ case SIGKILL:
+ signame = "SIGKILL";
+ break;
+ case SIGSEGV:
+ signame = "SIGSEGV";
+ break;
+ case SIGILL:
+ signame = "SIGILL";
+ break;
+ case SIGTERM:
+ signame = "SIGTERM";
+ break;
+ default:
+ sprintf(tmp, "signal %d", WTERMSIG(exit_code));
+ signame = tmp;
+ break;
+ }
+ test_error__("Test interrupted by %s", signame);
+ } else {
+ test_error__("Test ended in an unexpected way [%d]", exit_code);
+ }
+ }
+
+#elif defined(CUTEST_WIN__)
+
+ char buffer[512] = { 0 };
+ STARTUPINFOA startupInfo = { 0 };
+ PROCESS_INFORMATION processInfo;
+ DWORD exitCode;
+
+ /* Windows has no fork(). So we propagate all info into the child
+ * through a command line arguments. */
+ _snprintf(buffer, sizeof(buffer) - 1,
+ "%s --no-exec --no-summary --verbose=%d --color=%s -- \"%s\"",
+ test_argv0__, test_verbose_level__,
+ test_colorize__ ? "always" : "never", test->name);
+ startupInfo.cb = sizeof(STARTUPINFO);
+ if (CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL,
+ &startupInfo, &processInfo)) {
+ WaitForSingleObject(processInfo.hProcess, INFINITE);
+ GetExitCodeProcess(processInfo.hProcess, &exitCode);
+ CloseHandle(processInfo.hThread);
+ CloseHandle(processInfo.hProcess);
+ failed = (exitCode != 0);
+ } else {
+ test_error__("Cannot create unit test subprocess [%ld].",
+ GetLastError());
+ failed = 1;
+ }
+
+#else
+
+ /* A platform where we don't know how to run child process. */
+ failed = (test_do_run__(test) != 0);
+
+#endif
+
+ } else {
+ /* Child processes suppressed through --no-exec. */
+ failed = (test_do_run__(test) != 0);
+ }
+
+ test_current_unit__ = NULL;
+
+ test_stat_run_units__++;
+ if (failed)
+ test_stat_failed_units__++;
+}
+
+#if defined(CUTEST_WIN__)
+/* Callback for SEH events. */
+static LONG CALLBACK test_exception_filter__(EXCEPTION_POINTERS *ptrs)
+{
+ test_error__("Unhandled SEH exception %08lx at %p.",
+ ptrs->ExceptionRecord->ExceptionCode,
+ ptrs->ExceptionRecord->ExceptionAddress);
+ fflush(stdout);
+ fflush(stderr);
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+#endif
+
+static void test_help__(void)
+{
+ printf("Usage: %s [options] [test...]\n", test_argv0__);
+ printf("Run the specified unit tests; or if the option '--skip' is used, "
+ "run all\n");
+ printf("tests in the suite but those listed. By default, if no tests are "
+ "specified\n");
+ printf("on the command line, all unit tests in the suite are run.\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(
+ " -s, --skip Execute all unit tests but the listed ones\n");
+ printf(" --no-exec Do not execute unit tests as child "
+ "processes\n");
+ printf(
+ " --no-summary Suppress printing of test results summary\n");
+ printf(" -l, --list List unit tests in the suite and exit\n");
+ printf(" -v, --verbose Enable more verbose output\n");
+ printf(" --verbose=LEVEL Set verbose level to LEVEL:\n");
+ printf(" 0 ... Be silent\n");
+ printf(" 1 ... Output one line per test (and "
+ "summary)\n");
+ printf(" 2 ... As 1 and failed conditions (this "
+ "is default)\n");
+ printf(" 3 ... As 1 and all conditions (and "
+ "extended summary)\n");
+ printf(" --color=WHEN Enable colorized output (WHEN is one of "
+ "'auto', 'always', 'never')\n");
+ printf(" -h, --help Display this help and exit\n");
+ printf("\n");
+ test_list_names__();
+}
+
+int main(int argc, char **argv)
+{
+ const struct test__ **tests = NULL;
+ int i, j, n = 0;
+ int seen_double_dash = 0;
+
+ test_argv0__ = argv[0];
+
+#if defined CUTEST_UNIX__
+ test_colorize__ = isatty(STDOUT_FILENO);
+#elif defined CUTEST_WIN__
+ test_colorize__ = _isatty(_fileno(stdout));
+#else
+ test_colorize__ = 0;
+#endif
+
+ /* Parse options */
+ for (i = 1; i < argc; i++) {
+ if (seen_double_dash || argv[i][0] != '-') {
+ tests = (const struct test__ **)realloc(
+ (void *)tests, (n + 1) * sizeof(const struct test__ *));
+ if (tests == NULL) {
+ fprintf(stderr, "Out of memory.\n");
+ exit(2);
+ }
+ tests[n] = test_by_name__(argv[i]);
+ if (tests[n] == NULL) {
+ fprintf(stderr, "%s: Unrecognized unit test '%s'\n", argv[0],
+ argv[i]);
+ fprintf(stderr, "Try '%s --list' for list of unit tests.\n",
+ argv[0]);
+ exit(2);
+ }
+ n++;
+ } else if (strcmp(argv[i], "--") == 0) {
+ seen_double_dash = 1;
+ } else if (strcmp(argv[i], "--help") == 0 ||
+ strcmp(argv[i], "-h") == 0) {
+ test_help__();
+ exit(0);
+ } else if (strcmp(argv[i], "--verbose") == 0 ||
+ strcmp(argv[i], "-v") == 0) {
+ test_verbose_level__++;
+ } else if (strncmp(argv[i], "--verbose=", 10) == 0) {
+ test_verbose_level__ = atoi(argv[i] + 10);
+ } else if (strcmp(argv[i], "--color=auto") == 0) {
+ /* noop (set from above) */
+ } else if (strcmp(argv[i], "--color=always") == 0 ||
+ strcmp(argv[i], "--color") == 0) {
+ test_colorize__ = 1;
+ } else if (strcmp(argv[i], "--color=never") == 0) {
+ test_colorize__ = 0;
+ } else if (strcmp(argv[i], "--skip") == 0 ||
+ strcmp(argv[i], "-s") == 0) {
+ test_skip_mode__ = 1;
+ } else if (strcmp(argv[i], "--no-exec") == 0) {
+ test_no_exec__ = 1;
+ } else if (strcmp(argv[i], "--no-summary") == 0) {
+ test_no_summary__ = 1;
+ } else if (strcmp(argv[i], "--list") == 0 ||
+ strcmp(argv[i], "-l") == 0) {
+ test_list_names__();
+ exit(0);
+ } else {
+ fprintf(stderr, "%s: Unrecognized option '%s'\n", argv[0], argv[i]);
+ fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]);
+ exit(2);
+ }
+ }
+
+#if defined(CUTEST_WIN__)
+ SetUnhandledExceptionFilter(test_exception_filter__);
+#endif
+
+ /* Count all test units */
+ test_count__ = 0;
+ for (i = 0; test_list__[i].func != NULL; i++)
+ test_count__++;
+
+ /* Run the tests */
+ if (n == 0) {
+ /* Run all tests */
+ for (i = 0; test_list__[i].func != NULL; i++)
+ test_run__(&test_list__[i]);
+ } else if (!test_skip_mode__) {
+ /* Run the listed tests */
+ for (i = 0; i < n; i++)
+ test_run__(tests[i]);
+ } else {
+ /* Run all tests except those listed */
+ for (i = 0; test_list__[i].func != NULL; i++) {
+ int want_skip = 0;
+ for (j = 0; j < n; j++) {
+ if (tests[j] == &test_list__[i]) {
+ want_skip = 1;
+ break;
+ }
+ }
+ if (!want_skip)
+ test_run__(&test_list__[i]);
+ }
+ }
+
+ /* Write a summary */
+ if (!test_no_summary__ && test_verbose_level__ >= 1) {
+ test_print_in_color__(CUTEST_COLOR_DEFAULT_INTENSIVE__, "\nSummary:\n");
+
+ if (test_verbose_level__ >= 3) {
+ printf(" Count of all unit tests: %4d\n", test_count__);
+ printf(" Count of run unit tests: %4d\n",
+ test_stat_run_units__);
+ printf(" Count of failed unit tests: %4d\n",
+ test_stat_failed_units__);
+ printf(" Count of skipped unit tests: %4d\n",
+ test_count__ - test_stat_run_units__);
+ }
+
+ if (test_stat_failed_units__ == 0) {
+ test_print_in_color__(CUTEST_COLOR_GREEN_INTENSIVE__,
+ " SUCCESS: All unit tests have passed.\n");
+ } else {
+ test_print_in_color__(
+ CUTEST_COLOR_RED_INTENSIVE__,
+ " FAILED: %d of %d unit tests have failed.\n",
+ test_stat_failed_units__, test_stat_run_units__);
+ }
+ }
+
+ if (tests != NULL)
+ free((void *)tests);
+
+ return (test_stat_failed_units__ == 0) ? 0 : 1;
+}
+
+#endif /* #ifndef TEST_NO_MAIN */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* #ifndef CUTEST_H__ */
diff --git a/netwerk/srtp/src/test/dtls_srtp_driver.c b/netwerk/srtp/src/test/dtls_srtp_driver.c
new file mode 100644
index 0000000000..4f4d0a39b3
--- /dev/null
+++ b/netwerk/srtp/src/test/dtls_srtp_driver.c
@@ -0,0 +1,261 @@
+/*
+ * dtls_srtp_driver.c
+ *
+ * test driver for DTLS-SRTP functions
+ *
+ * David McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 <stdio.h> /* for printf() */
+#include "getopt_s.h" /* for local getopt() */
+#include "srtp_priv.h"
+
+srtp_err_status_t test_dtls_srtp(void);
+
+srtp_hdr_t *srtp_create_test_packet(int pkt_octet_len, uint32_t ssrc);
+
+void usage(char *prog_name)
+{
+ printf("usage: %s [ -t ][ -c ][ -v ][-d <debug_module> ]* [ -l ]\n"
+ " -d <mod> turn on debugging module <mod>\n"
+ " -l list debugging modules\n",
+ prog_name);
+ exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned do_list_mods = 0;
+ int q;
+ srtp_err_status_t err;
+
+ printf("dtls_srtp_driver\n");
+
+ /* initialize srtp library */
+ err = srtp_init();
+ if (err) {
+ printf("error: srtp init failed with error code %d\n", err);
+ exit(1);
+ }
+
+ /* process input arguments */
+ while (1) {
+ q = getopt_s(argc, argv, "ld:");
+ if (q == -1)
+ break;
+ switch (q) {
+ case 'l':
+ do_list_mods = 1;
+ break;
+ case 'd':
+ err = srtp_crypto_kernel_set_debug_module(optarg_s, 1);
+ if (err) {
+ printf("error: set debug module (%s) failed\n", optarg_s);
+ exit(1);
+ }
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (do_list_mods) {
+ err = srtp_crypto_kernel_list_debug_modules();
+ if (err) {
+ printf("error: list of debug modules failed\n");
+ exit(1);
+ }
+ }
+
+ printf("testing dtls_srtp...");
+ err = test_dtls_srtp();
+ if (err) {
+ printf("\nerror (code %d)\n", err);
+ exit(1);
+ }
+ printf("passed\n");
+
+ /* shut down srtp library */
+ err = srtp_shutdown();
+ if (err) {
+ printf("error: srtp shutdown failed with error code %d\n", err);
+ exit(1);
+ }
+
+ return 0;
+}
+
+srtp_err_status_t test_dtls_srtp(void)
+{
+ srtp_hdr_t *test_packet;
+ int test_packet_len = 80;
+ srtp_t s;
+ srtp_policy_t policy;
+ uint8_t key[SRTP_MAX_KEY_LEN];
+ uint8_t salt[SRTP_MAX_KEY_LEN];
+ unsigned int key_len, salt_len;
+ srtp_profile_t profile;
+ srtp_err_status_t err;
+
+ memset(&policy, 0x0, sizeof(srtp_policy_t));
+
+ /* create a 'null' SRTP session */
+ err = srtp_create(&s, NULL);
+ if (err)
+ return err;
+
+ /*
+ * verify that packet-processing functions behave properly - we
+ * expect that these functions will return srtp_err_status_no_ctx
+ */
+ test_packet = srtp_create_test_packet(80, 0xa5a5a5a5);
+ if (test_packet == NULL)
+ return srtp_err_status_alloc_fail;
+
+ err = srtp_protect(s, test_packet, &test_packet_len);
+ if (err != srtp_err_status_no_ctx) {
+ printf("wrong return value from srtp_protect() (got code %d)\n", err);
+ return srtp_err_status_fail;
+ }
+
+ err = srtp_unprotect(s, test_packet, &test_packet_len);
+ if (err != srtp_err_status_no_ctx) {
+ printf("wrong return value from srtp_unprotect() (got code %d)\n", err);
+ return srtp_err_status_fail;
+ }
+
+ err = srtp_protect_rtcp(s, test_packet, &test_packet_len);
+ if (err != srtp_err_status_no_ctx) {
+ printf("wrong return value from srtp_protect_rtcp() (got code %d)\n",
+ err);
+ return srtp_err_status_fail;
+ }
+
+ err = srtp_unprotect_rtcp(s, test_packet, &test_packet_len);
+ if (err != srtp_err_status_no_ctx) {
+ printf("wrong return value from srtp_unprotect_rtcp() (got code %d)\n",
+ err);
+ return srtp_err_status_fail;
+ }
+
+ /*
+ * set keys to known values for testing
+ */
+ profile = srtp_profile_aes128_cm_sha1_80;
+ key_len = srtp_profile_get_master_key_length(profile);
+ salt_len = srtp_profile_get_master_salt_length(profile);
+ memset(key, 0xff, key_len);
+ memset(salt, 0xee, salt_len);
+ srtp_append_salt_to_key(key, key_len, salt, salt_len);
+ policy.key = key;
+
+ /* initialize SRTP policy from profile */
+ err = srtp_crypto_policy_set_from_profile_for_rtp(&policy.rtp, profile);
+ if (err)
+ return err;
+ err = srtp_crypto_policy_set_from_profile_for_rtcp(&policy.rtcp, profile);
+ if (err)
+ return err;
+ policy.ssrc.type = ssrc_any_inbound;
+ policy.ekt = NULL;
+ policy.window_size = 128;
+ policy.allow_repeat_tx = 0;
+ policy.next = NULL;
+
+ err = srtp_add_stream(s, &policy);
+ if (err)
+ return err;
+
+ err = srtp_dealloc(s);
+ if (err)
+ return err;
+
+ free(test_packet);
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp_create_test_packet(len, ssrc) returns a pointer to a
+ * (malloced) example RTP packet whose data field has the length given
+ * by pkt_octet_len and the SSRC value ssrc. The total length of the
+ * packet is twelve octets longer, since the header is at the
+ * beginning. There is room at the end of the packet for a trailer,
+ * and the four octets following the packet are filled with 0xff
+ * values to enable testing for overwrites.
+ *
+ * note that the location of the test packet can (and should) be
+ * deallocated with the free() call once it is no longer needed.
+ */
+
+srtp_hdr_t *srtp_create_test_packet(int pkt_octet_len, uint32_t ssrc)
+{
+ int i;
+ uint8_t *buffer;
+ srtp_hdr_t *hdr;
+ int bytes_in_hdr = 12;
+
+ /* allocate memory for test packet */
+ hdr = malloc(pkt_octet_len + bytes_in_hdr + SRTP_MAX_TRAILER_LEN + 4);
+ if (!hdr)
+ return NULL;
+
+ hdr->version = 2; /* RTP version two */
+ hdr->p = 0; /* no padding needed */
+ hdr->x = 0; /* no header extension */
+ hdr->cc = 0; /* no CSRCs */
+ hdr->m = 0; /* marker bit */
+ hdr->pt = 0xf; /* payload type */
+ hdr->seq = htons(0x1234); /* sequence number */
+ hdr->ts = htonl(0xdecafbad); /* timestamp */
+ hdr->ssrc = htonl(ssrc); /* synch. source */
+
+ buffer = (uint8_t *)hdr;
+ buffer += bytes_in_hdr;
+
+ /* set RTP data to 0xab */
+ for (i = 0; i < pkt_octet_len; i++)
+ *buffer++ = 0xab;
+
+ /* set post-data value to 0xffff to enable overrun checking */
+ for (i = 0; i < SRTP_MAX_TRAILER_LEN + 4; i++)
+ *buffer++ = 0xff;
+
+ return hdr;
+}
diff --git a/netwerk/srtp/src/test/getopt_s.c b/netwerk/srtp/src/test/getopt_s.c
new file mode 100644
index 0000000000..e0bd7f77ed
--- /dev/null
+++ b/netwerk/srtp/src/test/getopt_s.c
@@ -0,0 +1,109 @@
+/*
+ * getopt.c
+ *
+ * a minimal implementation of the getopt() function, written so that
+ * test applications that use that function can run on non-POSIX
+ * platforms
+ *
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 <stdlib.h> /* for NULL */
+
+int optind_s = 0;
+
+char *optarg_s;
+
+#define GETOPT_FOUND_WITHOUT_ARGUMENT 2
+#define GETOPT_FOUND_WITH_ARGUMENT 1
+#define GETOPT_NOT_FOUND 0
+
+static int getopt_check_character(char c, const char *string)
+{
+ unsigned int max_string_len = 128;
+
+ while (*string != 0) {
+ if (max_string_len == 0) {
+ return GETOPT_NOT_FOUND;
+ }
+ max_string_len--;
+ if (*string++ == c) {
+ if (*string == ':') {
+ return GETOPT_FOUND_WITH_ARGUMENT;
+ } else {
+ return GETOPT_FOUND_WITHOUT_ARGUMENT;
+ }
+ }
+ }
+ return GETOPT_NOT_FOUND;
+}
+
+int getopt_s(int argc, char *const argv[], const char *optstring)
+{
+ while (optind_s + 1 < argc) {
+ char *string;
+
+ /* move 'string' on to next argument */
+ optind_s++;
+ string = argv[optind_s];
+
+ if (string == NULL)
+ return '?'; /* NULL argument string */
+
+ if (string[0] != '-')
+ return -1; /* found an unexpected character */
+
+ switch (getopt_check_character(string[1], optstring)) {
+ case GETOPT_FOUND_WITH_ARGUMENT:
+ if (optind_s + 1 < argc) {
+ optind_s++;
+ optarg_s = argv[optind_s];
+ return string[1];
+ } else {
+ return '?'; /* argument missing */
+ }
+ case GETOPT_FOUND_WITHOUT_ARGUMENT:
+ return string[1];
+ case GETOPT_NOT_FOUND:
+ default:
+ return '?'; /* didn't find expected character */
+ break;
+ }
+ }
+
+ return -1;
+}
diff --git a/netwerk/srtp/src/test/rdbx_driver.c b/netwerk/srtp/src/test/rdbx_driver.c
new file mode 100644
index 0000000000..3da6503719
--- /dev/null
+++ b/netwerk/srtp/src/test/rdbx_driver.c
@@ -0,0 +1,360 @@
+/*
+ * rdbx_driver.c
+ *
+ * driver for the rdbx implementation (replay database with extended range)
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h> /* for printf() */
+#include "getopt_s.h" /* for local getopt() */
+
+#include "rdbx.h"
+
+#ifdef ROC_TEST
+#error "srtp_rdbx_t won't work with ROC_TEST - bitmask same size as seq_median"
+#endif
+
+#include "ut_sim.h"
+
+srtp_err_status_t test_replay_dbx(int num_trials, unsigned long ws);
+
+double rdbx_check_adds_per_second(int num_trials, unsigned long ws);
+
+void usage(char *prog_name)
+{
+ printf("usage: %s [ -t | -v ]\n", prog_name);
+ exit(255);
+}
+
+int main(int argc, char *argv[])
+{
+ double rate;
+ srtp_err_status_t status;
+ int q;
+ unsigned do_timing_test = 0;
+ unsigned do_validation = 0;
+
+ /* process input arguments */
+ while (1) {
+ q = getopt_s(argc, argv, "tv");
+ if (q == -1)
+ break;
+ switch (q) {
+ case 't':
+ do_timing_test = 1;
+ break;
+ case 'v':
+ do_validation = 1;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ printf("rdbx (replay database w/ extended range) test driver\n"
+ "David A. McGrew\n"
+ "Cisco Systems, Inc.\n");
+
+ if (!do_validation && !do_timing_test)
+ usage(argv[0]);
+
+ if (do_validation) {
+ printf("testing srtp_rdbx_t (ws=128)...\n");
+
+ status = test_replay_dbx(1 << 12, 128);
+ if (status) {
+ printf("failed\n");
+ exit(1);
+ }
+ printf("passed\n");
+
+ printf("testing srtp_rdbx_t (ws=1024)...\n");
+
+ status = test_replay_dbx(1 << 12, 1024);
+ if (status) {
+ printf("failed\n");
+ exit(1);
+ }
+ printf("passed\n");
+ }
+
+ if (do_timing_test) {
+ rate = rdbx_check_adds_per_second(1 << 18, 128);
+ printf("rdbx_check/replay_adds per second (ws=128): %e\n", rate);
+ rate = rdbx_check_adds_per_second(1 << 18, 1024);
+ printf("rdbx_check/replay_adds per second (ws=1024): %e\n", rate);
+ }
+
+ return 0;
+}
+
+void print_rdbx(srtp_rdbx_t *rdbx)
+{
+ char buf[2048];
+ printf("rdbx: {%llu, %s}\n", (unsigned long long)(rdbx->index),
+ bitvector_bit_string(&rdbx->bitmask, buf, sizeof(buf)));
+}
+
+/*
+ * rdbx_check_add(rdbx, idx) checks a known-to-be-good idx against
+ * rdbx, then adds it. if a failure is detected (i.e., the check
+ * indicates that the value is already in rdbx) then
+ * srtp_err_status_algo_fail is returned.
+ *
+ */
+
+srtp_err_status_t rdbx_check_add(srtp_rdbx_t *rdbx, uint32_t idx)
+{
+ int delta;
+ srtp_xtd_seq_num_t est;
+
+ delta = srtp_index_guess(&rdbx->index, &est, idx);
+
+ if (srtp_rdbx_check(rdbx, delta) != srtp_err_status_ok) {
+ printf("replay_check failed at index %u\n", idx);
+ return srtp_err_status_algo_fail;
+ }
+
+ /*
+ * in practice, we'd authenticate the packet containing idx, using
+ * the estimated value est, at this point
+ */
+
+ if (srtp_rdbx_add_index(rdbx, delta) != srtp_err_status_ok) {
+ printf("rdbx_add_index failed at index %u\n", idx);
+ return srtp_err_status_algo_fail;
+ }
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * rdbx_check_expect_failure(srtp_rdbx_t *rdbx, uint32_t idx)
+ *
+ * checks that a sequence number idx is in the replay database
+ * and thus will be rejected
+ */
+
+srtp_err_status_t rdbx_check_expect_failure(srtp_rdbx_t *rdbx, uint32_t idx)
+{
+ int delta;
+ srtp_xtd_seq_num_t est;
+ srtp_err_status_t status;
+
+ delta = srtp_index_guess(&rdbx->index, &est, idx);
+
+ status = srtp_rdbx_check(rdbx, delta);
+ if (status == srtp_err_status_ok) {
+ printf("delta: %d ", delta);
+ printf("replay_check failed at index %u (false positive)\n", idx);
+ return srtp_err_status_algo_fail;
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t rdbx_check_add_unordered(srtp_rdbx_t *rdbx, uint32_t idx)
+{
+ int delta;
+ srtp_xtd_seq_num_t est;
+ srtp_err_status_t rstat;
+
+ delta = srtp_index_guess(&rdbx->index, &est, idx);
+
+ rstat = srtp_rdbx_check(rdbx, delta);
+ if ((rstat != srtp_err_status_ok) &&
+ (rstat != srtp_err_status_replay_old)) {
+ printf("replay_check_add_unordered failed at index %u\n", idx);
+ return srtp_err_status_algo_fail;
+ }
+ if (rstat == srtp_err_status_replay_old) {
+ return srtp_err_status_ok;
+ }
+ if (srtp_rdbx_add_index(rdbx, delta) != srtp_err_status_ok) {
+ printf("rdbx_add_index failed at index %u\n", idx);
+ return srtp_err_status_algo_fail;
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t test_replay_dbx(int num_trials, unsigned long ws)
+{
+ srtp_rdbx_t rdbx;
+ uint32_t idx, ircvd;
+ ut_connection utc;
+ srtp_err_status_t status;
+ int num_fp_trials;
+
+ status = srtp_rdbx_init(&rdbx, ws);
+ if (status) {
+ printf("replay_init failed with error code %d\n", status);
+ exit(1);
+ }
+
+ /*
+ * test sequential insertion
+ */
+ printf("\ttesting sequential insertion...");
+ for (idx = 0; (int)idx < num_trials; idx++) {
+ status = rdbx_check_add(&rdbx, idx);
+ if (status)
+ return status;
+ }
+ printf("passed\n");
+
+ /*
+ * test for false positives by checking all of the index
+ * values which we've just added
+ *
+ * note that we limit the number of trials here, since allowing the
+ * rollover counter to roll over would defeat this test
+ */
+ num_fp_trials = num_trials % 0x10000;
+ if (num_fp_trials == 0) {
+ printf("warning: no false positive tests performed\n");
+ }
+ printf("\ttesting for false positives...");
+ for (idx = 0; (int)idx < num_fp_trials; idx++) {
+ status = rdbx_check_expect_failure(&rdbx, idx);
+ if (status)
+ return status;
+ }
+ printf("passed\n");
+
+ /* re-initialize */
+ srtp_rdbx_dealloc(&rdbx);
+
+ if (srtp_rdbx_init(&rdbx, ws) != srtp_err_status_ok) {
+ printf("replay_init failed\n");
+ return srtp_err_status_init_fail;
+ }
+
+ /*
+ * test non-sequential insertion
+ *
+ * this test covers only fase negatives, since the values returned
+ * by ut_next_index(...) are distinct
+ */
+ ut_init(&utc);
+
+ printf("\ttesting non-sequential insertion...");
+ for (idx = 0; (int)idx < num_trials; idx++) {
+ ircvd = ut_next_index(&utc);
+ status = rdbx_check_add_unordered(&rdbx, ircvd);
+ if (status)
+ return status;
+ status = rdbx_check_expect_failure(&rdbx, ircvd);
+ if (status)
+ return status;
+ }
+ printf("passed\n");
+
+ /* re-initialize */
+ srtp_rdbx_dealloc(&rdbx);
+
+ if (srtp_rdbx_init(&rdbx, ws) != srtp_err_status_ok) {
+ printf("replay_init failed\n");
+ return srtp_err_status_init_fail;
+ }
+
+ /*
+ * test insertion with large gaps.
+ * check for false positives for each insertion.
+ */
+ printf("\ttesting insertion with large gaps...");
+ for (idx = 0, ircvd = 0; (int)idx < num_trials;
+ idx++, ircvd += (1 << (rand() % 12))) {
+ status = rdbx_check_add(&rdbx, ircvd);
+ if (status)
+ return status;
+ status = rdbx_check_expect_failure(&rdbx, ircvd);
+ if (status)
+ return status;
+ }
+ printf("passed\n");
+
+ srtp_rdbx_dealloc(&rdbx);
+
+ return srtp_err_status_ok;
+}
+
+#include <time.h> /* for clock() */
+#include <stdlib.h> /* for random() */
+
+double rdbx_check_adds_per_second(int num_trials, unsigned long ws)
+{
+ uint32_t i;
+ int delta;
+ srtp_rdbx_t rdbx;
+ srtp_xtd_seq_num_t est;
+ clock_t timer;
+ int failures; /* count number of failures */
+
+ if (srtp_rdbx_init(&rdbx, ws) != srtp_err_status_ok) {
+ printf("replay_init failed\n");
+ exit(1);
+ }
+
+ failures = 0;
+ timer = clock();
+ for (i = 0; (int)i < num_trials; i++) {
+ delta = srtp_index_guess(&rdbx.index, &est, i);
+
+ if (srtp_rdbx_check(&rdbx, delta) != srtp_err_status_ok)
+ ++failures;
+ else if (srtp_rdbx_add_index(&rdbx, delta) != srtp_err_status_ok)
+ ++failures;
+ }
+ timer = clock() - timer;
+ if (timer < 1) {
+ timer = 1;
+ }
+
+ printf("number of failures: %d \n", failures);
+
+ srtp_rdbx_dealloc(&rdbx);
+
+ return (double)CLOCKS_PER_SEC * num_trials / timer;
+}
diff --git a/netwerk/srtp/src/test/replay_driver.c b/netwerk/srtp/src/test/replay_driver.c
new file mode 100644
index 0000000000..a88224684f
--- /dev/null
+++ b/netwerk/srtp/src/test/replay_driver.c
@@ -0,0 +1,283 @@
+/*
+ * replay_driver.c
+ *
+ * A driver for the replay_database implementation
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include "rdb.h"
+#include "ut_sim.h"
+
+/*
+ * num_trials defines the number of trials that are used in the
+ * validation functions below
+ */
+
+unsigned num_trials = 1 << 16;
+
+srtp_err_status_t test_rdb_db(void);
+
+double rdb_check_adds_per_second(void);
+
+int main(void)
+{
+ srtp_err_status_t err;
+
+ printf("testing anti-replay database (srtp_rdb_t)...\n");
+ err = test_rdb_db();
+ if (err) {
+ printf("failed\n");
+ exit(1);
+ }
+ printf("done\n");
+
+ printf("rdb_check/rdb_adds per second: %e\n", rdb_check_adds_per_second());
+
+ return 0;
+}
+
+void print_rdb(srtp_rdb_t *rdb)
+{
+ printf("rdb: {%u, %s}\n", rdb->window_start,
+ v128_bit_string(&rdb->bitmask));
+}
+
+srtp_err_status_t rdb_check_add(srtp_rdb_t *rdb, uint32_t idx)
+{
+ if (srtp_rdb_check(rdb, idx) != srtp_err_status_ok) {
+ printf("rdb_check failed at index %u\n", idx);
+ return srtp_err_status_fail;
+ }
+ if (srtp_rdb_add_index(rdb, idx) != srtp_err_status_ok) {
+ printf("rdb_add_index failed at index %u\n", idx);
+ return srtp_err_status_fail;
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t rdb_check_expect_failure(srtp_rdb_t *rdb, uint32_t idx)
+{
+ srtp_err_status_t err;
+
+ err = srtp_rdb_check(rdb, idx);
+ if ((err != srtp_err_status_replay_old) &&
+ (err != srtp_err_status_replay_fail)) {
+ printf("rdb_check failed at index %u (false positive)\n", idx);
+ return srtp_err_status_fail;
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t rdb_check_add_unordered(srtp_rdb_t *rdb, uint32_t idx)
+{
+ srtp_err_status_t rstat;
+
+ /* printf("index: %u\n", idx); */
+ rstat = srtp_rdb_check(rdb, idx);
+ if ((rstat != srtp_err_status_ok) &&
+ (rstat != srtp_err_status_replay_old)) {
+ printf("rdb_check_add_unordered failed at index %u\n", idx);
+ return rstat;
+ }
+ if (rstat == srtp_err_status_replay_old) {
+ return srtp_err_status_ok;
+ }
+ if (srtp_rdb_add_index(rdb, idx) != srtp_err_status_ok) {
+ printf("rdb_add_index failed at index %u\n", idx);
+ return srtp_err_status_fail;
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t test_rdb_db()
+{
+ srtp_rdb_t rdb;
+ uint32_t idx, ircvd;
+ ut_connection utc;
+ srtp_err_status_t err;
+
+ if (srtp_rdb_init(&rdb) != srtp_err_status_ok) {
+ printf("rdb_init failed\n");
+ return srtp_err_status_init_fail;
+ }
+
+ /* test sequential insertion */
+ for (idx = 0; idx < num_trials; idx++) {
+ err = rdb_check_add(&rdb, idx);
+ if (err)
+ return err;
+ }
+
+ /* test for false positives */
+ for (idx = 0; idx < num_trials; idx++) {
+ err = rdb_check_expect_failure(&rdb, idx);
+ if (err)
+ return err;
+ }
+
+ /* re-initialize */
+ if (srtp_rdb_init(&rdb) != srtp_err_status_ok) {
+ printf("rdb_init failed\n");
+ return srtp_err_status_fail;
+ }
+
+ /* test non-sequential insertion */
+ ut_init(&utc);
+
+ for (idx = 0; idx < num_trials; idx++) {
+ ircvd = ut_next_index(&utc);
+ err = rdb_check_add_unordered(&rdb, ircvd);
+ if (err)
+ return err;
+ err = rdb_check_expect_failure(&rdb, ircvd);
+ if (err)
+ return err;
+ }
+
+ /* re-initialize */
+ if (srtp_rdb_init(&rdb) != srtp_err_status_ok) {
+ printf("rdb_init failed\n");
+ return srtp_err_status_fail;
+ }
+
+ /* test insertion with large gaps */
+ for (idx = 0, ircvd = 0; idx < num_trials;
+ idx++, ircvd += (1 << (rand() % 10))) {
+ err = rdb_check_add(&rdb, ircvd);
+ if (err)
+ return err;
+ err = rdb_check_expect_failure(&rdb, ircvd);
+ if (err)
+ return err;
+ }
+
+ /* re-initialize */
+ if (srtp_rdb_init(&rdb) != srtp_err_status_ok) {
+ printf("rdb_init failed\n");
+ return srtp_err_status_fail;
+ }
+
+ /* test loss of first 513 packets */
+ for (idx = 0; idx < num_trials; idx++) {
+ err = rdb_check_add(&rdb, idx + 513);
+ if (err)
+ return err;
+ }
+
+ /* test for false positives */
+ for (idx = 0; idx < num_trials + 513; idx++) {
+ err = rdb_check_expect_failure(&rdb, idx);
+ if (err)
+ return err;
+ }
+
+ /* test for key expired */
+ if (srtp_rdb_init(&rdb) != srtp_err_status_ok) {
+ printf("rdb_init failed\n");
+ return srtp_err_status_fail;
+ }
+ rdb.window_start = 0x7ffffffe;
+ if (srtp_rdb_increment(&rdb) != srtp_err_status_ok) {
+ printf("srtp_rdb_increment of 0x7ffffffe failed\n");
+ return srtp_err_status_fail;
+ }
+ if (srtp_rdb_get_value(&rdb) != 0x7fffffff) {
+ printf("rdb valiue was not 0x7fffffff\n");
+ return srtp_err_status_fail;
+ }
+ if (srtp_rdb_increment(&rdb) != srtp_err_status_key_expired) {
+ printf("srtp_rdb_increment of 0x7fffffff did not return "
+ "srtp_err_status_key_expired\n");
+ return srtp_err_status_fail;
+ }
+ if (srtp_rdb_get_value(&rdb) != 0x7fffffff) {
+ printf("rdb valiue was not 0x7fffffff\n");
+ return srtp_err_status_fail;
+ }
+
+ return srtp_err_status_ok;
+}
+
+#include <time.h> /* for clock() */
+#include <stdlib.h> /* for random() */
+
+#define REPLAY_NUM_TRIALS 10000000
+
+double rdb_check_adds_per_second(void)
+{
+ uint32_t i;
+ srtp_rdb_t rdb;
+ clock_t timer;
+ int failures = 0; /* count number of failures */
+
+ if (srtp_rdb_init(&rdb) != srtp_err_status_ok) {
+ printf("rdb_init failed\n");
+ exit(1);
+ }
+
+ timer = clock();
+ for (i = 0; i < REPLAY_NUM_TRIALS; i += 3) {
+ if (srtp_rdb_check(&rdb, i + 2) != srtp_err_status_ok)
+ ++failures;
+ if (srtp_rdb_add_index(&rdb, i + 2) != srtp_err_status_ok)
+ ++failures;
+ if (srtp_rdb_check(&rdb, i + 1) != srtp_err_status_ok)
+ ++failures;
+ if (srtp_rdb_add_index(&rdb, i + 1) != srtp_err_status_ok)
+ ++failures;
+ if (srtp_rdb_check(&rdb, i) != srtp_err_status_ok)
+ ++failures;
+ if (srtp_rdb_add_index(&rdb, i) != srtp_err_status_ok)
+ ++failures;
+ }
+ timer = clock() - timer;
+
+ return (double)CLOCKS_PER_SEC * REPLAY_NUM_TRIALS / timer;
+}
diff --git a/netwerk/srtp/src/test/roc_driver.c b/netwerk/srtp/src/test/roc_driver.c
new file mode 100644
index 0000000000..439862039a
--- /dev/null
+++ b/netwerk/srtp/src/test/roc_driver.c
@@ -0,0 +1,170 @@
+/*
+ * roc_driver.c
+ *
+ * test driver for rollover counter replay implementation
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+/*
+ * defining ROC_TEST causes small datatypes to be used in
+ * srtp_xtd_seq_num_t - this allows the functions to be exhaustively tested.
+ */
+#if ROC_NEEDS_TO_BE_TESTED
+#define ROC_TEST
+#endif
+
+#include "rdbx.h"
+#include "ut_sim.h"
+
+srtp_err_status_t roc_test(int num_trials);
+
+int main(void)
+{
+ srtp_err_status_t status;
+
+ printf("rollover counter test driver\n"
+ "David A. McGrew\n"
+ "Cisco Systems, Inc.\n");
+
+ printf("testing index functions...");
+ status = roc_test(1 << 18);
+ if (status) {
+ printf("failed\n");
+ exit(status);
+ }
+ printf("passed\n");
+ return 0;
+}
+
+#define ROC_VERBOSE 0
+
+srtp_err_status_t roc_test(int num_trials)
+{
+ srtp_xtd_seq_num_t local, est, ref;
+ ut_connection utc;
+ int i, num_bad_est = 0;
+ int delta;
+ uint32_t ircvd;
+ double failure_rate;
+
+ srtp_index_init(&local);
+ srtp_index_init(&ref);
+ srtp_index_init(&est);
+
+ printf("\n\ttesting sequential insertion...");
+ for (i = 0; i < 2048; i++) {
+ srtp_index_guess(&local, &est, (uint16_t)ref);
+#if ROC_VERBOSE
+ printf("%lld, %lld, %d\n", ref, est, i);
+#endif
+ if (ref != est) {
+#if ROC_VERBOSE
+ printf(" *bad estimate*\n");
+#endif
+ ++num_bad_est;
+ }
+ srtp_index_advance(&ref, 1);
+ }
+ failure_rate = (double)num_bad_est / num_trials;
+ if (failure_rate > 0.01) {
+ printf("error: failure rate too high (%d bad estimates in %d trials)\n",
+ num_bad_est, num_trials);
+ return srtp_err_status_algo_fail;
+ }
+ printf("done\n");
+
+ printf("\ttesting non-sequential insertion...");
+ srtp_index_init(&local);
+ srtp_index_init(&ref);
+ srtp_index_init(&est);
+ ut_init(&utc);
+
+ for (i = 0; i < num_trials; i++) {
+ /* get next seq num from unreliable transport simulator */
+ ircvd = ut_next_index(&utc);
+
+ /* set ref to value of ircvd */
+ ref = ircvd;
+
+ /* estimate index based on low bits of ircvd */
+ delta = srtp_index_guess(&local, &est, (uint16_t)ref);
+#if ROC_VERBOSE
+ printf("ref: %lld, local: %lld, est: %lld, ircvd: %d, delta: %d\n", ref,
+ local, est, ircvd, delta);
+#endif
+
+ if (local + delta != est) {
+ printf(" *bad delta*: local %llu + delta %d != est %llu\n",
+ (unsigned long long)local, delta, (unsigned long long)est);
+ return srtp_err_status_algo_fail;
+ }
+
+ /* now update local srtp_xtd_seq_num_t as necessary */
+ if (delta > 0)
+ srtp_index_advance(&local, delta);
+
+ if (ref != est) {
+#if ROC_VERBOSE
+ printf(" *bad estimate*\n");
+#endif
+ /* record failure event */
+ ++num_bad_est;
+
+ /* reset local value to correct value */
+ local = ref;
+ }
+ }
+ failure_rate = (double)num_bad_est / num_trials;
+ if (failure_rate > 0.01) {
+ printf("error: failure rate too high (%d bad estimates in %d trials)\n",
+ num_bad_est, num_trials);
+ return srtp_err_status_algo_fail;
+ }
+ printf("done\n");
+
+ return srtp_err_status_ok;
+}
diff --git a/netwerk/srtp/src/test/rtp.c b/netwerk/srtp/src/test/rtp.c
new file mode 100644
index 0000000000..f60a9b11b2
--- /dev/null
+++ b/netwerk/srtp/src/test/rtp.c
@@ -0,0 +1,227 @@
+/*
+ * rtp.c
+ *
+ * library functions for the real-time transport protocol
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 "rtp.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+
+#define PRINT_DEBUG 0 /* set to 1 to print out debugging data */
+#define VERBOSE_DEBUG 0 /* set to 1 to print out more data */
+
+int rtp_sendto(rtp_sender_t sender, const void *msg, int len)
+{
+ int octets_sent;
+ srtp_err_status_t stat;
+ int pkt_len = len + RTP_HEADER_LEN;
+
+ /* marshal data */
+ strncpy(sender->message.body, msg, len);
+
+ /* update header */
+ sender->message.header.seq = ntohs(sender->message.header.seq) + 1;
+ sender->message.header.seq = htons(sender->message.header.seq);
+ sender->message.header.ts = ntohl(sender->message.header.ts) + 1;
+ sender->message.header.ts = htonl(sender->message.header.ts);
+
+ /* apply srtp */
+ stat = srtp_protect(sender->srtp_ctx, &sender->message.header, &pkt_len);
+ if (stat) {
+#if PRINT_DEBUG
+ fprintf(stderr, "error: srtp protection failed with code %d\n", stat);
+#endif
+ return -1;
+ }
+#if VERBOSE_DEBUG
+ srtp_print_packet(&sender->message.header, pkt_len);
+#endif
+ octets_sent =
+ sendto(sender->socket, (void *)&sender->message, pkt_len, 0,
+ (struct sockaddr *)&sender->addr, sizeof(struct sockaddr_in));
+
+ if (octets_sent != pkt_len) {
+#if PRINT_DEBUG
+ fprintf(stderr, "error: couldn't send message %s", (char *)msg);
+ perror("");
+#endif
+ }
+
+ return octets_sent;
+}
+
+int rtp_recvfrom(rtp_receiver_t receiver, void *msg, int *len)
+{
+ int octets_recvd;
+ srtp_err_status_t stat;
+
+ octets_recvd = recvfrom(receiver->socket, (void *)&receiver->message, *len,
+ 0, (struct sockaddr *)NULL, 0);
+
+ if (octets_recvd == -1) {
+ *len = 0;
+ return -1;
+ }
+
+ /* verify rtp header */
+ if (receiver->message.header.version != 2) {
+ *len = 0;
+ return -1;
+ }
+
+#if PRINT_DEBUG
+ fprintf(stderr, "%d octets received from SSRC %u\n", octets_recvd,
+ receiver->message.header.ssrc);
+#endif
+#if VERBOSE_DEBUG
+ srtp_print_packet(&receiver->message.header, octets_recvd);
+#endif
+
+ /* apply srtp */
+ stat = srtp_unprotect(receiver->srtp_ctx, &receiver->message.header,
+ &octets_recvd);
+ if (stat) {
+ fprintf(stderr, "error: srtp unprotection failed with code %d%s\n",
+ stat,
+ stat == srtp_err_status_replay_fail
+ ? " (replay check failed)"
+ : stat == srtp_err_status_auth_fail ? " (auth check failed)"
+ : "");
+ return -1;
+ }
+ strncpy(msg, receiver->message.body, octets_recvd);
+
+ return octets_recvd;
+}
+
+int rtp_sender_init(rtp_sender_t sender,
+ int sock,
+ struct sockaddr_in addr,
+ unsigned int ssrc)
+{
+ /* set header values */
+ sender->message.header.ssrc = htonl(ssrc);
+ sender->message.header.ts = 0;
+ sender->message.header.seq = (uint16_t)rand();
+ sender->message.header.m = 0;
+ sender->message.header.pt = 0x1;
+ sender->message.header.version = 2;
+ sender->message.header.p = 0;
+ sender->message.header.x = 0;
+ sender->message.header.cc = 0;
+
+ /* set other stuff */
+ sender->socket = sock;
+ sender->addr = addr;
+
+ return 0;
+}
+
+int rtp_receiver_init(rtp_receiver_t rcvr,
+ int sock,
+ struct sockaddr_in addr,
+ unsigned int ssrc)
+{
+ /* set header values */
+ rcvr->message.header.ssrc = htonl(ssrc);
+ rcvr->message.header.ts = 0;
+ rcvr->message.header.seq = 0;
+ rcvr->message.header.m = 0;
+ rcvr->message.header.pt = 0x1;
+ rcvr->message.header.version = 2;
+ rcvr->message.header.p = 0;
+ rcvr->message.header.x = 0;
+ rcvr->message.header.cc = 0;
+
+ /* set other stuff */
+ rcvr->socket = sock;
+ rcvr->addr = addr;
+
+ return 0;
+}
+
+int rtp_sender_init_srtp(rtp_sender_t sender, const srtp_policy_t *policy)
+{
+ return srtp_create(&sender->srtp_ctx, policy);
+}
+
+int rtp_sender_deinit_srtp(rtp_sender_t sender)
+{
+ return srtp_dealloc(sender->srtp_ctx);
+}
+
+int rtp_receiver_init_srtp(rtp_receiver_t sender, const srtp_policy_t *policy)
+{
+ return srtp_create(&sender->srtp_ctx, policy);
+}
+
+int rtp_receiver_deinit_srtp(rtp_receiver_t sender)
+{
+ return srtp_dealloc(sender->srtp_ctx);
+}
+
+rtp_sender_t rtp_sender_alloc(void)
+{
+ return (rtp_sender_t)malloc(sizeof(rtp_sender_ctx_t));
+}
+
+void rtp_sender_dealloc(rtp_sender_t rtp_ctx)
+{
+ free(rtp_ctx);
+}
+
+rtp_receiver_t rtp_receiver_alloc(void)
+{
+ return (rtp_receiver_t)malloc(sizeof(rtp_receiver_ctx_t));
+}
+
+void rtp_receiver_dealloc(rtp_receiver_t rtp_ctx)
+{
+ free(rtp_ctx);
+}
diff --git a/netwerk/srtp/src/test/rtp.h b/netwerk/srtp/src/test/rtp.h
new file mode 100644
index 0000000000..37921a665c
--- /dev/null
+++ b/netwerk/srtp/src/test/rtp.h
@@ -0,0 +1,155 @@
+/*
+ * rtp.h
+ *
+ * rtp interface for srtp reference implementation
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ * data types:
+ *
+ * rtp_msg_t an rtp message (the data that goes on the wire)
+ * rtp_sender_t sender side socket and rtp info
+ * rtp_receiver_t receiver side socket and rtp info
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 SRTP_RTP_H
+#define SRTP_RTP_H
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#elif defined HAVE_WINSOCK2_H
+#include <winsock2.h>
+#endif
+
+#include "srtp_priv.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * RTP_HEADER_LEN indicates the size of an RTP header
+ */
+#define RTP_HEADER_LEN 12
+
+/*
+ * RTP_MAX_BUF_LEN defines the largest RTP packet in the rtp.c implementation
+ */
+#define RTP_MAX_BUF_LEN 16384
+
+typedef srtp_hdr_t rtp_hdr_t;
+
+typedef struct {
+ srtp_hdr_t header;
+ char body[RTP_MAX_BUF_LEN];
+} rtp_msg_t;
+
+typedef struct rtp_sender_ctx_t {
+ rtp_msg_t message;
+ int socket;
+ srtp_ctx_t *srtp_ctx;
+ struct sockaddr_in addr; /* reciever's address */
+} rtp_sender_ctx_t;
+
+typedef struct rtp_receiver_ctx_t {
+ rtp_msg_t message;
+ int socket;
+ srtp_ctx_t *srtp_ctx;
+ struct sockaddr_in addr; /* receiver's address */
+} rtp_receiver_ctx_t;
+
+typedef struct rtp_sender_ctx_t *rtp_sender_t;
+
+typedef struct rtp_receiver_ctx_t *rtp_receiver_t;
+
+int rtp_sendto(rtp_sender_t sender, const void *msg, int len);
+
+int rtp_recvfrom(rtp_receiver_t receiver, void *msg, int *len);
+
+int rtp_receiver_init(rtp_receiver_t rcvr,
+ int sock,
+ struct sockaddr_in addr,
+ unsigned int ssrc);
+
+int rtp_sender_init(rtp_sender_t sender,
+ int sock,
+ struct sockaddr_in addr,
+ unsigned int ssrc);
+
+/*
+ * srtp_sender_init(...) initializes an rtp_sender_t
+ */
+
+int srtp_sender_init(
+ rtp_sender_t rtp_ctx, /* structure to be init'ed */
+ struct sockaddr_in name, /* socket name */
+ srtp_sec_serv_t security_services, /* sec. servs. to be used */
+ unsigned char *input_key /* master key/salt in hex */
+ );
+
+int srtp_receiver_init(
+ rtp_receiver_t rtp_ctx, /* structure to be init'ed */
+ struct sockaddr_in name, /* socket name */
+ srtp_sec_serv_t security_services, /* sec. servs. to be used */
+ unsigned char *input_key /* master key/salt in hex */
+ );
+
+int rtp_sender_init_srtp(rtp_sender_t sender, const srtp_policy_t *policy);
+
+int rtp_sender_deinit_srtp(rtp_sender_t sender);
+
+int rtp_receiver_init_srtp(rtp_receiver_t sender, const srtp_policy_t *policy);
+
+int rtp_receiver_deinit_srtp(rtp_receiver_t sender);
+
+rtp_sender_t rtp_sender_alloc(void);
+
+void rtp_sender_dealloc(rtp_sender_t rtp_ctx);
+
+rtp_receiver_t rtp_receiver_alloc(void);
+
+void rtp_receiver_dealloc(rtp_receiver_t rtp_ctx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SRTP_RTP_H */
diff --git a/netwerk/srtp/src/test/rtp_decoder.c b/netwerk/srtp/src/test/rtp_decoder.c
new file mode 100644
index 0000000000..d718a2b3ad
--- /dev/null
+++ b/netwerk/srtp/src/test/rtp_decoder.c
@@ -0,0 +1,574 @@
+/*
+ * rtp_decoder.c
+ *
+ * decoder structures and functions for SRTP pcap decoder
+ *
+ * Example:
+ * $ wget --no-check-certificate \
+ * https://raw.githubusercontent.com/gteissier/srtp-decrypt/master/marseillaise-srtp.pcap
+ * $ ./test/rtp_decoder -a -t 10 -e 128 -b \
+ * aSBrbm93IGFsbCB5b3VyIGxpdHRsZSBzZWNyZXRz \
+ * < ~/marseillaise-srtp.pcap \
+ * | text2pcap -t "%M:%S." -u 10000,10000 - - \
+ * > ./marseillaise-rtp.pcap
+ *
+ * There is also a different way of setting up key size and tag size
+ * based upon RFC 4568 crypto suite specification, i.e.:
+ *
+ * $ ./test/rtp_decoder -s AES_CM_128_HMAC_SHA1_80 -b \
+ * aSBrbm93IGFsbCB5b3VyIGxpdHRsZSBzZWNyZXRz ...
+ *
+ * Audio can be extracted using extractaudio utility from the RTPproxy
+ * package:
+ *
+ * $ extractaudio -A ./marseillaise-rtp.pcap ./marseillaise-out.wav
+ *
+ * Bernardo Torres <bernardo@torresautomacao.com.br>
+ *
+ * Some structure and code from https://github.com/gteissier/srtp-decrypt
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 "getopt_s.h" /* for local getopt() */
+#include <assert.h> /* for assert() */
+
+#include <pcap.h>
+#include "rtp_decoder.h"
+#include "util.h"
+
+#ifndef timersub
+#define timersub(a, b, result) \
+ do { \
+ (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
+ (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
+ if ((result)->tv_usec < 0) { \
+ --(result)->tv_sec; \
+ (result)->tv_usec += 1000000; \
+ } \
+ } while (0)
+#endif
+
+#define MAX_KEY_LEN 96
+#define MAX_FILTER 256
+
+struct srtp_crypto_suite {
+ const char *can_name;
+ int key_size;
+ int tag_size;
+};
+
+static struct srtp_crypto_suite srtp_crypto_suites[] = {
+ {.can_name = "AES_CM_128_HMAC_SHA1_32", .key_size = 128, .tag_size = 4 },
+#if 0
+ {.can_name = "F8_128_HMAC_SHA1_32", .key_size = 128, .tag_size = 4},
+#endif
+ {.can_name = "AES_CM_128_HMAC_SHA1_32", .key_size = 128, .tag_size = 4 },
+ {.can_name = "AES_CM_128_HMAC_SHA1_80", .key_size = 128, .tag_size = 10 },
+ {.can_name = NULL }
+};
+
+int main(int argc, char *argv[])
+{
+ char errbuf[PCAP_ERRBUF_SIZE];
+ bpf_u_int32 pcap_net = 0;
+ pcap_t *pcap_handle;
+#if BEW
+ struct sockaddr_in local;
+#endif
+ srtp_sec_serv_t sec_servs = sec_serv_none;
+ int c;
+ struct srtp_crypto_suite scs, *i_scsp;
+ scs.key_size = 128;
+ scs.tag_size = 8;
+ int gcm_on = 0;
+ char *input_key = NULL;
+ int b64_input = 0;
+ char key[MAX_KEY_LEN];
+ struct bpf_program fp;
+ char filter_exp[MAX_FILTER] = "";
+ rtp_decoder_t dec;
+ srtp_policy_t policy;
+ srtp_err_status_t status;
+ int len;
+ int expected_len;
+ int do_list_mods = 0;
+
+ fprintf(stderr, "Using %s [0x%x]\n", srtp_get_version_string(),
+ srtp_get_version());
+
+ /* initialize srtp library */
+ status = srtp_init();
+ if (status) {
+ fprintf(stderr,
+ "error: srtp initialization failed with error code %d\n",
+ status);
+ exit(1);
+ }
+
+ /* check args */
+ while (1) {
+ c = getopt_s(argc, argv, "b:k:gt:ae:ld:f:s:");
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'b':
+ b64_input = 1;
+ /* fall thru */
+ case 'k':
+ input_key = optarg_s;
+ break;
+ case 'e':
+ scs.key_size = atoi(optarg_s);
+ if (scs.key_size != 128 && scs.key_size != 256) {
+ fprintf(stderr,
+ "error: encryption key size must be 128 or 256 (%d)\n",
+ scs.key_size);
+ exit(1);
+ }
+ input_key = malloc(scs.key_size);
+ sec_servs |= sec_serv_conf;
+ break;
+ case 't':
+ scs.tag_size = atoi(optarg_s);
+ break;
+ case 'a':
+ sec_servs |= sec_serv_auth;
+ break;
+ case 'g':
+ gcm_on = 1;
+ sec_servs |= sec_serv_auth;
+ break;
+ case 'd':
+ status = srtp_crypto_kernel_set_debug_module(optarg_s, 1);
+ if (status) {
+ fprintf(stderr, "error: set debug module (%s) failed\n",
+ optarg_s);
+ exit(1);
+ }
+ break;
+ case 'f':
+ if (strlen(optarg_s) > MAX_FILTER) {
+ fprintf(stderr, "error: filter bigger than %d characters\n",
+ MAX_FILTER);
+ exit(1);
+ }
+ fprintf(stderr, "Setting filter as %s\n", optarg_s);
+ strcpy(filter_exp, optarg_s);
+ break;
+ case 'l':
+ do_list_mods = 1;
+ break;
+ case 's':
+ for (i_scsp = &srtp_crypto_suites[0]; i_scsp->can_name != NULL;
+ i_scsp++) {
+ if (strcasecmp(i_scsp->can_name, optarg_s) == 0) {
+ break;
+ }
+ }
+ if (i_scsp->can_name == NULL) {
+ fprintf(stderr, "Unknown/unsupported crypto suite name %s\n",
+ optarg_s);
+ exit(1);
+ }
+ scs = *i_scsp;
+ input_key = malloc(scs.key_size);
+ sec_servs |= sec_serv_conf | sec_serv_auth;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (gcm_on && scs.tag_size != 8 && scs.tag_size != 16) {
+ fprintf(stderr, "error: GCM tag size must be 8 or 16 (%d)\n",
+ scs.tag_size);
+ // exit(1);
+ }
+
+ if (do_list_mods) {
+ status = srtp_crypto_kernel_list_debug_modules();
+ if (status) {
+ fprintf(stderr, "error: list of debug modules failed\n");
+ exit(1);
+ }
+ return 0;
+ }
+
+ if ((sec_servs && !input_key) || (!sec_servs && input_key)) {
+ /*
+ * a key must be provided if and only if security services have
+ * been requested
+ */
+ if (input_key == NULL) {
+ fprintf(stderr, "key not provided\n");
+ }
+ if (!sec_servs) {
+ fprintf(stderr, "no secservs\n");
+ }
+ fprintf(stderr, "provided\n");
+ usage(argv[0]);
+ }
+
+ /* report security services selected on the command line */
+ fprintf(stderr, "security services: ");
+ if (sec_servs & sec_serv_conf)
+ fprintf(stderr, "confidentiality ");
+ if (sec_servs & sec_serv_auth)
+ fprintf(stderr, "message authentication");
+ if (sec_servs == sec_serv_none)
+ fprintf(stderr, "none");
+ fprintf(stderr, "\n");
+
+ /* set up the srtp policy and master key */
+ if (sec_servs) {
+ /*
+ * create policy structure, using the default mechanisms but
+ * with only the security services requested on the command line,
+ * using the right SSRC value
+ */
+ switch (sec_servs) {
+ case sec_serv_conf_and_auth:
+ if (gcm_on) {
+#ifdef OPENSSL
+ switch (scs.key_size) {
+ case 128:
+ srtp_crypto_policy_set_aes_gcm_128_8_auth(&policy.rtp);
+ srtp_crypto_policy_set_aes_gcm_128_8_auth(&policy.rtcp);
+ break;
+ case 256:
+ srtp_crypto_policy_set_aes_gcm_256_8_auth(&policy.rtp);
+ srtp_crypto_policy_set_aes_gcm_256_8_auth(&policy.rtcp);
+ break;
+ }
+#else
+ fprintf(stderr, "error: GCM mode only supported when using the "
+ "OpenSSL crypto engine.\n");
+ return 0;
+#endif
+ } else {
+ switch (scs.key_size) {
+ case 128:
+ srtp_crypto_policy_set_rtp_default(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ break;
+ case 256:
+ srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ break;
+ }
+ }
+ break;
+ case sec_serv_conf:
+ if (gcm_on) {
+ fprintf(
+ stderr,
+ "error: GCM mode must always be used with auth enabled\n");
+ return -1;
+ } else {
+ switch (scs.key_size) {
+ case 128:
+ srtp_crypto_policy_set_aes_cm_128_null_auth(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ break;
+ case 256:
+ srtp_crypto_policy_set_aes_cm_256_null_auth(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ break;
+ }
+ }
+ break;
+ case sec_serv_auth:
+ if (gcm_on) {
+#ifdef OPENSSL
+ switch (scs.key_size) {
+ case 128:
+ srtp_crypto_policy_set_aes_gcm_128_8_only_auth(&policy.rtp);
+ srtp_crypto_policy_set_aes_gcm_128_8_only_auth(
+ &policy.rtcp);
+ break;
+ case 256:
+ srtp_crypto_policy_set_aes_gcm_256_8_only_auth(&policy.rtp);
+ srtp_crypto_policy_set_aes_gcm_256_8_only_auth(
+ &policy.rtcp);
+ break;
+ }
+#else
+ printf("error: GCM mode only supported when using the OpenSSL "
+ "crypto engine.\n");
+ return 0;
+#endif
+ } else {
+ srtp_crypto_policy_set_null_cipher_hmac_sha1_80(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ }
+ break;
+ default:
+ fprintf(stderr, "error: unknown security service requested\n");
+ return -1;
+ }
+
+ policy.key = (uint8_t *)key;
+ policy.ekt = NULL;
+ policy.next = NULL;
+ policy.window_size = 128;
+ policy.allow_repeat_tx = 0;
+ policy.rtp.sec_serv = sec_servs;
+ policy.rtcp.sec_serv =
+ sec_servs; // sec_serv_none; /* we don't do RTCP anyway */
+ fprintf(stderr, "setting tag len %d\n", scs.tag_size);
+ policy.rtp.auth_tag_len = scs.tag_size;
+
+ if (gcm_on && scs.tag_size != 8) {
+ fprintf(stderr, "setted tag len %d\n", scs.tag_size);
+ policy.rtp.auth_tag_len = scs.tag_size;
+ }
+
+ /*
+ * read key from hexadecimal or base64 on command line into an octet
+ * string
+ */
+ if (b64_input) {
+ int pad;
+ expected_len = policy.rtp.cipher_key_len * 4 / 3;
+ len = base64_string_to_octet_string(key, &pad, input_key,
+ expected_len);
+ if (pad != 0) {
+ fprintf(stderr, "error: padding in base64 unexpected\n");
+ exit(1);
+ }
+ } else {
+ expected_len = policy.rtp.cipher_key_len * 2;
+ len = hex_string_to_octet_string(key, input_key, expected_len);
+ }
+ /* check that hex string is the right length */
+ if (len < expected_len) {
+ fprintf(stderr, "error: too few digits in key/salt "
+ "(should be %d digits, found %d)\n",
+ expected_len, len);
+ exit(1);
+ }
+ if (strlen(input_key) > policy.rtp.cipher_key_len * 2) {
+ fprintf(stderr, "error: too many digits in key/salt "
+ "(should be %d hexadecimal digits, found %u)\n",
+ policy.rtp.cipher_key_len * 2, (unsigned)strlen(input_key));
+ exit(1);
+ }
+
+ fprintf(stderr, "set master key/salt to %s/",
+ octet_string_hex_string(key, 16));
+ fprintf(stderr, "%s\n", octet_string_hex_string(key + 16, 14));
+
+ } else {
+ fprintf(stderr,
+ "error: neither encryption or authentication were selected");
+ exit(1);
+ }
+
+ pcap_handle = pcap_open_offline("-", errbuf);
+
+ if (!pcap_handle) {
+ fprintf(stderr, "libpcap failed to open file '%s'\n", errbuf);
+ exit(1);
+ }
+ assert(pcap_handle != NULL);
+ if ((pcap_compile(pcap_handle, &fp, filter_exp, 1, pcap_net)) == -1) {
+ fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp,
+ pcap_geterr(pcap_handle));
+ return (2);
+ }
+ if (pcap_setfilter(pcap_handle, &fp) == -1) {
+ fprintf(stderr, "couldn't install filter %s: %s\n", filter_exp,
+ pcap_geterr(pcap_handle));
+ return (2);
+ }
+ dec = rtp_decoder_alloc();
+ if (dec == NULL) {
+ fprintf(stderr, "error: malloc() failed\n");
+ exit(1);
+ }
+ fprintf(stderr, "Starting decoder\n");
+ rtp_decoder_init(dec, policy);
+
+ pcap_loop(pcap_handle, 0, rtp_decoder_handle_pkt, (u_char *)dec);
+
+ rtp_decoder_deinit_srtp(dec);
+ rtp_decoder_dealloc(dec);
+
+ status = srtp_shutdown();
+ if (status) {
+ fprintf(stderr, "error: srtp shutdown failed with error code %d\n",
+ status);
+ exit(1);
+ }
+
+ return 0;
+}
+
+void usage(char *string)
+{
+ fprintf(
+ stderr,
+ "usage: %s [-d <debug>]* [[-k][-b] <key> [-a][-e]]\n"
+ "or %s -l\n"
+ "where -a use message authentication\n"
+ " -e <key size> use encryption (use 128 or 256 for key size)\n"
+ " -g Use AES-GCM mode (must be used with -e)\n"
+ " -t <tag size> Tag size to use (in GCM mode use 8 or 16)\n"
+ " -k <key> sets the srtp master key given in hexadecimal\n"
+ " -b <key> sets the srtp master key given in base64\n"
+ " -l list debug modules\n"
+ " -f \"<pcap filter>\" to filter only the desired SRTP packets\n"
+ " -d <debug> turn on debugging for module <debug>\n"
+ " -s \"<srtp-crypto-suite>\" to set both key and tag size based\n"
+ " on RFC4568-style crypto suite specification\n",
+ string, string);
+ exit(1);
+}
+
+rtp_decoder_t rtp_decoder_alloc(void)
+{
+ return (rtp_decoder_t)malloc(sizeof(rtp_decoder_ctx_t));
+}
+
+void rtp_decoder_dealloc(rtp_decoder_t rtp_ctx)
+{
+ free(rtp_ctx);
+}
+
+srtp_err_status_t rtp_decoder_init_srtp(rtp_decoder_t decoder,
+ unsigned int ssrc)
+{
+ decoder->policy.ssrc.value = htonl(ssrc);
+ return srtp_create(&decoder->srtp_ctx, &decoder->policy);
+}
+
+int rtp_decoder_deinit_srtp(rtp_decoder_t decoder)
+{
+ return srtp_dealloc(decoder->srtp_ctx);
+}
+
+int rtp_decoder_init(rtp_decoder_t dcdr, srtp_policy_t policy)
+{
+ dcdr->rtp_offset = DEFAULT_RTP_OFFSET;
+ dcdr->srtp_ctx = NULL;
+ dcdr->start_tv.tv_usec = 0;
+ dcdr->start_tv.tv_sec = 0;
+ dcdr->frame_nr = -1;
+ dcdr->policy = policy;
+ dcdr->policy.ssrc.type = ssrc_specific;
+ return 0;
+}
+
+/*
+ * decodes key as base64
+ */
+
+void hexdump(const void *ptr, size_t size)
+{
+ int i, j;
+ const unsigned char *cptr = ptr;
+
+ for (i = 0; i < size; i += 16) {
+ fprintf(stdout, "%04x ", i);
+ for (j = 0; j < 16 && i + j < size; j++) {
+ fprintf(stdout, "%02x ", cptr[i + j]);
+ }
+ fprintf(stdout, "\n");
+ }
+}
+
+void rtp_decoder_handle_pkt(u_char *arg,
+ const struct pcap_pkthdr *hdr,
+ const u_char *bytes)
+{
+ rtp_decoder_t dcdr = (rtp_decoder_t)arg;
+ int pktsize;
+ struct timeval delta;
+ int octets_recvd;
+ srtp_err_status_t status;
+ dcdr->frame_nr++;
+
+ if ((dcdr->start_tv.tv_sec == 0) && (dcdr->start_tv.tv_usec == 0)) {
+ dcdr->start_tv = hdr->ts;
+ }
+
+ if (hdr->caplen < dcdr->rtp_offset) {
+ return;
+ }
+ const void *rtp_packet = bytes + dcdr->rtp_offset;
+
+ memcpy((void *)&dcdr->message, rtp_packet, hdr->caplen - dcdr->rtp_offset);
+ pktsize = hdr->caplen - dcdr->rtp_offset;
+ octets_recvd = pktsize;
+
+ if (octets_recvd == -1) {
+ return;
+ }
+
+ /* verify rtp header */
+ if (dcdr->message.header.version != 2) {
+ return;
+ }
+ if (dcdr->srtp_ctx == NULL) {
+ status = rtp_decoder_init_srtp(dcdr, dcdr->message.header.ssrc);
+ if (status) {
+ exit(1);
+ }
+ }
+ status = srtp_unprotect(dcdr->srtp_ctx, &dcdr->message, &octets_recvd);
+ if (status) {
+ return;
+ }
+ timersub(&hdr->ts, &dcdr->start_tv, &delta);
+ fprintf(stdout, "%02ld:%02ld.%06ld\n", delta.tv_sec / 60, delta.tv_sec % 60,
+ (long)delta.tv_usec);
+ hexdump(&dcdr->message, octets_recvd);
+}
+
+void rtp_print_error(srtp_err_status_t status, char *message)
+{
+ // clang-format off
+ fprintf(stderr,
+ "error: %s %d%s\n", message, status,
+ status == srtp_err_status_replay_fail ? " (replay check failed)" :
+ status == srtp_err_status_bad_param ? " (bad param)" :
+ status == srtp_err_status_no_ctx ? " (no context)" :
+ status == srtp_err_status_cipher_fail ? " (cipher failed)" :
+ status == srtp_err_status_key_expired ? " (key expired)" :
+ status == srtp_err_status_auth_fail ? " (auth check failed)" : "");
+ // clang-format on
+}
diff --git a/netwerk/srtp/src/test/rtp_decoder.h b/netwerk/srtp/src/test/rtp_decoder.h
new file mode 100644
index 0000000000..c8c31dd773
--- /dev/null
+++ b/netwerk/srtp/src/test/rtp_decoder.h
@@ -0,0 +1,105 @@
+/*
+ * rtp_decoder.h
+ *
+ * decoder structures and functions for SRTP pcap decoder
+ *
+ * Bernardo Torres <bernardo@torresautomacao.com.br>
+ *
+ * Some structure and code from https://github.com/gteissier/srtp-decrypt
+ *
+ */
+/*
+ *
+ * Copyright (c) 2001-2017 Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 RTP_DECODER_H
+#define RTP_DECODER_H
+
+#include "srtp_priv.h"
+#include "rtp.h"
+
+#define DEFAULT_RTP_OFFSET 42
+
+typedef struct rtp_decoder_ctx_t {
+ srtp_policy_t policy;
+ srtp_ctx_t *srtp_ctx;
+ int rtp_offset;
+ struct timeval start_tv;
+ int frame_nr;
+ rtp_msg_t message;
+} rtp_decoder_ctx_t;
+
+typedef struct rtp_decoder_ctx_t *rtp_decoder_t;
+
+/*
+ * error to string
+ */
+void rtp_print_error(srtp_err_status_t status, char *message);
+
+/*
+ * prints the output of a random buffer in hexadecimal
+ */
+void hexdump(const void *ptr, size_t size);
+
+/*
+ * the function usage() prints an error message describing how this
+ * program should be called, then calls exit()
+ */
+void usage(char *prog_name);
+
+/*
+ * transforms base64 key into octet
+ */
+char *decode_sdes(char *in, char *out);
+
+/*
+ * pcap handling
+ */
+void rtp_decoder_handle_pkt(u_char *arg,
+ const struct pcap_pkthdr *hdr,
+ const u_char *bytes);
+
+rtp_decoder_t rtp_decoder_alloc(void);
+
+void rtp_decoder_dealloc(rtp_decoder_t rtp_ctx);
+
+int rtp_decoder_init(rtp_decoder_t dcdr, srtp_policy_t policy);
+
+srtp_err_status_t rtp_decoder_init_srtp(rtp_decoder_t decoder,
+ unsigned int ssrc);
+
+int rtp_decoder_deinit_srtp(rtp_decoder_t decoder);
+
+#endif /* RTP_DECODER_H */
diff --git a/netwerk/srtp/src/test/rtpw.c b/netwerk/srtp/src/test/rtpw.c
new file mode 100644
index 0000000000..901816e46d
--- /dev/null
+++ b/netwerk/srtp/src/test/rtpw.c
@@ -0,0 +1,701 @@
+/*
+ * rtpw.c
+ *
+ * rtp word sender/receiver
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ *
+ * This app is a simple RTP application intended only for testing
+ * libsrtp. It reads one word at a time from words.txt (or
+ * whatever file is specified as DICT_FILE or with -w), and sends one word out
+ * each USEC_RATE microseconds. Secure RTP protections can be
+ * applied. See the usage() function for more details.
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "getopt_s.h" /* for local getopt() */
+
+#include <stdio.h> /* for printf, fprintf */
+#include <stdlib.h> /* for atoi() */
+#include <errno.h>
+#include <signal.h> /* for signal() */
+
+#include <string.h> /* for strncpy() */
+#include <time.h> /* for usleep() */
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h> /* for close() */
+#elif defined(_MSC_VER)
+#include <io.h> /* for _close() */
+#define close _close
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#elif defined HAVE_WINSOCK2_H
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#define RTPW_USE_WINSOCK2 1
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#include "srtp.h"
+#include "rtp.h"
+#include "util.h"
+
+#define DICT_FILE "words.txt"
+#define USEC_RATE (5e5)
+#define MAX_WORD_LEN 128
+#define ADDR_IS_MULTICAST(a) IN_MULTICAST(htonl(a))
+#define MAX_KEY_LEN 96
+
+#ifndef HAVE_USLEEP
+#ifdef HAVE_WINDOWS_H
+#define usleep(us) Sleep((us) / 1000)
+#else
+#define usleep(us) sleep((us) / 1000000)
+#endif
+#endif
+
+/*
+ * the function usage() prints an error message describing how this
+ * program should be called, then calls exit()
+ */
+
+void usage(char *prog_name);
+
+/*
+ * leave_group(...) de-registers from a multicast group
+ */
+
+void leave_group(int sock, struct ip_mreq mreq, char *name);
+
+/*
+ * setup_signal_handler() sets up a signal handler to trigger
+ * cleanups after an interrupt
+ */
+int setup_signal_handler(char *name);
+
+/*
+ * handle_signal(...) handles interrupt signal to trigger cleanups
+ */
+
+volatile int interrupted = 0;
+
+/*
+ * program_type distinguishes the [s]rtp sender and receiver cases
+ */
+
+typedef enum { sender, receiver, unknown } program_type;
+
+int main(int argc, char *argv[])
+{
+ char *dictfile = DICT_FILE;
+ FILE *dict;
+ char word[MAX_WORD_LEN];
+ int sock, ret;
+ struct in_addr rcvr_addr;
+ struct sockaddr_in name;
+ struct ip_mreq mreq;
+#if BEW
+ struct sockaddr_in local;
+#endif
+ program_type prog_type = unknown;
+ srtp_sec_serv_t sec_servs = sec_serv_none;
+ unsigned char ttl = 5;
+ int c;
+ int key_size = 128;
+ int tag_size = 8;
+ int gcm_on = 0;
+ char *input_key = NULL;
+ int b64_input = 0;
+ char *address = NULL;
+ char key[MAX_KEY_LEN];
+ unsigned short port = 0;
+ rtp_sender_t snd;
+ srtp_policy_t policy;
+ srtp_err_status_t status;
+ int len;
+ int expected_len;
+ int do_list_mods = 0;
+ uint32_t ssrc = 0xdeadbeef; /* ssrc value hardcoded for now */
+#ifdef RTPW_USE_WINSOCK2
+ WORD wVersionRequested = MAKEWORD(2, 0);
+ WSADATA wsaData;
+
+ ret = WSAStartup(wVersionRequested, &wsaData);
+ if (ret != 0) {
+ fprintf(stderr, "error: WSAStartup() failed: %d\n", ret);
+ exit(1);
+ }
+#endif
+
+ memset(&policy, 0x0, sizeof(srtp_policy_t));
+
+ printf("Using %s [0x%x]\n", srtp_get_version_string(), srtp_get_version());
+
+ if (setup_signal_handler(argv[0]) != 0) {
+ exit(1);
+ }
+
+ /* initialize srtp library */
+ status = srtp_init();
+ if (status) {
+ printf("error: srtp initialization failed with error code %d\n",
+ status);
+ exit(1);
+ }
+
+ /* check args */
+ while (1) {
+ c = getopt_s(argc, argv, "b:k:rsgt:ae:ld:w:");
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'b':
+ b64_input = 1;
+ /* fall thru */
+ case 'k':
+ input_key = optarg_s;
+ break;
+ case 'e':
+ key_size = atoi(optarg_s);
+ if (key_size != 128 && key_size != 256) {
+ printf("error: encryption key size must be 128 or 256 (%d)\n",
+ key_size);
+ exit(1);
+ }
+ sec_servs |= sec_serv_conf;
+ break;
+ case 't':
+ tag_size = atoi(optarg_s);
+ if (tag_size != 8 && tag_size != 16) {
+ printf("error: GCM tag size must be 8 or 16 (%d)\n", tag_size);
+ exit(1);
+ }
+ break;
+ case 'a':
+ sec_servs |= sec_serv_auth;
+ break;
+ case 'g':
+ gcm_on = 1;
+ sec_servs |= sec_serv_auth;
+ break;
+ case 'r':
+ prog_type = receiver;
+ break;
+ case 's':
+ prog_type = sender;
+ break;
+ case 'd':
+ status = srtp_set_debug_module(optarg_s, 1);
+ if (status) {
+ printf("error: set debug module (%s) failed\n", optarg_s);
+ exit(1);
+ }
+ break;
+ case 'l':
+ do_list_mods = 1;
+ break;
+ case 'w':
+ dictfile = optarg_s;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (prog_type == unknown) {
+ if (do_list_mods) {
+ status = srtp_list_debug_modules();
+ if (status) {
+ printf("error: list of debug modules failed\n");
+ exit(1);
+ }
+ return 0;
+ } else {
+ printf("error: neither sender [-s] nor receiver [-r] specified\n");
+ usage(argv[0]);
+ }
+ }
+
+ if ((sec_servs && !input_key) || (!sec_servs && input_key)) {
+ /*
+ * a key must be provided if and only if security services have
+ * been requested
+ */
+ usage(argv[0]);
+ }
+
+ if (argc != optind_s + 2) {
+ /* wrong number of arguments */
+ usage(argv[0]);
+ }
+
+ /* get address from arg */
+ address = argv[optind_s++];
+
+ /* get port from arg */
+ port = atoi(argv[optind_s++]);
+
+/* set address */
+#ifdef HAVE_INET_ATON
+ if (0 == inet_aton(address, &rcvr_addr)) {
+ fprintf(stderr, "%s: cannot parse IP v4 address %s\n", argv[0],
+ address);
+ exit(1);
+ }
+ if (rcvr_addr.s_addr == INADDR_NONE) {
+ fprintf(stderr, "%s: address error", argv[0]);
+ exit(1);
+ }
+#else
+ rcvr_addr.s_addr = inet_addr(address);
+ if (0xffffffff == rcvr_addr.s_addr) {
+ fprintf(stderr, "%s: cannot parse IP v4 address %s\n", argv[0],
+ address);
+ exit(1);
+ }
+#endif
+
+ /* open socket */
+ sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock < 0) {
+ int err;
+#ifdef RTPW_USE_WINSOCK2
+ err = WSAGetLastError();
+#else
+ err = errno;
+#endif
+ fprintf(stderr, "%s: couldn't open socket: %d\n", argv[0], err);
+ exit(1);
+ }
+
+ memset(&name, 0, sizeof(struct sockaddr_in));
+ name.sin_addr = rcvr_addr;
+ name.sin_family = PF_INET;
+ name.sin_port = htons(port);
+
+ if (ADDR_IS_MULTICAST(rcvr_addr.s_addr)) {
+ if (prog_type == sender) {
+ ret = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,
+ sizeof(ttl));
+ if (ret < 0) {
+ fprintf(stderr, "%s: Failed to set TTL for multicast group",
+ argv[0]);
+ perror("");
+ exit(1);
+ }
+ }
+
+ mreq.imr_multiaddr.s_addr = rcvr_addr.s_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ ret = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&mreq,
+ sizeof(mreq));
+ if (ret < 0) {
+ fprintf(stderr, "%s: Failed to join multicast group", argv[0]);
+ perror("");
+ exit(1);
+ }
+ }
+
+ /* report security services selected on the command line */
+ printf("security services: ");
+ if (sec_servs & sec_serv_conf)
+ printf("confidentiality ");
+ if (sec_servs & sec_serv_auth)
+ printf("message authentication");
+ if (sec_servs == sec_serv_none)
+ printf("none");
+ printf("\n");
+
+ /* set up the srtp policy and master key */
+ if (sec_servs) {
+ /*
+ * create policy structure, using the default mechanisms but
+ * with only the security services requested on the command line,
+ * using the right SSRC value
+ */
+ switch (sec_servs) {
+ case sec_serv_conf_and_auth:
+ if (gcm_on) {
+#ifdef GCM
+ switch (key_size) {
+ case 128:
+ srtp_crypto_policy_set_aes_gcm_128_8_auth(&policy.rtp);
+ srtp_crypto_policy_set_aes_gcm_128_8_auth(&policy.rtcp);
+ break;
+ case 256:
+ srtp_crypto_policy_set_aes_gcm_256_8_auth(&policy.rtp);
+ srtp_crypto_policy_set_aes_gcm_256_8_auth(&policy.rtcp);
+ break;
+ }
+#else
+ printf("error: GCM mode only supported when using the OpenSSL "
+ "or NSS crypto engine.\n");
+ return 0;
+#endif
+ } else {
+ switch (key_size) {
+ case 128:
+ srtp_crypto_policy_set_rtp_default(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ break;
+ case 256:
+ srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ break;
+ }
+ }
+ break;
+ case sec_serv_conf:
+ if (gcm_on) {
+ printf(
+ "error: GCM mode must always be used with auth enabled\n");
+ return -1;
+ } else {
+ switch (key_size) {
+ case 128:
+ srtp_crypto_policy_set_aes_cm_128_null_auth(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ break;
+ case 256:
+ srtp_crypto_policy_set_aes_cm_256_null_auth(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ break;
+ }
+ }
+ break;
+ case sec_serv_auth:
+ if (gcm_on) {
+#ifdef GCM
+ switch (key_size) {
+ case 128:
+ srtp_crypto_policy_set_aes_gcm_128_8_only_auth(&policy.rtp);
+ srtp_crypto_policy_set_aes_gcm_128_8_only_auth(
+ &policy.rtcp);
+ break;
+ case 256:
+ srtp_crypto_policy_set_aes_gcm_256_8_only_auth(&policy.rtp);
+ srtp_crypto_policy_set_aes_gcm_256_8_only_auth(
+ &policy.rtcp);
+ break;
+ }
+#else
+ printf("error: GCM mode only supported when using the OpenSSL "
+ "crypto engine.\n");
+ return 0;
+#endif
+ } else {
+ srtp_crypto_policy_set_null_cipher_hmac_sha1_80(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ }
+ break;
+ default:
+ printf("error: unknown security service requested\n");
+ return -1;
+ }
+ policy.ssrc.type = ssrc_specific;
+ policy.ssrc.value = ssrc;
+ policy.key = (uint8_t *)key;
+ policy.ekt = NULL;
+ policy.next = NULL;
+ policy.window_size = 128;
+ policy.allow_repeat_tx = 0;
+ policy.rtp.sec_serv = sec_servs;
+ policy.rtcp.sec_serv = sec_serv_none; /* we don't do RTCP anyway */
+
+ if (gcm_on && tag_size != 8) {
+ policy.rtp.auth_tag_len = tag_size;
+ }
+
+ /*
+ * read key from hexadecimal or base64 on command line into an octet
+ * string
+ */
+ if (b64_input) {
+ int pad;
+ expected_len = (policy.rtp.cipher_key_len * 4) / 3;
+ len = base64_string_to_octet_string(key, &pad, input_key,
+ expected_len);
+ if (pad != 0) {
+ fprintf(stderr, "error: padding in base64 unexpected\n");
+ exit(1);
+ }
+ } else {
+ expected_len = policy.rtp.cipher_key_len * 2;
+ len = hex_string_to_octet_string(key, input_key, expected_len);
+ }
+ /* check that hex string is the right length */
+ if (len < expected_len) {
+ fprintf(stderr, "error: too few digits in key/salt "
+ "(should be %d digits, found %d)\n",
+ expected_len, len);
+ exit(1);
+ }
+ if ((int)strlen(input_key) > policy.rtp.cipher_key_len * 2) {
+ fprintf(stderr, "error: too many digits in key/salt "
+ "(should be %d hexadecimal digits, found %u)\n",
+ policy.rtp.cipher_key_len * 2, (unsigned)strlen(input_key));
+ exit(1);
+ }
+
+ printf("set master key/salt to %s/", octet_string_hex_string(key, 16));
+ printf("%s\n", octet_string_hex_string(key + 16, 14));
+
+ } else {
+ /*
+ * we're not providing security services, so set the policy to the
+ * null policy
+ *
+ * Note that this policy does not conform to the SRTP
+ * specification, since RTCP authentication is required. However,
+ * the effect of this policy is to turn off SRTP, so that this
+ * application is now a vanilla-flavored RTP application.
+ */
+ srtp_crypto_policy_set_null_cipher_hmac_null(&policy.rtp);
+ srtp_crypto_policy_set_null_cipher_hmac_null(&policy.rtcp);
+ policy.key = (uint8_t *)key;
+ policy.ssrc.type = ssrc_specific;
+ policy.ssrc.value = ssrc;
+ policy.window_size = 0;
+ policy.allow_repeat_tx = 0;
+ policy.ekt = NULL;
+ policy.next = NULL;
+ }
+
+ if (prog_type == sender) {
+#if BEW
+ /* bind to local socket (to match crypto policy, if need be) */
+ memset(&local, 0, sizeof(struct sockaddr_in));
+ local.sin_addr.s_addr = htonl(INADDR_ANY);
+ local.sin_port = htons(port);
+ ret = bind(sock, (struct sockaddr *)&local, sizeof(struct sockaddr_in));
+ if (ret < 0) {
+ fprintf(stderr, "%s: bind failed\n", argv[0]);
+ perror("");
+ exit(1);
+ }
+#endif /* BEW */
+
+ /* initialize sender's rtp and srtp contexts */
+ snd = rtp_sender_alloc();
+ if (snd == NULL) {
+ fprintf(stderr, "error: malloc() failed\n");
+ exit(1);
+ }
+ rtp_sender_init(snd, sock, name, ssrc);
+ status = rtp_sender_init_srtp(snd, &policy);
+ if (status) {
+ fprintf(stderr, "error: srtp_create() failed with code %d\n",
+ status);
+ exit(1);
+ }
+
+ /* open dictionary */
+ dict = fopen(dictfile, "r");
+ if (dict == NULL) {
+ fprintf(stderr, "%s: couldn't open file %s\n", argv[0], dictfile);
+ if (ADDR_IS_MULTICAST(rcvr_addr.s_addr)) {
+ leave_group(sock, mreq, argv[0]);
+ }
+ exit(1);
+ }
+
+ /* read words from dictionary, then send them off */
+ while (!interrupted && fgets(word, MAX_WORD_LEN, dict) != NULL) {
+ len = strlen(word) + 1; /* plus one for null */
+
+ if (len > MAX_WORD_LEN)
+ printf("error: word %s too large to send\n", word);
+ else {
+ rtp_sendto(snd, word, len);
+ printf("sending word: %s", word);
+ }
+ usleep(USEC_RATE);
+ }
+
+ rtp_sender_deinit_srtp(snd);
+ rtp_sender_dealloc(snd);
+
+ fclose(dict);
+ } else { /* prog_type == receiver */
+ rtp_receiver_t rcvr;
+
+ if (bind(sock, (struct sockaddr *)&name, sizeof(name)) < 0) {
+ close(sock);
+ fprintf(stderr, "%s: socket bind error\n", argv[0]);
+ perror(NULL);
+ if (ADDR_IS_MULTICAST(rcvr_addr.s_addr)) {
+ leave_group(sock, mreq, argv[0]);
+ }
+ exit(1);
+ }
+
+ rcvr = rtp_receiver_alloc();
+ if (rcvr == NULL) {
+ fprintf(stderr, "error: malloc() failed\n");
+ exit(1);
+ }
+ rtp_receiver_init(rcvr, sock, name, ssrc);
+ status = rtp_receiver_init_srtp(rcvr, &policy);
+ if (status) {
+ fprintf(stderr, "error: srtp_create() failed with code %d\n",
+ status);
+ exit(1);
+ }
+
+ /* get next word and loop */
+ while (!interrupted) {
+ len = MAX_WORD_LEN;
+ if (rtp_recvfrom(rcvr, word, &len) > -1)
+ printf("\tword: %s\n", word);
+ }
+
+ rtp_receiver_deinit_srtp(rcvr);
+ rtp_receiver_dealloc(rcvr);
+ }
+
+ if (ADDR_IS_MULTICAST(rcvr_addr.s_addr)) {
+ leave_group(sock, mreq, argv[0]);
+ }
+
+#ifdef RTPW_USE_WINSOCK2
+ ret = closesocket(sock);
+#else
+ ret = close(sock);
+#endif
+ if (ret < 0) {
+ fprintf(stderr, "%s: Failed to close socket", argv[0]);
+ perror("");
+ }
+
+ status = srtp_shutdown();
+ if (status) {
+ printf("error: srtp shutdown failed with error code %d\n", status);
+ exit(1);
+ }
+
+#ifdef RTPW_USE_WINSOCK2
+ WSACleanup();
+#endif
+
+ return 0;
+}
+
+void usage(char *string)
+{
+ printf("usage: %s [-d <debug>]* [-k <key> [-a][-e]] "
+ "[-s | -r] dest_ip dest_port\n"
+ "or %s -l\n"
+ "where -a use message authentication\n"
+ " -e <key size> use encryption (use 128 or 256 for key size)\n"
+ " -g Use AES-GCM mode (must be used with -e)\n"
+ " -t <tag size> Tag size to use in GCM mode (use 8 or 16)\n"
+ " -k <key> sets the srtp master key given in hexadecimal\n"
+ " -b <key> sets the srtp master key given in base64\n"
+ " -s act as rtp sender\n"
+ " -r act as rtp receiver\n"
+ " -l list debug modules\n"
+ " -d <debug> turn on debugging for module <debug>\n"
+ " -w <wordsfile> use <wordsfile> for input, rather than %s\n",
+ string, string, DICT_FILE);
+ exit(1);
+}
+
+void leave_group(int sock, struct ip_mreq mreq, char *name)
+{
+ int ret;
+
+ ret = setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (void *)&mreq,
+ sizeof(mreq));
+ if (ret < 0) {
+ fprintf(stderr, "%s: Failed to leave multicast group", name);
+ perror("");
+ }
+}
+
+void handle_signal(int signum)
+{
+ interrupted = 1;
+ /* Reset handler explicitly, in case we don't have sigaction() (and signal()
+ has BSD semantics), or we don't have SA_RESETHAND */
+ signal(signum, SIG_DFL);
+}
+
+int setup_signal_handler(char *name)
+{
+#if HAVE_SIGACTION
+ struct sigaction act;
+ memset(&act, 0, sizeof(act));
+
+ act.sa_handler = handle_signal;
+ sigemptyset(&act.sa_mask);
+#if defined(SA_RESETHAND)
+ act.sa_flags = SA_RESETHAND;
+#else
+ act.sa_flags = 0;
+#endif
+ /* Note that we're not setting SA_RESTART; we want recvfrom to return
+ * EINTR when we signal the receiver. */
+
+ if (sigaction(SIGTERM, &act, NULL) != 0) {
+ fprintf(stderr, "%s: error setting up signal handler", name);
+ perror("");
+ return -1;
+ }
+#else
+ if (signal(SIGTERM, handle_signal) == SIG_ERR) {
+ fprintf(stderr, "%s: error setting up signal handler", name);
+ perror("");
+ return -1;
+ }
+#endif
+ return 0;
+}
diff --git a/netwerk/srtp/src/test/rtpw_test.sh b/netwerk/srtp/src/test/rtpw_test.sh
new file mode 100755
index 0000000000..158a39312d
--- /dev/null
+++ b/netwerk/srtp/src/test/rtpw_test.sh
@@ -0,0 +1,176 @@
+#!/bin/sh
+#
+# usage: rtpw_test <rtpw_commands>
+#
+# tests the rtpw sender and receiver functions
+#
+# Copyright (c) 2001-2017, Cisco Systems, Inc.
+# 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 the 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 HOLDERS 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.
+#
+
+case $(uname -s) in
+ *CYGWIN*|*MINGW*)
+ EXE=".exe"
+ ;;
+ *Linux*)
+ EXE=""
+ export LD_LIBRARY_PATH=$CRYPTO_LIBDIR
+ ;;
+ *Darwin*)
+ EXE=""
+ export DYLD_LIBRARY_PATH=$CRYPTO_LIBDIR
+ ;;
+esac
+
+RTPW=./rtpw$EXE
+DEST_PORT=9999
+DURATION=3
+
+key=Ky7cUDT2GnI0XKWYbXv9AYmqbcLsqzL9mvdN9t/G
+
+ARGS="-b $key -a -e 128"
+
+# First, we run "killall" to get rid of all existing rtpw processes.
+# This step also enables this script to clean up after itself; if this
+# script is interrupted after the rtpw processes are started but before
+# they are killed, those processes will linger. Re-running the script
+# will get rid of them.
+
+killall rtpw 2>/dev/null
+
+if test -x $RTPW; then
+
+echo $0 ": starting rtpw receiver process... "
+
+$RTPW $* $ARGS -r 0.0.0.0 $DEST_PORT &
+
+receiver_pid=$!
+
+echo $0 ": receiver PID = $receiver_pid"
+
+sleep 1
+
+# verify that the background job is running
+ps -e | grep -q $receiver_pid
+retval=$?
+echo $retval
+if [ $retval != 0 ]; then
+ echo $0 ": error"
+ exit 254
+fi
+
+echo $0 ": starting rtpw sender process..."
+
+$RTPW $* $ARGS -s 127.0.0.1 $DEST_PORT &
+
+sender_pid=$!
+
+echo $0 ": sender PID = $sender_pid"
+
+# verify that the background job is running
+ps -e | grep -q $sender_pid
+retval=$?
+echo $retval
+if [ $retval != 0 ]; then
+ echo $0 ": error"
+ exit 255
+fi
+
+sleep $DURATION
+
+kill $receiver_pid
+kill $sender_pid
+
+wait $receiver_pid 2>/dev/null
+wait $sender_pid 2>/dev/null
+
+
+key=033490ba9e82994fc21013395739038992b2edc5034f61a72345ca598d7bfd0189aa6dc2ecab32fd9af74df6dfc6
+
+ARGS="-k $key -a -e 256"
+
+echo $0 ": starting rtpw receiver process... "
+
+$RTPW $* $ARGS -r 0.0.0.0 $DEST_PORT &
+
+receiver_pid=$!
+
+echo $0 ": receiver PID = $receiver_pid"
+
+sleep 1
+
+# verify that the background job is running
+ps -e | grep -q $receiver_pid
+retval=$?
+echo $retval
+if [ $retval != 0 ]; then
+ echo $0 ": error"
+ exit 254
+fi
+
+echo $0 ": starting rtpw sender process..."
+
+$RTPW $* $ARGS -s 127.0.0.1 $DEST_PORT &
+
+sender_pid=$!
+
+echo $0 ": sender PID = $sender_pid"
+
+# verify that the background job is running
+ps -e | grep -q $sender_pid
+retval=$?
+echo $retval
+if [ $retval != 0 ]; then
+ echo $0 ": error"
+ exit 255
+fi
+
+sleep $DURATION
+
+kill $receiver_pid
+kill $sender_pid
+
+wait $receiver_pid 2>/dev/null
+wait $sender_pid 2>/dev/null
+
+echo $0 ": done (test passed)"
+
+else
+
+echo "error: can't find executable" $RTPW
+exit 1
+
+fi
+
+# EOF
+
+
diff --git a/netwerk/srtp/src/test/rtpw_test_gcm.sh b/netwerk/srtp/src/test/rtpw_test_gcm.sh
new file mode 100755
index 0000000000..644255e1a1
--- /dev/null
+++ b/netwerk/srtp/src/test/rtpw_test_gcm.sh
@@ -0,0 +1,260 @@
+#!/bin/sh
+#
+# usage: rtpw_test <rtpw_commands>
+#
+# tests the rtpw sender and receiver functions
+#
+# Copyright (c) 2001-2017, Cisco Systems, Inc.
+# 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 the 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 HOLDERS 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.
+#
+
+case $(uname -s) in
+ *CYGWIN*|*MINGW*)
+ EXE=".exe"
+ ;;
+ *Linux*)
+ EXE=""
+ export LD_LIBRARY_PATH=$CRYPTO_LIBDIR
+ ;;
+ *Darwin*)
+ EXE=""
+ export DYLD_LIBRARY_PATH=$CRYPTO_LIBDIR
+ ;;
+esac
+
+RTPW=./rtpw$EXE
+DEST_PORT=9999
+DURATION=3
+
+# First, we run "killall" to get rid of all existing rtpw processes.
+# This step also enables this script to clean up after itself; if this
+# script is interrupted after the rtpw processes are started but before
+# they are killed, those processes will linger. Re-running the script
+# will get rid of them.
+
+killall rtpw 2>/dev/null
+
+if test -x $RTPW; then
+
+GCMARGS128="-k 01234567890123456789012345678901234567890123456789012345 -g -e 128"
+echo $0 ": starting GCM mode 128-bit rtpw receiver process... "
+
+exec $RTPW $* $GCMARGS128 -r 127.0.0.1 $DEST_PORT &
+
+receiver_pid=$!
+
+echo $0 ": receiver PID = $receiver_pid"
+
+sleep 1
+
+# verify that the background job is running
+ps -e | grep -q $receiver_pid
+retval=$?
+echo $retval
+if [ $retval != 0 ]; then
+ echo $0 ": error"
+ exit 254
+fi
+
+echo $0 ": starting GCM 128-bit rtpw sender process..."
+
+exec $RTPW $* $GCMARGS128 -s 127.0.0.1 $DEST_PORT &
+
+sender_pid=$!
+
+echo $0 ": sender PID = $sender_pid"
+
+# verify that the background job is running
+ps -e | grep -q $sender_pid
+retval=$?
+echo $retval
+if [ $retval != 0 ]; then
+ echo $0 ": error"
+ exit 255
+fi
+
+sleep $DURATION
+
+kill $receiver_pid
+kill $sender_pid
+
+wait $receiver_pid 2>/dev/null
+wait $sender_pid 2>/dev/null
+
+GCMARGS128="-k 01234567890123456789012345678901234567890123456789012345 -g -t 16 -e 128"
+echo $0 ": starting GCM mode 128-bit (16 byte tag) rtpw receiver process... "
+
+exec $RTPW $* $GCMARGS128 -r 127.0.0.1 $DEST_PORT &
+
+receiver_pid=$!
+
+echo $0 ": receiver PID = $receiver_pid"
+
+sleep 1
+
+# verify that the background job is running
+ps -e | grep -q $receiver_pid
+retval=$?
+echo $retval
+if [ $retval != 0 ]; then
+ echo $0 ": error"
+ exit 254
+fi
+
+echo $0 ": starting GCM 128-bit (16 byte tag) rtpw sender process..."
+
+exec $RTPW $* $GCMARGS128 -s 127.0.0.1 $DEST_PORT &
+
+sender_pid=$!
+
+echo $0 ": sender PID = $sender_pid"
+
+# verify that the background job is running
+ps -e | grep -q $sender_pid
+retval=$?
+echo $retval
+if [ $retval != 0 ]; then
+ echo $0 ": error"
+ exit 255
+fi
+
+sleep $DURATION
+
+kill $receiver_pid
+kill $sender_pid
+
+wait $receiver_pid 2>/dev/null
+wait $sender_pid 2>/dev/null
+
+
+GCMARGS256="-k 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567 -g -e 256"
+echo $0 ": starting GCM mode 256-bit rtpw receiver process... "
+
+exec $RTPW $* $GCMARGS256 -r 127.0.0.1 $DEST_PORT &
+
+receiver_pid=$!
+
+echo $0 ": receiver PID = $receiver_pid"
+
+sleep 1
+
+# verify that the background job is running
+ps -e | grep -q $receiver_pid
+retval=$?
+echo $retval
+if [ $retval != 0 ]; then
+ echo $0 ": error"
+ exit 254
+fi
+
+echo $0 ": starting GCM 256-bit rtpw sender process..."
+
+exec $RTPW $* $GCMARGS256 -s 127.0.0.1 $DEST_PORT &
+
+sender_pid=$!
+
+echo $0 ": sender PID = $sender_pid"
+
+# verify that the background job is running
+ps -e | grep -q $sender_pid
+retval=$?
+echo $retval
+if [ $retval != 0 ]; then
+ echo $0 ": error"
+ exit 255
+fi
+
+sleep $DURATION
+
+kill $receiver_pid
+kill $sender_pid
+
+wait $receiver_pid 2>/dev/null
+wait $sender_pid 2>/dev/null
+
+GCMARGS256="-k a123456789012345678901234567890123456789012345678901234567890123456789012345678901234567 -g -t 16 -e 256"
+echo $0 ": starting GCM mode 256-bit (16 byte tag) rtpw receiver process... "
+
+exec $RTPW $* $GCMARGS256 -r 127.0.0.1 $DEST_PORT &
+
+receiver_pid=$!
+
+echo $0 ": receiver PID = $receiver_pid"
+
+sleep 1
+
+# verify that the background job is running
+ps -e | grep -q $receiver_pid
+retval=$?
+echo $retval
+if [ $retval != 0 ]; then
+ echo $0 ": error"
+ exit 254
+fi
+
+echo $0 ": starting GCM 256-bit (16 byte tag) rtpw sender process..."
+
+exec $RTPW $* $GCMARGS256 -s 127.0.0.1 $DEST_PORT &
+
+sender_pid=$!
+
+echo $0 ": sender PID = $sender_pid"
+
+# verify that the background job is running
+ps -e | grep -q $sender_pid
+retval=$?
+echo $retval
+if [ $retval != 0 ]; then
+ echo $0 ": error"
+ exit 255
+fi
+
+sleep $DURATION
+
+kill $receiver_pid
+kill $sender_pid
+
+wait $receiver_pid 2>/dev/null
+wait $sender_pid 2>/dev/null
+
+echo $0 ": done (test passed)"
+
+else
+
+echo "error: can't find executable" $RTPW
+exit 1
+
+fi
+
+# EOF
+
+
diff --git a/netwerk/srtp/src/test/srtp_driver.c b/netwerk/srtp/src/test/srtp_driver.c
new file mode 100644
index 0000000000..a7234539f7
--- /dev/null
+++ b/netwerk/srtp/src/test/srtp_driver.c
@@ -0,0 +1,3837 @@
+/*
+ * srtp_driver.c
+ *
+ * a test driver for libSRTP
+ *
+ * David A. McGrew
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2001-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 <string.h> /* for memcpy() */
+#include <time.h> /* for clock() */
+#include <stdlib.h> /* for malloc(), free() */
+#include <stdio.h> /* for print(), fflush() */
+#include "getopt_s.h" /* for local getopt() */
+
+#include "srtp_priv.h"
+#include "util.h"
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#elif defined HAVE_WINSOCK2_H
+#include <winsock2.h>
+#endif
+
+#define PRINT_REFERENCE_PACKET 1
+
+srtp_err_status_t srtp_validate(void);
+
+#ifdef GCM
+srtp_err_status_t srtp_validate_gcm(void);
+#endif
+
+srtp_err_status_t srtp_validate_encrypted_extensions_headers(void);
+
+#ifdef GCM
+srtp_err_status_t srtp_validate_encrypted_extensions_headers_gcm(void);
+#endif
+
+srtp_err_status_t srtp_validate_aes_256(void);
+
+srtp_err_status_t srtp_create_big_policy(srtp_policy_t **list);
+
+srtp_err_status_t srtp_dealloc_big_policy(srtp_policy_t *list);
+
+srtp_err_status_t srtp_test_empty_payload(void);
+
+#ifdef GCM
+srtp_err_status_t srtp_test_empty_payload_gcm(void);
+#endif
+
+srtp_err_status_t srtp_test_remove_stream(void);
+
+srtp_err_status_t srtp_test_update(void);
+
+srtp_err_status_t srtp_test_protect_trailer_length(void);
+
+srtp_err_status_t srtp_test_protect_rtcp_trailer_length(void);
+
+srtp_err_status_t srtp_test_get_roc(void);
+
+srtp_err_status_t srtp_test_set_receiver_roc(void);
+
+srtp_err_status_t srtp_test_set_sender_roc(void);
+
+double srtp_bits_per_second(int msg_len_octets, const srtp_policy_t *policy);
+
+double srtp_rejections_per_second(int msg_len_octets,
+ const srtp_policy_t *policy);
+
+void srtp_do_timing(const srtp_policy_t *policy);
+
+void srtp_do_rejection_timing(const srtp_policy_t *policy);
+
+srtp_err_status_t srtp_test(const srtp_policy_t *policy,
+ int extension_header,
+ int mki_index);
+
+srtp_err_status_t srtcp_test(const srtp_policy_t *policy, int mki_index);
+
+srtp_err_status_t srtp_session_print_policy(srtp_t srtp);
+
+srtp_err_status_t srtp_print_policy(const srtp_policy_t *policy);
+
+char *srtp_packet_to_string(srtp_hdr_t *hdr, int packet_len);
+
+double mips_estimate(int num_trials, int *ignore);
+
+#define TEST_MKI_ID_SIZE 4
+
+extern uint8_t test_key[46];
+extern uint8_t test_key_2[46];
+extern uint8_t test_mki_id[TEST_MKI_ID_SIZE];
+extern uint8_t test_mki_id_2[TEST_MKI_ID_SIZE];
+
+// clang-format off
+srtp_master_key_t master_key_1 = {
+ test_key,
+ test_mki_id,
+ TEST_MKI_ID_SIZE
+};
+
+srtp_master_key_t master_key_2 = {
+ test_key_2,
+ test_mki_id_2,
+ TEST_MKI_ID_SIZE
+};
+
+srtp_master_key_t *test_keys[2] = {
+ &master_key_1,
+ &master_key_2
+};
+// clang-format on
+
+void usage(char *prog_name)
+{
+ printf("usage: %s [ -t ][ -c ][ -v ][ -o ][-d <debug_module> ]* [ -l ]\n"
+ " -t run timing test\n"
+ " -r run rejection timing test\n"
+ " -c run codec timing test\n"
+ " -v run validation tests\n"
+ " -o output logging to stdout\n"
+ " -d <mod> turn on debugging module <mod>\n"
+ " -l list debugging modules\n",
+ prog_name);
+ exit(1);
+}
+
+void log_handler(srtp_log_level_t level, const char *msg, void *data)
+{
+ char level_char = '?';
+ switch (level) {
+ case srtp_log_level_error:
+ level_char = 'e';
+ break;
+ case srtp_log_level_warning:
+ level_char = 'w';
+ break;
+ case srtp_log_level_info:
+ level_char = 'i';
+ break;
+ case srtp_log_level_debug:
+ level_char = 'd';
+ break;
+ }
+ printf("SRTP-LOG [%c]: %s\n", level_char, msg);
+}
+
+/*
+ * The policy_array is a null-terminated array of policy structs. it
+ * is declared at the end of this file
+ */
+
+extern const srtp_policy_t *policy_array[];
+
+/* the wildcard_policy is declared below; it has a wildcard ssrc */
+
+extern const srtp_policy_t wildcard_policy;
+
+/*
+ * mod_driver debug module - debugging module for this test driver
+ *
+ * we use the crypto_kernel debugging system in this driver, which
+ * makes the interface uniform and increases portability
+ */
+
+srtp_debug_module_t mod_driver = {
+ 0, /* debugging is off by default */
+ "driver" /* printable name for module */
+};
+
+int main(int argc, char *argv[])
+{
+ int q;
+ unsigned do_timing_test = 0;
+ unsigned do_rejection_test = 0;
+ unsigned do_codec_timing = 0;
+ unsigned do_validation = 0;
+ unsigned do_list_mods = 0;
+ unsigned do_log_stdout = 0;
+ srtp_err_status_t status;
+
+ /*
+ * verify that the compiler has interpreted the header data
+ * structure srtp_hdr_t correctly
+ */
+ if (sizeof(srtp_hdr_t) != 12) {
+ printf("error: srtp_hdr_t has incorrect size"
+ "(size is %ld bytes, expected 12)\n",
+ (long)sizeof(srtp_hdr_t));
+ exit(1);
+ }
+
+ /* initialize srtp library */
+ status = srtp_init();
+ if (status) {
+ printf("error: srtp init failed with error code %d\n", status);
+ exit(1);
+ }
+
+ /* load srtp_driver debug module */
+ status = srtp_crypto_kernel_load_debug_module(&mod_driver);
+ if (status) {
+ printf("error: load of srtp_driver debug module failed "
+ "with error code %d\n",
+ status);
+ exit(1);
+ }
+
+ /* process input arguments */
+ while (1) {
+ q = getopt_s(argc, argv, "trcvold:");
+ if (q == -1) {
+ break;
+ }
+ switch (q) {
+ case 't':
+ do_timing_test = 1;
+ break;
+ case 'r':
+ do_rejection_test = 1;
+ break;
+ case 'c':
+ do_codec_timing = 1;
+ break;
+ case 'v':
+ do_validation = 1;
+ break;
+ case 'o':
+ do_log_stdout = 1;
+ break;
+ case 'l':
+ do_list_mods = 1;
+ break;
+ case 'd':
+ status = srtp_set_debug_module(optarg_s, 1);
+ if (status) {
+ printf("error: set debug module (%s) failed\n", optarg_s);
+ exit(1);
+ }
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ if (!do_validation && !do_timing_test && !do_codec_timing &&
+ !do_list_mods && !do_rejection_test) {
+ usage(argv[0]);
+ }
+
+ if (do_log_stdout) {
+ status = srtp_install_log_handler(log_handler, NULL);
+ if (status) {
+ printf("error: install log handler failed\n");
+ exit(1);
+ }
+ }
+
+ if (do_list_mods) {
+ status = srtp_list_debug_modules();
+ if (status) {
+ printf("error: list of debug modules failed\n");
+ exit(1);
+ }
+ }
+
+ if (do_validation) {
+ const srtp_policy_t **policy = policy_array;
+ srtp_policy_t *big_policy;
+
+ /* loop over policy array, testing srtp and srtcp for each policy */
+ while (*policy != NULL) {
+ printf("testing srtp_protect and srtp_unprotect\n");
+ if (srtp_test(*policy, 0, -1) == srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+
+ printf("testing srtp_protect and srtp_unprotect with encrypted "
+ "extensions headers\n");
+ if (srtp_test(*policy, 1, -1) == srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+ printf("testing srtp_protect_rtcp and srtp_unprotect_rtcp\n");
+ if (srtcp_test(*policy, -1) == srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+ printf("testing srtp_protect_rtp and srtp_unprotect_rtp with MKI "
+ "index set to 0\n");
+ if (srtp_test(*policy, 0, 0) == srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+ printf("testing srtp_protect_rtp and srtp_unprotect_rtp with MKI "
+ "index set to 1\n");
+ if (srtp_test(*policy, 0, 1) == srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+
+ printf("testing srtp_protect_rtcp and srtp_unprotect_rtcp with MKI "
+ "index set to 0\n");
+ if (srtcp_test(*policy, 0) == srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+ printf("testing srtp_protect_rtcp and srtp_unprotect_rtcp with MKI "
+ "index set to 1\n");
+ if (srtcp_test(*policy, 1) == srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+ policy++;
+ }
+
+ /* create a big policy list and run tests on it */
+ status = srtp_create_big_policy(&big_policy);
+ if (status) {
+ printf("unexpected failure with error code %d\n", status);
+ exit(1);
+ }
+ printf("testing srtp_protect and srtp_unprotect with big policy\n");
+ if (srtp_test(big_policy, 0, -1) == srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+ printf("testing srtp_protect and srtp_unprotect with big policy and "
+ "encrypted extensions headers\n");
+ if (srtp_test(big_policy, 1, -1) == srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+ status = srtp_dealloc_big_policy(big_policy);
+ if (status) {
+ printf("unexpected failure with error code %d\n", status);
+ exit(1);
+ }
+
+ /* run test on wildcard policy */
+ printf("testing srtp_protect and srtp_unprotect on "
+ "wildcard ssrc policy\n");
+ if (srtp_test(&wildcard_policy, 0, -1) == srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+ printf("testing srtp_protect and srtp_unprotect on "
+ "wildcard ssrc policy and encrypted extensions headers\n");
+ if (srtp_test(&wildcard_policy, 1, -1) == srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+
+ /*
+ * run validation test against the reference packets - note
+ * that this test only covers the default policy
+ */
+ printf("testing srtp_protect and srtp_unprotect against "
+ "reference packet\n");
+ if (srtp_validate() == srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+
+#ifdef GCM
+ printf("testing srtp_protect and srtp_unprotect against "
+ "reference packet using GCM\n");
+ if (srtp_validate_gcm() == srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+#endif
+
+ printf("testing srtp_protect and srtp_unprotect against "
+ "reference packet with encrypted extensions headers\n");
+ if (srtp_validate_encrypted_extensions_headers() == srtp_err_status_ok)
+ printf("passed\n\n");
+ else {
+ printf("failed\n");
+ exit(1);
+ }
+
+#ifdef GCM
+ printf("testing srtp_protect and srtp_unprotect against "
+ "reference packet with encrypted extension headers (GCM)\n");
+ if (srtp_validate_encrypted_extensions_headers_gcm() ==
+ srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+#endif
+
+ /*
+ * run validation test against the reference packets for
+ * AES-256
+ */
+ printf("testing srtp_protect and srtp_unprotect against "
+ "reference packet (AES-256)\n");
+ if (srtp_validate_aes_256() == srtp_err_status_ok) {
+ printf("passed\n\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+
+ /*
+ * test packets with empty payload
+ */
+ printf("testing srtp_protect and srtp_unprotect against "
+ "packet with empty payload\n");
+ if (srtp_test_empty_payload() == srtp_err_status_ok) {
+ printf("passed\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+#ifdef GCM
+ printf("testing srtp_protect and srtp_unprotect against "
+ "packet with empty payload (GCM)\n");
+ if (srtp_test_empty_payload_gcm() == srtp_err_status_ok) {
+ printf("passed\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+#endif
+
+ /*
+ * test the function srtp_remove_stream()
+ */
+ printf("testing srtp_remove_stream()...");
+ if (srtp_test_remove_stream() == srtp_err_status_ok) {
+ printf("passed\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+
+ /*
+ * test the function srtp_update()
+ */
+ printf("testing srtp_update()...");
+ if (srtp_test_update() == srtp_err_status_ok) {
+ printf("passed\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+
+ /*
+ * test the functions srtp_get_protect_trailer_length
+ * and srtp_get_protect_rtcp_trailer_length
+ */
+ printf("testing srtp_get_protect_trailer_length()...");
+ if (srtp_test_protect_trailer_length() == srtp_err_status_ok) {
+ printf("passed\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+
+ printf("testing srtp_get_protect_rtcp_trailer_length()...");
+ if (srtp_test_protect_rtcp_trailer_length() == srtp_err_status_ok) {
+ printf("passed\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+
+ printf("testing srtp_test_get_roc()...");
+ if (srtp_test_get_roc() == srtp_err_status_ok) {
+ printf("passed\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+
+ printf("testing srtp_test_set_receiver_roc()...");
+ if (srtp_test_set_receiver_roc() == srtp_err_status_ok) {
+ printf("passed\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+
+ printf("testing srtp_test_set_sender_roc()...");
+ if (srtp_test_set_sender_roc() == srtp_err_status_ok) {
+ printf("passed\n");
+ } else {
+ printf("failed\n");
+ exit(1);
+ }
+ }
+
+ if (do_timing_test) {
+ const srtp_policy_t **policy = policy_array;
+
+ /* loop over policies, run timing test for each */
+ while (*policy != NULL) {
+ srtp_print_policy(*policy);
+ srtp_do_timing(*policy);
+ policy++;
+ }
+ }
+
+ if (do_rejection_test) {
+ const srtp_policy_t **policy = policy_array;
+
+ /* loop over policies, run rejection timing test for each */
+ while (*policy != NULL) {
+ srtp_print_policy(*policy);
+ srtp_do_rejection_timing(*policy);
+ policy++;
+ }
+ }
+
+ if (do_codec_timing) {
+ srtp_policy_t policy;
+ int ignore;
+ double mips_value = mips_estimate(1000000000, &ignore);
+
+ memset(&policy, 0, sizeof(policy));
+ srtp_crypto_policy_set_rtp_default(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ policy.ssrc.type = ssrc_specific;
+ policy.ssrc.value = 0xdecafbad;
+ policy.key = test_key;
+ policy.ekt = NULL;
+ policy.window_size = 128;
+ policy.allow_repeat_tx = 0;
+ policy.next = NULL;
+
+ printf("mips estimate: %e\n", mips_value);
+
+ printf("testing srtp processing time for voice codecs:\n");
+ printf("codec\t\tlength (octets)\t\tsrtp instructions/second\n");
+ printf("G.711\t\t%d\t\t\t%e\n", 80,
+ (double)mips_value * (80 * 8) /
+ srtp_bits_per_second(80, &policy) / .01);
+ printf("G.711\t\t%d\t\t\t%e\n", 160,
+ (double)mips_value * (160 * 8) /
+ srtp_bits_per_second(160, &policy) / .02);
+ printf("G.726-32\t%d\t\t\t%e\n", 40,
+ (double)mips_value * (40 * 8) /
+ srtp_bits_per_second(40, &policy) / .01);
+ printf("G.726-32\t%d\t\t\t%e\n", 80,
+ (double)mips_value * (80 * 8) /
+ srtp_bits_per_second(80, &policy) / .02);
+ printf("G.729\t\t%d\t\t\t%e\n", 10,
+ (double)mips_value * (10 * 8) /
+ srtp_bits_per_second(10, &policy) / .01);
+ printf("G.729\t\t%d\t\t\t%e\n", 20,
+ (double)mips_value * (20 * 8) /
+ srtp_bits_per_second(20, &policy) / .02);
+ printf("Wideband\t%d\t\t\t%e\n", 320,
+ (double)mips_value * (320 * 8) /
+ srtp_bits_per_second(320, &policy) / .01);
+ printf("Wideband\t%d\t\t\t%e\n", 640,
+ (double)mips_value * (640 * 8) /
+ srtp_bits_per_second(640, &policy) / .02);
+ }
+
+ status = srtp_shutdown();
+ if (status) {
+ printf("error: srtp shutdown failed with error code %d\n", status);
+ exit(1);
+ }
+
+ return 0;
+}
+
+/*
+ * srtp_create_test_packet(len, ssrc) returns a pointer to a
+ * (malloced) example RTP packet whose data field has the length given
+ * by pkt_octet_len and the SSRC value ssrc. The total length of the
+ * packet is twelve octets longer, since the header is at the
+ * beginning. There is room at the end of the packet for a trailer,
+ * and the four octets following the packet are filled with 0xff
+ * values to enable testing for overwrites.
+ *
+ * note that the location of the test packet can (and should) be
+ * deallocated with the free() call once it is no longer needed.
+ */
+
+srtp_hdr_t *srtp_create_test_packet(int pkt_octet_len,
+ uint32_t ssrc,
+ int *pkt_len)
+{
+ int i;
+ uint8_t *buffer;
+ srtp_hdr_t *hdr;
+ int bytes_in_hdr = 12;
+
+ /* allocate memory for test packet */
+ hdr = (srtp_hdr_t *)malloc(pkt_octet_len + bytes_in_hdr +
+ SRTP_MAX_TRAILER_LEN + 4);
+ if (!hdr) {
+ return NULL;
+ }
+
+ hdr->version = 2; /* RTP version two */
+ hdr->p = 0; /* no padding needed */
+ hdr->x = 0; /* no header extension */
+ hdr->cc = 0; /* no CSRCs */
+ hdr->m = 0; /* marker bit */
+ hdr->pt = 0xf; /* payload type */
+ hdr->seq = htons(0x1234); /* sequence number */
+ hdr->ts = htonl(0xdecafbad); /* timestamp */
+ hdr->ssrc = htonl(ssrc); /* synch. source */
+
+ buffer = (uint8_t *)hdr;
+ buffer += bytes_in_hdr;
+
+ /* set RTP data to 0xab */
+ for (i = 0; i < pkt_octet_len; i++) {
+ *buffer++ = 0xab;
+ }
+
+ /* set post-data value to 0xffff to enable overrun checking */
+ for (i = 0; i < SRTP_MAX_TRAILER_LEN + 4; i++) {
+ *buffer++ = 0xff;
+ }
+
+ *pkt_len = bytes_in_hdr + pkt_octet_len;
+
+ return hdr;
+}
+
+static srtp_hdr_t *srtp_create_test_packet_extended(int pkt_octet_len,
+ uint32_t ssrc,
+ uint16_t seq,
+ uint32_t ts,
+ int *pkt_len)
+{
+ srtp_hdr_t *hdr;
+
+ hdr = srtp_create_test_packet(pkt_octet_len, ssrc, pkt_len);
+ if (hdr == NULL)
+ return hdr;
+
+ hdr->seq = htons(seq);
+ hdr->ts = htonl(ts);
+ return hdr;
+}
+
+srtp_hdr_t *srtp_create_test_packet_ext_hdr(int pkt_octet_len,
+ uint32_t ssrc,
+ int *pkt_len)
+{
+ int i;
+ uint8_t *buffer;
+ srtp_hdr_t *hdr;
+ int bytes_in_hdr = 12;
+ uint8_t extension_header[12] = { /* one-byte header */
+ 0xbe, 0xde,
+ /* size */
+ 0x00, 0x02,
+ /* id 1, length 1 (i.e. 2 bytes) */
+ 0x11,
+ /* payload */
+ 0xca, 0xfe,
+ /* padding */
+ 0x00,
+ /* id 2, length 0 (i.e. 1 byte) */
+ 0x20,
+ /* payload */
+ 0xba,
+ /* padding */
+ 0x00, 0x00
+ };
+
+ /* allocate memory for test packet */
+ hdr = (srtp_hdr_t *)malloc(pkt_octet_len + bytes_in_hdr +
+ sizeof(extension_header) + SRTP_MAX_TRAILER_LEN +
+ 4);
+ if (!hdr)
+ return NULL;
+
+ hdr->version = 2; /* RTP version two */
+ hdr->p = 0; /* no padding needed */
+ hdr->x = 1; /* no header extension */
+ hdr->cc = 0; /* no CSRCs */
+ hdr->m = 0; /* marker bit */
+ hdr->pt = 0xf; /* payload type */
+ hdr->seq = htons(0x1234); /* sequence number */
+ hdr->ts = htonl(0xdecafbad); /* timestamp */
+ hdr->ssrc = htonl(ssrc); /* synch. source */
+
+ buffer = (uint8_t *)hdr;
+ buffer += bytes_in_hdr;
+
+ memcpy(buffer, extension_header, sizeof(extension_header));
+ buffer += sizeof(extension_header);
+
+ /* set RTP data to 0xab */
+ for (i = 0; i < pkt_octet_len; i++)
+ *buffer++ = 0xab;
+
+ /* set post-data value to 0xffff to enable overrun checking */
+ for (i = 0; i < SRTP_MAX_TRAILER_LEN + 4; i++)
+ *buffer++ = 0xff;
+
+ *pkt_len = bytes_in_hdr + sizeof(extension_header) + pkt_octet_len;
+
+ return hdr;
+}
+
+void srtp_do_timing(const srtp_policy_t *policy)
+{
+ int len;
+
+ /*
+ * note: the output of this function is formatted so that it
+ * can be used in gnuplot. '#' indicates a comment, and "\r\n"
+ * terminates a record
+ */
+
+ printf("# testing srtp throughput:\r\n");
+ printf("# mesg length (octets)\tthroughput (megabits per second)\r\n");
+
+ for (len = 16; len <= 2048; len *= 2) {
+ printf("%d\t\t\t%f\r\n", len,
+ srtp_bits_per_second(len, policy) / 1.0E6);
+ }
+
+ /* these extra linefeeds let gnuplot know that a dataset is done */
+ printf("\r\n\r\n");
+}
+
+void srtp_do_rejection_timing(const srtp_policy_t *policy)
+{
+ int len;
+
+ /*
+ * note: the output of this function is formatted so that it
+ * can be used in gnuplot. '#' indicates a comment, and "\r\n"
+ * terminates a record
+ */
+
+ printf("# testing srtp rejection throughput:\r\n");
+ printf("# mesg length (octets)\trejections per second\r\n");
+
+ for (len = 8; len <= 2048; len *= 2) {
+ printf("%d\t\t\t%e\r\n", len, srtp_rejections_per_second(len, policy));
+ }
+
+ /* these extra linefeeds let gnuplot know that a dataset is done */
+ printf("\r\n\r\n");
+}
+
+#define MAX_MSG_LEN 1024
+
+double srtp_bits_per_second(int msg_len_octets, const srtp_policy_t *policy)
+{
+ srtp_t srtp;
+ srtp_hdr_t *mesg;
+ int i;
+ clock_t timer;
+ int num_trials = 100000;
+ int input_len, len;
+ uint32_t ssrc;
+ srtp_err_status_t status;
+
+ /*
+ * allocate and initialize an srtp session
+ */
+ status = srtp_create(&srtp, policy);
+ if (status) {
+ printf("error: srtp_create() failed with error code %d\n", status);
+ exit(1);
+ }
+
+ /*
+ * if the ssrc is unspecified, use a predetermined one
+ */
+ if (policy->ssrc.type != ssrc_specific) {
+ ssrc = 0xdeadbeef;
+ } else {
+ ssrc = policy->ssrc.value;
+ }
+
+ /*
+ * create a test packet
+ */
+ mesg = srtp_create_test_packet(msg_len_octets, ssrc, &input_len);
+ if (mesg == NULL) {
+ return 0.0; /* indicate failure by returning zero */
+ }
+ timer = clock();
+ for (i = 0; i < num_trials; i++) {
+ len = input_len;
+ /* srtp protect message */
+ status = srtp_protect(srtp, mesg, &len);
+ if (status) {
+ printf("error: srtp_protect() failed with error code %d\n", status);
+ exit(1);
+ }
+
+ /* increment message number */
+ {
+ /* hack sequence to avoid problems with macros for htons/ntohs on
+ * some systems */
+ short new_seq = ntohs(mesg->seq) + 1;
+ mesg->seq = htons(new_seq);
+ }
+ }
+ timer = clock() - timer;
+
+ free(mesg);
+
+ status = srtp_dealloc(srtp);
+ if (status) {
+ printf("error: srtp_dealloc() failed with error code %d\n", status);
+ exit(1);
+ }
+
+ return (double)(msg_len_octets)*8 * num_trials * CLOCKS_PER_SEC / timer;
+}
+
+double srtp_rejections_per_second(int msg_len_octets,
+ const srtp_policy_t *policy)
+{
+ srtp_ctx_t *srtp;
+ srtp_hdr_t *mesg;
+ int i;
+ int len;
+ clock_t timer;
+ int num_trials = 1000000;
+ uint32_t ssrc = policy->ssrc.value;
+ srtp_err_status_t status;
+
+ /*
+ * allocate and initialize an srtp session
+ */
+ status = srtp_create(&srtp, policy);
+ if (status) {
+ printf("error: srtp_create() failed with error code %d\n", status);
+ exit(1);
+ }
+
+ mesg = srtp_create_test_packet(msg_len_octets, ssrc, &len);
+ if (mesg == NULL) {
+ return 0.0; /* indicate failure by returning zero */
+ }
+ srtp_protect(srtp, (srtp_hdr_t *)mesg, &len);
+
+ timer = clock();
+ for (i = 0; i < num_trials; i++) {
+ len = msg_len_octets;
+ srtp_unprotect(srtp, (srtp_hdr_t *)mesg, &len);
+ }
+ timer = clock() - timer;
+
+ free(mesg);
+
+ status = srtp_dealloc(srtp);
+ if (status) {
+ printf("error: srtp_dealloc() failed with error code %d\n", status);
+ exit(1);
+ }
+
+ return (double)num_trials * CLOCKS_PER_SEC / timer;
+}
+
+void err_check(srtp_err_status_t s)
+{
+ if (s == srtp_err_status_ok) {
+ return;
+ } else {
+ fprintf(stderr, "error: unexpected srtp failure (code %d)\n", s);
+ }
+ exit(1);
+}
+
+srtp_err_status_t srtp_test_call_protect(srtp_t srtp_sender,
+ srtp_hdr_t *hdr,
+ int *len,
+ int mki_index)
+{
+ if (mki_index == -1) {
+ return srtp_protect(srtp_sender, hdr, len);
+ } else {
+ return srtp_protect_mki(srtp_sender, hdr, len, 1, mki_index);
+ }
+}
+
+srtp_err_status_t srtp_test_call_protect_rtcp(srtp_t srtp_sender,
+ srtp_hdr_t *hdr,
+ int *len,
+ int mki_index)
+{
+ if (mki_index == -1) {
+ return srtp_protect_rtcp(srtp_sender, hdr, len);
+ } else {
+ return srtp_protect_rtcp_mki(srtp_sender, hdr, len, 1, mki_index);
+ }
+}
+
+srtp_err_status_t srtp_test_call_unprotect(srtp_t srtp_sender,
+ srtp_hdr_t *hdr,
+ int *len,
+ int use_mki)
+{
+ if (use_mki == -1) {
+ return srtp_unprotect(srtp_sender, hdr, len);
+ } else {
+ return srtp_unprotect_mki(srtp_sender, hdr, len, use_mki);
+ }
+}
+
+srtp_err_status_t srtp_test_call_unprotect_rtcp(srtp_t srtp_sender,
+ srtp_hdr_t *hdr,
+ int *len,
+ int use_mki)
+{
+ if (use_mki == -1) {
+ return srtp_unprotect_rtcp(srtp_sender, hdr, len);
+ } else {
+ return srtp_unprotect_rtcp_mki(srtp_sender, hdr, len, use_mki);
+ }
+}
+
+srtp_err_status_t srtp_test(const srtp_policy_t *policy,
+ int extension_header,
+ int mki_index)
+{
+ int i;
+ srtp_t srtp_sender;
+ srtp_t srtp_rcvr;
+ srtp_err_status_t status = srtp_err_status_ok;
+ srtp_hdr_t *hdr, *hdr2;
+ uint8_t hdr_enc[64];
+ uint8_t *pkt_end;
+ int msg_len_octets, msg_len_enc, msg_len;
+ int len, len2;
+ uint32_t tag_length;
+ uint32_t ssrc;
+ srtp_policy_t *rcvr_policy;
+ srtp_policy_t tmp_policy;
+ int header = 1;
+ int use_mki = 0;
+
+ if (mki_index >= 0)
+ use_mki = 1;
+
+ if (extension_header) {
+ memcpy(&tmp_policy, policy, sizeof(srtp_policy_t));
+ tmp_policy.enc_xtn_hdr = &header;
+ tmp_policy.enc_xtn_hdr_count = 1;
+ err_check(srtp_create(&srtp_sender, &tmp_policy));
+ } else {
+ err_check(srtp_create(&srtp_sender, policy));
+ }
+
+ /* print out policy */
+ err_check(srtp_session_print_policy(srtp_sender));
+
+ /*
+ * initialize data buffer, using the ssrc in the policy unless that
+ * value is a wildcard, in which case we'll just use an arbitrary
+ * one
+ */
+ if (policy->ssrc.type != ssrc_specific) {
+ ssrc = 0xdecafbad;
+ } else {
+ ssrc = policy->ssrc.value;
+ }
+ msg_len_octets = 28;
+ if (extension_header) {
+ hdr = srtp_create_test_packet_ext_hdr(msg_len_octets, ssrc, &len);
+ hdr2 = srtp_create_test_packet_ext_hdr(msg_len_octets, ssrc, &len2);
+ } else {
+ hdr = srtp_create_test_packet(msg_len_octets, ssrc, &len);
+ hdr2 = srtp_create_test_packet(msg_len_octets, ssrc, &len2);
+ }
+
+ /* save original msg len */
+ msg_len = len;
+
+ if (hdr == NULL) {
+ free(hdr2);
+ return srtp_err_status_alloc_fail;
+ }
+ if (hdr2 == NULL) {
+ free(hdr);
+ return srtp_err_status_alloc_fail;
+ }
+
+ debug_print(mod_driver, "before protection:\n%s",
+ srtp_packet_to_string(hdr, len));
+
+#if PRINT_REFERENCE_PACKET
+ debug_print(mod_driver, "reference packet before protection:\n%s",
+ octet_string_hex_string((uint8_t *)hdr, len));
+#endif
+ err_check(srtp_test_call_protect(srtp_sender, hdr, &len, mki_index));
+
+ debug_print(mod_driver, "after protection:\n%s",
+ srtp_packet_to_string(hdr, len));
+#if PRINT_REFERENCE_PACKET
+ debug_print(mod_driver, "after protection:\n%s",
+ octet_string_hex_string((uint8_t *)hdr, len));
+#endif
+
+ /* save protected message and length */
+ memcpy(hdr_enc, hdr, len);
+ msg_len_enc = len;
+
+ /*
+ * check for overrun of the srtp_protect() function
+ *
+ * The packet is followed by a value of 0xfffff; if the value of the
+ * data following the packet is different, then we know that the
+ * protect function is overwriting the end of the packet.
+ */
+ err_check(srtp_get_protect_trailer_length(srtp_sender, use_mki, mki_index,
+ &tag_length));
+ pkt_end = (uint8_t *)hdr + msg_len + tag_length;
+ for (i = 0; i < 4; i++) {
+ if (pkt_end[i] != 0xff) {
+ fprintf(stdout, "overwrite in srtp_protect() function "
+ "(expected %x, found %x in trailing octet %d)\n",
+ 0xff, ((uint8_t *)hdr)[i], i);
+ free(hdr);
+ free(hdr2);
+ return srtp_err_status_algo_fail;
+ }
+ }
+
+ /*
+ * if the policy includes confidentiality, check that ciphertext is
+ * different than plaintext
+ *
+ * Note that this check will give false negatives, with some small
+ * probability, especially if the packets are short. For that
+ * reason, we skip this check if the plaintext is less than four
+ * octets long.
+ */
+ if ((policy->rtp.sec_serv & sec_serv_conf) && (msg_len_octets >= 4)) {
+ printf("testing that ciphertext is distinct from plaintext...");
+ status = srtp_err_status_algo_fail;
+ for (i = 12; i < msg_len_octets + 12; i++) {
+ if (((uint8_t *)hdr)[i] != ((uint8_t *)hdr2)[i]) {
+ status = srtp_err_status_ok;
+ }
+ }
+ if (status) {
+ printf("failed\n");
+ free(hdr);
+ free(hdr2);
+ return status;
+ }
+ printf("passed\n");
+ }
+
+ /*
+ * if the policy uses a 'wildcard' ssrc, then we need to make a copy
+ * of the policy that changes the direction to inbound
+ *
+ * we always copy the policy into the rcvr_policy, since otherwise
+ * the compiler would fret about the constness of the policy
+ */
+ rcvr_policy = (srtp_policy_t *)malloc(sizeof(srtp_policy_t));
+ if (rcvr_policy == NULL) {
+ free(hdr);
+ free(hdr2);
+ return srtp_err_status_alloc_fail;
+ }
+ if (extension_header) {
+ memcpy(rcvr_policy, &tmp_policy, sizeof(srtp_policy_t));
+ if (tmp_policy.ssrc.type == ssrc_any_outbound) {
+ rcvr_policy->ssrc.type = ssrc_any_inbound;
+ }
+ } else {
+ memcpy(rcvr_policy, policy, sizeof(srtp_policy_t));
+ if (policy->ssrc.type == ssrc_any_outbound) {
+ rcvr_policy->ssrc.type = ssrc_any_inbound;
+ }
+ }
+
+ err_check(srtp_create(&srtp_rcvr, rcvr_policy));
+
+ err_check(srtp_test_call_unprotect(srtp_rcvr, hdr, &len, use_mki));
+
+ debug_print(mod_driver, "after unprotection:\n%s",
+ srtp_packet_to_string(hdr, len));
+
+ /* verify that the unprotected packet matches the origial one */
+ for (i = 0; i < len; i++) {
+ if (((uint8_t *)hdr)[i] != ((uint8_t *)hdr2)[i]) {
+ fprintf(stdout, "mismatch at octet %d\n", i);
+ status = srtp_err_status_algo_fail;
+ }
+ }
+ if (status) {
+ free(hdr);
+ free(hdr2);
+ free(rcvr_policy);
+ return status;
+ }
+
+ /*
+ * if the policy includes authentication, then test for false positives
+ */
+ if (policy->rtp.sec_serv & sec_serv_auth) {
+ char *data = ((char *)hdr) + (extension_header ? 24 : 12);
+
+ printf("testing for false positives in replay check...");
+
+ /* unprotect a second time - should fail with a replay error */
+ status =
+ srtp_test_call_unprotect(srtp_rcvr, hdr, &msg_len_enc, use_mki);
+ if (status != srtp_err_status_replay_fail) {
+ printf("failed with error code %d\n", status);
+ free(hdr);
+ free(hdr2);
+ free(rcvr_policy);
+ return status;
+ } else {
+ printf("passed\n");
+ }
+
+ printf("testing for false positives in auth check...");
+
+ /* increment sequence number in header */
+ hdr->seq++;
+
+ /* apply protection */
+ err_check(srtp_test_call_protect(srtp_sender, hdr, &len, mki_index));
+
+ /* flip bits in packet */
+ data[0] ^= 0xff;
+
+ /* unprotect, and check for authentication failure */
+ status = srtp_test_call_unprotect(srtp_rcvr, hdr, &len, use_mki);
+ if (status != srtp_err_status_auth_fail) {
+ printf("failed\n");
+ free(hdr);
+ free(hdr2);
+ free(rcvr_policy);
+ return status;
+ } else {
+ printf("passed\n");
+ }
+ }
+
+ err_check(srtp_dealloc(srtp_sender));
+ err_check(srtp_dealloc(srtp_rcvr));
+
+ free(hdr);
+ free(hdr2);
+ free(rcvr_policy);
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtcp_test(const srtp_policy_t *policy, int mki_index)
+{
+ int i;
+ srtp_t srtcp_sender;
+ srtp_t srtcp_rcvr;
+ srtp_err_status_t status = srtp_err_status_ok;
+ srtp_hdr_t *hdr, *hdr2;
+ uint8_t hdr_enc[64];
+ uint8_t *pkt_end;
+ int msg_len_octets, msg_len_enc, msg_len;
+ int len, len2;
+ uint32_t tag_length;
+ uint32_t ssrc;
+ srtp_policy_t *rcvr_policy;
+ int use_mki = 0;
+
+ if (mki_index >= 0)
+ use_mki = 1;
+
+ err_check(srtp_create(&srtcp_sender, policy));
+
+ /* print out policy */
+ err_check(srtp_session_print_policy(srtcp_sender));
+
+ /*
+ * initialize data buffer, using the ssrc in the policy unless that
+ * value is a wildcard, in which case we'll just use an arbitrary
+ * one
+ */
+ if (policy->ssrc.type != ssrc_specific) {
+ ssrc = 0xdecafbad;
+ } else {
+ ssrc = policy->ssrc.value;
+ }
+ msg_len_octets = 28;
+ hdr = srtp_create_test_packet(msg_len_octets, ssrc, &len);
+ /* save message len */
+ msg_len = len;
+
+ if (hdr == NULL) {
+ return srtp_err_status_alloc_fail;
+ }
+ hdr2 = srtp_create_test_packet(msg_len_octets, ssrc, &len2);
+ if (hdr2 == NULL) {
+ free(hdr);
+ return srtp_err_status_alloc_fail;
+ }
+
+ debug_print(mod_driver, "before protection:\n%s",
+ srtp_packet_to_string(hdr, len));
+
+#if PRINT_REFERENCE_PACKET
+ debug_print(mod_driver, "reference packet before protection:\n%s",
+ octet_string_hex_string((uint8_t *)hdr, len));
+#endif
+ err_check(srtp_test_call_protect_rtcp(srtcp_sender, hdr, &len, mki_index));
+
+ debug_print(mod_driver, "after protection:\n%s",
+ srtp_packet_to_string(hdr, len));
+#if PRINT_REFERENCE_PACKET
+ debug_print(mod_driver, "after protection:\n%s",
+ octet_string_hex_string((uint8_t *)hdr, len));
+#endif
+
+ /* save protected message and length */
+ memcpy(hdr_enc, hdr, len);
+ msg_len_enc = len;
+
+ /*
+ * check for overrun of the srtp_protect() function
+ *
+ * The packet is followed by a value of 0xfffff; if the value of the
+ * data following the packet is different, then we know that the
+ * protect function is overwriting the end of the packet.
+ */
+ srtp_get_protect_rtcp_trailer_length(srtcp_sender, use_mki, mki_index,
+ &tag_length);
+ pkt_end = (uint8_t *)hdr + msg_len + tag_length;
+ for (i = 0; i < 4; i++) {
+ if (pkt_end[i] != 0xff) {
+ fprintf(stdout, "overwrite in srtp_protect_rtcp() function "
+ "(expected %x, found %x in trailing octet %d)\n",
+ 0xff, ((uint8_t *)hdr)[i], i);
+ free(hdr);
+ free(hdr2);
+ return srtp_err_status_algo_fail;
+ }
+ }
+
+ /*
+ * if the policy includes confidentiality, check that ciphertext is
+ * different than plaintext
+ *
+ * Note that this check will give false negatives, with some small
+ * probability, especially if the packets are short. For that
+ * reason, we skip this check if the plaintext is less than four
+ * octets long.
+ */
+ if ((policy->rtcp.sec_serv & sec_serv_conf) && (msg_len_octets >= 4)) {
+ printf("testing that ciphertext is distinct from plaintext...");
+ status = srtp_err_status_algo_fail;
+ for (i = 12; i < msg_len_octets + 12; i++) {
+ if (((uint8_t *)hdr)[i] != ((uint8_t *)hdr2)[i]) {
+ status = srtp_err_status_ok;
+ }
+ }
+ if (status) {
+ printf("failed\n");
+ free(hdr);
+ free(hdr2);
+ return status;
+ }
+ printf("passed\n");
+ }
+
+ /*
+ * if the policy uses a 'wildcard' ssrc, then we need to make a copy
+ * of the policy that changes the direction to inbound
+ *
+ * we always copy the policy into the rcvr_policy, since otherwise
+ * the compiler would fret about the constness of the policy
+ */
+ rcvr_policy = (srtp_policy_t *)malloc(sizeof(srtp_policy_t));
+ if (rcvr_policy == NULL) {
+ free(hdr);
+ free(hdr2);
+ return srtp_err_status_alloc_fail;
+ }
+ memcpy(rcvr_policy, policy, sizeof(srtp_policy_t));
+ if (policy->ssrc.type == ssrc_any_outbound) {
+ rcvr_policy->ssrc.type = ssrc_any_inbound;
+ }
+
+ err_check(srtp_create(&srtcp_rcvr, rcvr_policy));
+
+ err_check(srtp_test_call_unprotect_rtcp(srtcp_rcvr, hdr, &len, use_mki));
+
+ debug_print(mod_driver, "after unprotection:\n%s",
+ srtp_packet_to_string(hdr, len));
+
+ /* verify that the unprotected packet matches the origial one */
+ for (i = 0; i < len; i++) {
+ if (((uint8_t *)hdr)[i] != ((uint8_t *)hdr2)[i]) {
+ fprintf(stdout, "mismatch at octet %d\n", i);
+ status = srtp_err_status_algo_fail;
+ }
+ }
+ if (status) {
+ free(hdr);
+ free(hdr2);
+ free(rcvr_policy);
+ return status;
+ }
+
+ /*
+ * if the policy includes authentication, then test for false positives
+ */
+ if (policy->rtp.sec_serv & sec_serv_auth) {
+ char *data = ((char *)hdr) + 12;
+
+ printf("testing for false positives in replay check...");
+
+ /* unprotect a second time - should fail with a replay error */
+ status = srtp_test_call_unprotect_rtcp(srtcp_rcvr, hdr, &msg_len_enc,
+ use_mki);
+ if (status != srtp_err_status_replay_fail) {
+ printf("failed with error code %d\n", status);
+ free(hdr);
+ free(hdr2);
+ free(rcvr_policy);
+ return status;
+ } else {
+ printf("passed\n");
+ }
+
+ printf("testing for false positives in auth check...");
+
+ /* increment sequence number in header */
+ hdr->seq++;
+
+ /* apply protection */
+ err_check(
+ srtp_test_call_protect_rtcp(srtcp_sender, hdr, &len, mki_index));
+
+ /* flip bits in packet */
+ data[0] ^= 0xff;
+
+ /* unprotect, and check for authentication failure */
+ status = srtp_test_call_unprotect_rtcp(srtcp_rcvr, hdr, &len, use_mki);
+ if (status != srtp_err_status_auth_fail) {
+ printf("failed\n");
+ free(hdr);
+ free(hdr2);
+ free(rcvr_policy);
+ return status;
+ } else {
+ printf("passed\n");
+ }
+ }
+
+ err_check(srtp_dealloc(srtcp_sender));
+ err_check(srtp_dealloc(srtcp_rcvr));
+
+ free(hdr);
+ free(hdr2);
+ free(rcvr_policy);
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_session_print_policy(srtp_t srtp)
+{
+ char *serv_descr[4] = { "none", "confidentiality", "authentication",
+ "confidentiality and authentication" };
+ char *direction[3] = { "unknown", "outbound", "inbound" };
+ srtp_stream_t stream;
+ srtp_session_keys_t *session_keys = NULL;
+
+ /* sanity checking */
+ if (srtp == NULL) {
+ return srtp_err_status_fail;
+ }
+
+ /* if there's a template stream, print it out */
+ if (srtp->stream_template != NULL) {
+ stream = srtp->stream_template;
+ session_keys = &stream->session_keys[0];
+ printf("# SSRC: any %s\r\n"
+ "# rtp cipher: %s\r\n"
+ "# rtp auth: %s\r\n"
+ "# rtp services: %s\r\n"
+ "# rtcp cipher: %s\r\n"
+ "# rtcp auth: %s\r\n"
+ "# rtcp services: %s\r\n"
+ "# window size: %lu\r\n"
+ "# tx rtx allowed:%s\r\n",
+ direction[stream->direction],
+ session_keys->rtp_cipher->type->description,
+ session_keys->rtp_auth->type->description,
+ serv_descr[stream->rtp_services],
+ session_keys->rtcp_cipher->type->description,
+ session_keys->rtcp_auth->type->description,
+ serv_descr[stream->rtcp_services],
+ srtp_rdbx_get_window_size(&stream->rtp_rdbx),
+ stream->allow_repeat_tx ? "true" : "false");
+
+ printf("# Encrypted extension headers: ");
+ if (stream->enc_xtn_hdr && stream->enc_xtn_hdr_count > 0) {
+ int *enc_xtn_hdr = stream->enc_xtn_hdr;
+ int count = stream->enc_xtn_hdr_count;
+ while (count > 0) {
+ printf("%d ", *enc_xtn_hdr);
+ enc_xtn_hdr++;
+ count--;
+ }
+ printf("\n");
+ } else {
+ printf("none\n");
+ }
+ }
+
+ /* loop over streams in session, printing the policy of each */
+ stream = srtp->stream_list;
+ while (stream != NULL) {
+ if (stream->rtp_services > sec_serv_conf_and_auth) {
+ return srtp_err_status_bad_param;
+ }
+ session_keys = &stream->session_keys[0];
+
+ printf("# SSRC: 0x%08x\r\n"
+ "# rtp cipher: %s\r\n"
+ "# rtp auth: %s\r\n"
+ "# rtp services: %s\r\n"
+ "# rtcp cipher: %s\r\n"
+ "# rtcp auth: %s\r\n"
+ "# rtcp services: %s\r\n"
+ "# window size: %lu\r\n"
+ "# tx rtx allowed:%s\r\n",
+ stream->ssrc, session_keys->rtp_cipher->type->description,
+ session_keys->rtp_auth->type->description,
+ serv_descr[stream->rtp_services],
+ session_keys->rtcp_cipher->type->description,
+ session_keys->rtcp_auth->type->description,
+ serv_descr[stream->rtcp_services],
+ srtp_rdbx_get_window_size(&stream->rtp_rdbx),
+ stream->allow_repeat_tx ? "true" : "false");
+
+ printf("# Encrypted extension headers: ");
+ if (stream->enc_xtn_hdr && stream->enc_xtn_hdr_count > 0) {
+ int *enc_xtn_hdr = stream->enc_xtn_hdr;
+ int count = stream->enc_xtn_hdr_count;
+ while (count > 0) {
+ printf("%d ", *enc_xtn_hdr);
+ enc_xtn_hdr++;
+ count--;
+ }
+ printf("\n");
+ } else {
+ printf("none\n");
+ }
+
+ /* advance to next stream in the list */
+ stream = stream->next;
+ }
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_print_policy(const srtp_policy_t *policy)
+{
+ srtp_err_status_t status;
+ srtp_t session;
+
+ status = srtp_create(&session, policy);
+ if (status) {
+ return status;
+ }
+ status = srtp_session_print_policy(session);
+ if (status) {
+ return status;
+ }
+ status = srtp_dealloc(session);
+ if (status) {
+ return status;
+ }
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp_print_packet(...) is for debugging only
+ * it prints an RTP packet to the stdout
+ *
+ * note that this function is *not* threadsafe
+ */
+
+#include <stdio.h>
+
+#define MTU 2048
+
+char packet_string[MTU];
+
+char *srtp_packet_to_string(srtp_hdr_t *hdr, int pkt_octet_len)
+{
+ int octets_in_rtp_header = 12;
+ uint8_t *data = ((uint8_t *)hdr) + octets_in_rtp_header;
+ int hex_len = pkt_octet_len - octets_in_rtp_header;
+
+ /* sanity checking */
+ if ((hdr == NULL) || (pkt_octet_len > MTU)) {
+ return NULL;
+ }
+
+ /* write packet into string */
+ sprintf(packet_string, "(s)rtp packet: {\n"
+ " version:\t%d\n"
+ " p:\t\t%d\n"
+ " x:\t\t%d\n"
+ " cc:\t\t%d\n"
+ " m:\t\t%d\n"
+ " pt:\t\t%x\n"
+ " seq:\t\t%x\n"
+ " ts:\t\t%x\n"
+ " ssrc:\t%x\n"
+ " data:\t%s\n"
+ "} (%d octets in total)\n",
+ hdr->version, hdr->p, hdr->x, hdr->cc, hdr->m, hdr->pt, hdr->seq,
+ hdr->ts, hdr->ssrc, octet_string_hex_string(data, hex_len),
+ pkt_octet_len);
+
+ return packet_string;
+}
+
+/*
+ * mips_estimate() is a simple function to estimate the number of
+ * instructions per second that the host can perform. note that this
+ * function can be grossly wrong; you may want to have a manual sanity
+ * check of its output!
+ *
+ * the 'ignore' pointer is there to convince the compiler to not just
+ * optimize away the function
+ */
+
+double mips_estimate(int num_trials, int *ignore)
+{
+ clock_t t;
+ volatile int i, sum;
+
+ sum = 0;
+ t = clock();
+ for (i = 0; i < num_trials; i++) {
+ sum += i;
+ }
+ t = clock() - t;
+ if (t < 1) {
+ t = 1;
+ }
+
+ /* printf("%d\n", sum); */
+ *ignore = sum;
+
+ return (double)num_trials * CLOCKS_PER_SEC / t;
+}
+
+/*
+ * srtp_validate() verifies the correctness of libsrtp by comparing
+ * some computed packets against some pre-computed reference values.
+ * These packets were made with the default SRTP policy.
+ */
+
+srtp_err_status_t srtp_validate()
+{
+ // clang-format off
+ uint8_t srtp_plaintext_ref[28] = {
+ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab
+ };
+ uint8_t srtp_plaintext[38] = {
+ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ uint8_t srtp_ciphertext[38] = {
+ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0x4e, 0x55, 0xdc, 0x4c,
+ 0xe7, 0x99, 0x78, 0xd8, 0x8c, 0xa4, 0xd2, 0x15,
+ 0x94, 0x9d, 0x24, 0x02, 0xb7, 0x8d, 0x6a, 0xcc,
+ 0x99, 0xea, 0x17, 0x9b, 0x8d, 0xbb
+ };
+ uint8_t rtcp_plaintext_ref[24] = {
+ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ };
+ uint8_t rtcp_plaintext[38] = {
+ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ uint8_t srtcp_ciphertext[38] = {
+ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe,
+ 0x71, 0x28, 0x03, 0x5b, 0xe4, 0x87, 0xb9, 0xbd,
+ 0xbe, 0xf8, 0x90, 0x41, 0xf9, 0x77, 0xa5, 0xa8,
+ 0x80, 0x00, 0x00, 0x01, 0x99, 0x3e, 0x08, 0xcd,
+ 0x54, 0xd6, 0xc1, 0x23, 0x07, 0x98
+ };
+ // clang-format on
+
+ srtp_t srtp_snd, srtp_recv;
+ srtp_err_status_t status;
+ int len;
+ srtp_policy_t policy;
+
+ /*
+ * create a session with a single stream using the default srtp
+ * policy and with the SSRC value 0xcafebabe
+ */
+ memset(&policy, 0, sizeof(policy));
+ srtp_crypto_policy_set_rtp_default(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ policy.ssrc.type = ssrc_specific;
+ policy.ssrc.value = 0xcafebabe;
+ policy.key = test_key;
+ policy.ekt = NULL;
+ policy.window_size = 128;
+ policy.allow_repeat_tx = 0;
+ policy.next = NULL;
+
+ status = srtp_create(&srtp_snd, &policy);
+ if (status) {
+ return status;
+ }
+
+ /*
+ * protect plaintext, then compare with ciphertext
+ */
+ len = 28;
+ status = srtp_protect(srtp_snd, srtp_plaintext, &len);
+ if (status || (len != 38)) {
+ return srtp_err_status_fail;
+ }
+
+ debug_print(mod_driver, "ciphertext:\n %s",
+ octet_string_hex_string(srtp_plaintext, len));
+ debug_print(mod_driver, "ciphertext reference:\n %s",
+ octet_string_hex_string(srtp_ciphertext, len));
+
+ if (octet_string_is_eq(srtp_plaintext, srtp_ciphertext, len)) {
+ return srtp_err_status_fail;
+ }
+
+ /*
+ * protect plaintext rtcp, then compare with srtcp ciphertext
+ */
+ len = 24;
+ status = srtp_protect_rtcp(srtp_snd, rtcp_plaintext, &len);
+ if (status || (len != 38)) {
+ return srtp_err_status_fail;
+ }
+
+ debug_print(mod_driver, "srtcp ciphertext:\n %s",
+ octet_string_hex_string(rtcp_plaintext, len));
+ debug_print(mod_driver, "srtcp ciphertext reference:\n %s",
+ octet_string_hex_string(srtcp_ciphertext, len));
+
+ if (octet_string_is_eq(rtcp_plaintext, srtcp_ciphertext, len)) {
+ return srtp_err_status_fail;
+ }
+
+ /*
+ * create a receiver session context comparable to the one created
+ * above - we need to do this so that the replay checking doesn't
+ * complain
+ */
+ status = srtp_create(&srtp_recv, &policy);
+ if (status) {
+ return status;
+ }
+
+ /*
+ * unprotect ciphertext, then compare with plaintext
+ */
+ status = srtp_unprotect(srtp_recv, srtp_ciphertext, &len);
+ if (status || (len != 28)) {
+ return status;
+ }
+
+ if (octet_string_is_eq(srtp_ciphertext, srtp_plaintext_ref, len)) {
+ return srtp_err_status_fail;
+ }
+
+ /*
+ * unprotect srtcp ciphertext, then compare with rtcp plaintext
+ */
+ len = 38;
+ status = srtp_unprotect_rtcp(srtp_recv, srtcp_ciphertext, &len);
+ if (status || (len != 24)) {
+ return status;
+ }
+
+ if (octet_string_is_eq(srtcp_ciphertext, rtcp_plaintext_ref, len)) {
+ return srtp_err_status_fail;
+ }
+
+ status = srtp_dealloc(srtp_snd);
+ if (status) {
+ return status;
+ }
+
+ status = srtp_dealloc(srtp_recv);
+ if (status) {
+ return status;
+ }
+
+ return srtp_err_status_ok;
+}
+
+#ifdef GCM
+/*
+ * srtp_validate_gcm() verifies the correctness of libsrtp by comparing
+ * an computed packet against the known ciphertext for the plaintext.
+ */
+srtp_err_status_t srtp_validate_gcm()
+{
+ // clang-format off
+ unsigned char test_key_gcm[28] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+ 0xa8, 0xa9, 0xaa, 0xab
+ };
+ uint8_t rtp_plaintext_ref[28] = {
+ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab
+ };
+ uint8_t rtp_plaintext[44] = {
+ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
+ };
+ uint8_t srtp_ciphertext[44] = {
+ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0xc5, 0x00, 0x2e, 0xde,
+ 0x04, 0xcf, 0xdd, 0x2e, 0xb9, 0x11, 0x59, 0xe0,
+ 0x88, 0x0a, 0xa0, 0x6e, 0xd2, 0x97, 0x68, 0x26,
+ 0xf7, 0x96, 0xb2, 0x01, 0xdf, 0x31, 0x31, 0xa1,
+ 0x27, 0xe8, 0xa3, 0x92
+ };
+ uint8_t rtcp_plaintext_ref[24] = {
+ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ };
+ uint8_t rtcp_plaintext[44] = {
+ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
+ };
+ uint8_t srtcp_ciphertext[44] = {
+ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe,
+ 0xc9, 0x8b, 0x8b, 0x5d, 0xf0, 0x39, 0x2a, 0x55,
+ 0x85, 0x2b, 0x6c, 0x21, 0xac, 0x8e, 0x70, 0x25,
+ 0xc5, 0x2c, 0x6f, 0xbe, 0xa2, 0xb3, 0xb4, 0x46,
+ 0xea, 0x31, 0x12, 0x3b, 0xa8, 0x8c, 0xe6, 0x1e,
+ 0x80, 0x00, 0x00, 0x01
+ };
+ // clang-format on
+
+ srtp_t srtp_snd, srtp_recv;
+ srtp_err_status_t status;
+ int len;
+ srtp_policy_t policy;
+
+ /*
+ * create a session with a single stream using the default srtp
+ * policy and with the SSRC value 0xcafebabe
+ */
+ memset(&policy, 0, sizeof(policy));
+ srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtp);
+ srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtcp);
+ policy.ssrc.type = ssrc_specific;
+ policy.ssrc.value = 0xcafebabe;
+ policy.key = test_key_gcm;
+ policy.ekt = NULL;
+ policy.window_size = 128;
+ policy.allow_repeat_tx = 0;
+ policy.next = NULL;
+
+ status = srtp_create(&srtp_snd, &policy);
+ if (status) {
+ return status;
+ }
+
+ /*
+ * protect plaintext rtp, then compare with srtp ciphertext
+ */
+ len = 28;
+ status = srtp_protect(srtp_snd, rtp_plaintext, &len);
+ if (status || (len != 44)) {
+ return srtp_err_status_fail;
+ }
+
+ debug_print(mod_driver, "srtp ciphertext:\n %s",
+ octet_string_hex_string(rtp_plaintext, len));
+ debug_print(mod_driver, "srtp ciphertext reference:\n %s",
+ octet_string_hex_string(srtp_ciphertext, len));
+
+ if (octet_string_is_eq(rtp_plaintext, srtp_ciphertext, len)) {
+ return srtp_err_status_fail;
+ }
+
+ /*
+ * protect plaintext rtcp, then compare with srtcp ciphertext
+ */
+ len = 24;
+ status = srtp_protect_rtcp(srtp_snd, rtcp_plaintext, &len);
+ if (status || (len != 44)) {
+ return srtp_err_status_fail;
+ }
+
+ debug_print(mod_driver, "srtcp ciphertext:\n %s",
+ octet_string_hex_string(rtcp_plaintext, len));
+ debug_print(mod_driver, "srtcp ciphertext reference:\n %s",
+ octet_string_hex_string(srtcp_ciphertext, len));
+
+ if (octet_string_is_eq(rtcp_plaintext, srtcp_ciphertext, len)) {
+ return srtp_err_status_fail;
+ }
+
+ /*
+ * create a receiver session context comparable to the one created
+ * above - we need to do this so that the replay checking doesn't
+ * complain
+ */
+ status = srtp_create(&srtp_recv, &policy);
+ if (status) {
+ return status;
+ }
+
+ /*
+ * unprotect srtp ciphertext, then compare with rtp plaintext
+ */
+ len = 44;
+ status = srtp_unprotect(srtp_recv, srtp_ciphertext, &len);
+ if (status || (len != 28)) {
+ return status;
+ }
+
+ if (octet_string_is_eq(srtp_ciphertext, rtp_plaintext_ref, len)) {
+ return srtp_err_status_fail;
+ }
+
+ /*
+ * unprotect srtcp ciphertext, then compare with rtcp plaintext
+ */
+ len = 44;
+ status = srtp_unprotect_rtcp(srtp_recv, srtcp_ciphertext, &len);
+ if (status || (len != 24)) {
+ return status;
+ }
+
+ if (octet_string_is_eq(srtcp_ciphertext, rtcp_plaintext_ref, len)) {
+ return srtp_err_status_fail;
+ }
+
+ status = srtp_dealloc(srtp_snd);
+ if (status) {
+ return status;
+ }
+
+ status = srtp_dealloc(srtp_recv);
+ if (status) {
+ return status;
+ }
+
+ return srtp_err_status_ok;
+}
+#endif
+
+/*
+ * Test vectors taken from RFC 6904, Appendix A
+ */
+srtp_err_status_t srtp_validate_encrypted_extensions_headers()
+{
+ // clang-format off
+ unsigned char test_key_ext_headers[30] = {
+ 0xe1, 0xf9, 0x7a, 0x0d, 0x3e, 0x01, 0x8b, 0xe0,
+ 0xd6, 0x4f, 0xa3, 0x2c, 0x06, 0xde, 0x41, 0x39,
+ 0x0e, 0xc6, 0x75, 0xad, 0x49, 0x8a, 0xfe, 0xeb,
+ 0xb6, 0x96, 0x0b, 0x3a, 0xab, 0xe6
+ };
+ uint8_t srtp_plaintext_ref[56] = {
+ 0x90, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0xBE, 0xDE, 0x00, 0x06,
+ 0x17, 0x41, 0x42, 0x73, 0xA4, 0x75, 0x26, 0x27,
+ 0x48, 0x22, 0x00, 0x00, 0xC8, 0x30, 0x8E, 0x46,
+ 0x55, 0x99, 0x63, 0x86, 0xB3, 0x95, 0xFB, 0x00,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab
+ };
+ uint8_t srtp_plaintext[66] = {
+ 0x90, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0xBE, 0xDE, 0x00, 0x06,
+ 0x17, 0x41, 0x42, 0x73, 0xA4, 0x75, 0x26, 0x27,
+ 0x48, 0x22, 0x00, 0x00, 0xC8, 0x30, 0x8E, 0x46,
+ 0x55, 0x99, 0x63, 0x86, 0xB3, 0x95, 0xFB, 0x00,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00
+ };
+ uint8_t srtp_ciphertext[66] = {
+ 0x90, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0xBE, 0xDE, 0x00, 0x06,
+ 0x17, 0x58, 0x8A, 0x92, 0x70, 0xF4, 0xE1, 0x5E,
+ 0x1C, 0x22, 0x00, 0x00, 0xC8, 0x30, 0x95, 0x46,
+ 0xA9, 0x94, 0xF0, 0xBC, 0x54, 0x78, 0x97, 0x00,
+ 0x4e, 0x55, 0xdc, 0x4c, 0xe7, 0x99, 0x78, 0xd8,
+ 0x8c, 0xa4, 0xd2, 0x15, 0x94, 0x9d, 0x24, 0x02,
+ 0x5a, 0x46, 0xb3, 0xca, 0x35, 0xc5, 0x35, 0xa8,
+ 0x91, 0xc7
+ };
+ // clang-format on
+
+ srtp_t srtp_snd, srtp_recv;
+ srtp_err_status_t status;
+ int len;
+ srtp_policy_t policy;
+ int headers[3] = { 1, 3, 4 };
+
+ /*
+ * create a session with a single stream using the default srtp
+ * policy and with the SSRC value 0xcafebabe
+ */
+ memset(&policy, 0, sizeof(policy));
+ srtp_crypto_policy_set_rtp_default(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ policy.ssrc.type = ssrc_specific;
+ policy.ssrc.value = 0xcafebabe;
+ policy.key = test_key_ext_headers;
+ policy.ekt = NULL;
+ policy.window_size = 128;
+ policy.allow_repeat_tx = 0;
+ policy.enc_xtn_hdr = headers;
+ policy.enc_xtn_hdr_count = sizeof(headers) / sizeof(headers[0]);
+ policy.next = NULL;
+
+ status = srtp_create(&srtp_snd, &policy);
+ if (status)
+ return status;
+
+ /*
+ * protect plaintext, then compare with ciphertext
+ */
+ len = sizeof(srtp_plaintext_ref);
+ status = srtp_protect(srtp_snd, srtp_plaintext, &len);
+ if (status || (len != sizeof(srtp_plaintext)))
+ return srtp_err_status_fail;
+
+ debug_print(mod_driver, "ciphertext:\n %s",
+ srtp_octet_string_hex_string(srtp_plaintext, len));
+ debug_print(mod_driver, "ciphertext reference:\n %s",
+ srtp_octet_string_hex_string(srtp_ciphertext, len));
+
+ if (octet_string_is_eq(srtp_plaintext, srtp_ciphertext, len))
+ return srtp_err_status_fail;
+
+ /*
+ * create a receiver session context comparable to the one created
+ * above - we need to do this so that the replay checking doesn't
+ * complain
+ */
+ status = srtp_create(&srtp_recv, &policy);
+ if (status)
+ return status;
+
+ /*
+ * unprotect ciphertext, then compare with plaintext
+ */
+ status = srtp_unprotect(srtp_recv, srtp_ciphertext, &len);
+ if (status) {
+ return status;
+ } else if (len != sizeof(srtp_plaintext_ref)) {
+ return srtp_err_status_fail;
+ }
+
+ if (octet_string_is_eq(srtp_ciphertext, srtp_plaintext_ref, len))
+ return srtp_err_status_fail;
+
+ status = srtp_dealloc(srtp_snd);
+ if (status)
+ return status;
+
+ status = srtp_dealloc(srtp_recv);
+ if (status)
+ return status;
+
+ return srtp_err_status_ok;
+}
+
+#ifdef GCM
+
+/*
+ * Headers of test vectors taken from RFC 6904, Appendix A
+ */
+srtp_err_status_t srtp_validate_encrypted_extensions_headers_gcm()
+{
+ // clang-format off
+ unsigned char test_key_ext_headers[30] = {
+ 0xe1, 0xf9, 0x7a, 0x0d, 0x3e, 0x01, 0x8b, 0xe0,
+ 0xd6, 0x4f, 0xa3, 0x2c, 0x06, 0xde, 0x41, 0x39,
+ 0x0e, 0xc6, 0x75, 0xad, 0x49, 0x8a, 0xfe, 0xeb,
+ 0xb6, 0x96, 0x0b, 0x3a, 0xab, 0xe6
+ };
+ uint8_t srtp_plaintext_ref[56] = {
+ 0x90, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0xBE, 0xDE, 0x00, 0x06,
+ 0x17, 0x41, 0x42, 0x73, 0xA4, 0x75, 0x26, 0x27,
+ 0x48, 0x22, 0x00, 0x00, 0xC8, 0x30, 0x8E, 0x46,
+ 0x55, 0x99, 0x63, 0x86, 0xB3, 0x95, 0xFB, 0x00,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab
+ };
+ uint8_t srtp_plaintext[64] = {
+ 0x90, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0xBE, 0xDE, 0x00, 0x06,
+ 0x17, 0x41, 0x42, 0x73, 0xA4, 0x75, 0x26, 0x27,
+ 0x48, 0x22, 0x00, 0x00, 0xC8, 0x30, 0x8E, 0x46,
+ 0x55, 0x99, 0x63, 0x86, 0xB3, 0x95, 0xFB, 0x00,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ uint8_t srtp_ciphertext[64] = {
+ 0x90, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0xBE, 0xDE, 0x00, 0x06,
+ 0x17, 0x12, 0xe0, 0x20, 0x5b, 0xfa, 0x94, 0x9b,
+ 0x1C, 0x22, 0x00, 0x00, 0xC8, 0x30, 0xbb, 0x46,
+ 0x73, 0x27, 0x78, 0xd9, 0x92, 0x9a, 0xab, 0x00,
+ 0x0e, 0xca, 0x0c, 0xf9, 0x5e, 0xe9, 0x55, 0xb2,
+ 0x6c, 0xd3, 0xd2, 0x88, 0xb4, 0x9f, 0x6c, 0xa9,
+ 0xf4, 0xb1, 0xb7, 0x59, 0x71, 0x9e, 0xb5, 0xbc
+ };
+ // clang-format on
+
+ srtp_t srtp_snd, srtp_recv;
+ srtp_err_status_t status;
+ int len;
+ srtp_policy_t policy;
+ int headers[3] = { 1, 3, 4 };
+
+ /*
+ * create a session with a single stream using the default srtp
+ * policy and with the SSRC value 0xcafebabe
+ */
+ memset(&policy, 0, sizeof(policy));
+ srtp_crypto_policy_set_aes_gcm_128_8_auth(&policy.rtp);
+ srtp_crypto_policy_set_aes_gcm_128_8_auth(&policy.rtcp);
+ policy.ssrc.type = ssrc_specific;
+ policy.ssrc.value = 0xcafebabe;
+ policy.key = test_key_ext_headers;
+ policy.ekt = NULL;
+ policy.window_size = 128;
+ policy.allow_repeat_tx = 0;
+ policy.enc_xtn_hdr = headers;
+ policy.enc_xtn_hdr_count = sizeof(headers) / sizeof(headers[0]);
+ policy.next = NULL;
+
+ status = srtp_create(&srtp_snd, &policy);
+ if (status)
+ return status;
+
+ /*
+ * protect plaintext, then compare with ciphertext
+ */
+ len = sizeof(srtp_plaintext_ref);
+ status = srtp_protect(srtp_snd, srtp_plaintext, &len);
+ if (status || (len != sizeof(srtp_plaintext)))
+ return srtp_err_status_fail;
+
+ debug_print(mod_driver, "ciphertext:\n %s",
+ srtp_octet_string_hex_string(srtp_plaintext, len));
+ debug_print(mod_driver, "ciphertext reference:\n %s",
+ srtp_octet_string_hex_string(srtp_ciphertext, len));
+
+ if (octet_string_is_eq(srtp_plaintext, srtp_ciphertext, len))
+ return srtp_err_status_fail;
+
+ /*
+ * create a receiver session context comparable to the one created
+ * above - we need to do this so that the replay checking doesn't
+ * complain
+ */
+ status = srtp_create(&srtp_recv, &policy);
+ if (status)
+ return status;
+
+ /*
+ * unprotect ciphertext, then compare with plaintext
+ */
+ status = srtp_unprotect(srtp_recv, srtp_ciphertext, &len);
+ if (status) {
+ return status;
+ } else if (len != sizeof(srtp_plaintext_ref)) {
+ return srtp_err_status_fail;
+ }
+
+ if (octet_string_is_eq(srtp_ciphertext, srtp_plaintext_ref, len))
+ return srtp_err_status_fail;
+
+ status = srtp_dealloc(srtp_snd);
+ if (status)
+ return status;
+
+ status = srtp_dealloc(srtp_recv);
+ if (status)
+ return status;
+
+ return srtp_err_status_ok;
+}
+#endif
+
+/*
+ * srtp_validate_aes_256() verifies the correctness of libsrtp by comparing
+ * some computed packets against some pre-computed reference values.
+ * These packets were made with the AES-CM-256/HMAC-SHA-1-80 policy.
+ */
+
+srtp_err_status_t srtp_validate_aes_256()
+{
+ // clang-format off
+ unsigned char aes_256_test_key[46] = {
+ 0xf0, 0xf0, 0x49, 0x14, 0xb5, 0x13, 0xf2, 0x76,
+ 0x3a, 0x1b, 0x1f, 0xa1, 0x30, 0xf1, 0x0e, 0x29,
+ 0x98, 0xf6, 0xf6, 0xe4, 0x3e, 0x43, 0x09, 0xd1,
+ 0xe6, 0x22, 0xa0, 0xe3, 0x32, 0xb9, 0xf1, 0xb6,
+
+ 0x3b, 0x04, 0x80, 0x3d, 0xe5, 0x1e, 0xe7, 0xc9,
+ 0x64, 0x23, 0xab, 0x5b, 0x78, 0xd2
+ };
+ uint8_t srtp_plaintext_ref[28] = {
+ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab
+ };
+ uint8_t srtp_plaintext[38] = {
+ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ uint8_t srtp_ciphertext[38] = {
+ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad,
+ 0xca, 0xfe, 0xba, 0xbe, 0xf1, 0xd9, 0xde, 0x17,
+ 0xff, 0x25, 0x1f, 0xf1, 0xaa, 0x00, 0x77, 0x74,
+ 0xb0, 0xb4, 0xb4, 0x0d, 0xa0, 0x8d, 0x9d, 0x9a,
+ 0x5b, 0x3a, 0x55, 0xd8, 0x87, 0x3b
+ };
+ // clang-format on
+
+ srtp_t srtp_snd, srtp_recv;
+ srtp_err_status_t status;
+ int len;
+ srtp_policy_t policy;
+
+ /*
+ * create a session with a single stream using the default srtp
+ * policy and with the SSRC value 0xcafebabe
+ */
+ memset(&policy, 0, sizeof(policy));
+ srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(&policy.rtp);
+ srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(&policy.rtcp);
+ policy.ssrc.type = ssrc_specific;
+ policy.ssrc.value = 0xcafebabe;
+ policy.key = aes_256_test_key;
+ policy.ekt = NULL;
+ policy.window_size = 128;
+ policy.allow_repeat_tx = 0;
+ policy.next = NULL;
+
+ status = srtp_create(&srtp_snd, &policy);
+ if (status) {
+ return status;
+ }
+
+ /*
+ * protect plaintext, then compare with ciphertext
+ */
+ len = 28;
+ status = srtp_protect(srtp_snd, srtp_plaintext, &len);
+ if (status || (len != 38)) {
+ return srtp_err_status_fail;
+ }
+
+ debug_print(mod_driver, "ciphertext:\n %s",
+ octet_string_hex_string(srtp_plaintext, len));
+ debug_print(mod_driver, "ciphertext reference:\n %s",
+ octet_string_hex_string(srtp_ciphertext, len));
+
+ if (octet_string_is_eq(srtp_plaintext, srtp_ciphertext, len)) {
+ return srtp_err_status_fail;
+ }
+
+ /*
+ * create a receiver session context comparable to the one created
+ * above - we need to do this so that the replay checking doesn't
+ * complain
+ */
+ status = srtp_create(&srtp_recv, &policy);
+ if (status) {
+ return status;
+ }
+
+ /*
+ * unprotect ciphertext, then compare with plaintext
+ */
+ status = srtp_unprotect(srtp_recv, srtp_ciphertext, &len);
+ if (status || (len != 28)) {
+ return status;
+ }
+
+ if (octet_string_is_eq(srtp_ciphertext, srtp_plaintext_ref, len)) {
+ return srtp_err_status_fail;
+ }
+
+ status = srtp_dealloc(srtp_snd);
+ if (status) {
+ return status;
+ }
+
+ status = srtp_dealloc(srtp_recv);
+ if (status) {
+ return status;
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_create_big_policy(srtp_policy_t **list)
+{
+ extern const srtp_policy_t *policy_array[];
+ srtp_policy_t *p, *tmp;
+ int i = 0;
+ uint32_t ssrc = 0;
+
+ /* sanity checking */
+ if ((list == NULL) || (policy_array[0] == NULL)) {
+ return srtp_err_status_bad_param;
+ }
+
+ /*
+ * loop over policy list, mallocing a new list and copying values
+ * into it (and incrementing the SSRC value as we go along)
+ */
+ tmp = NULL;
+ while (policy_array[i] != NULL) {
+ p = (srtp_policy_t *)malloc(sizeof(srtp_policy_t));
+ if (p == NULL) {
+ return srtp_err_status_bad_param;
+ }
+ memcpy(p, policy_array[i], sizeof(srtp_policy_t));
+ p->ssrc.type = ssrc_specific;
+ p->ssrc.value = ssrc++;
+ p->next = tmp;
+ tmp = p;
+ i++;
+ }
+ *list = p;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_dealloc_big_policy(srtp_policy_t *list)
+{
+ srtp_policy_t *p, *next;
+
+ for (p = list; p != NULL; p = next) {
+ next = p->next;
+ free(p);
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_test_empty_payload()
+{
+ srtp_t srtp_snd, srtp_recv;
+ srtp_err_status_t status;
+ int len;
+ srtp_policy_t policy;
+ srtp_hdr_t *mesg;
+
+ /*
+ * create a session with a single stream using the default srtp
+ * policy and with the SSRC value 0xcafebabe
+ */
+ memset(&policy, 0, sizeof(policy));
+ srtp_crypto_policy_set_rtp_default(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ policy.ssrc.type = ssrc_specific;
+ policy.ssrc.value = 0xcafebabe;
+ policy.key = test_key;
+ policy.ekt = NULL;
+ policy.window_size = 128;
+ policy.allow_repeat_tx = 0;
+ policy.next = NULL;
+
+ status = srtp_create(&srtp_snd, &policy);
+ if (status) {
+ return status;
+ }
+
+ mesg = srtp_create_test_packet(0, policy.ssrc.value, &len);
+ if (mesg == NULL) {
+ return srtp_err_status_fail;
+ }
+
+ status = srtp_protect(srtp_snd, mesg, &len);
+ if (status) {
+ return status;
+ } else if (len != 12 + 10) {
+ return srtp_err_status_fail;
+ }
+
+ /*
+ * create a receiver session context comparable to the one created
+ * above - we need to do this so that the replay checking doesn't
+ * complain
+ */
+ status = srtp_create(&srtp_recv, &policy);
+ if (status) {
+ return status;
+ }
+
+ /*
+ * unprotect ciphertext, then compare with plaintext
+ */
+ status = srtp_unprotect(srtp_recv, mesg, &len);
+ if (status) {
+ return status;
+ } else if (len != 12) {
+ return srtp_err_status_fail;
+ }
+
+ status = srtp_dealloc(srtp_snd);
+ if (status) {
+ return status;
+ }
+
+ status = srtp_dealloc(srtp_recv);
+ if (status) {
+ return status;
+ }
+
+ free(mesg);
+
+ return srtp_err_status_ok;
+}
+
+#ifdef GCM
+srtp_err_status_t srtp_test_empty_payload_gcm()
+{
+ srtp_t srtp_snd, srtp_recv;
+ srtp_err_status_t status;
+ int len;
+ srtp_policy_t policy;
+ srtp_hdr_t *mesg;
+
+ /*
+ * create a session with a single stream using the default srtp
+ * policy and with the SSRC value 0xcafebabe
+ */
+ memset(&policy, 0, sizeof(policy));
+ srtp_crypto_policy_set_aes_gcm_128_8_auth(&policy.rtp);
+ srtp_crypto_policy_set_aes_gcm_128_8_auth(&policy.rtcp);
+ policy.ssrc.type = ssrc_specific;
+ policy.ssrc.value = 0xcafebabe;
+ policy.key = test_key;
+ policy.ekt = NULL;
+ policy.window_size = 128;
+ policy.allow_repeat_tx = 0;
+ policy.next = NULL;
+
+ status = srtp_create(&srtp_snd, &policy);
+ if (status) {
+ return status;
+ }
+
+ mesg = srtp_create_test_packet(0, policy.ssrc.value, &len);
+ if (mesg == NULL) {
+ return srtp_err_status_fail;
+ }
+
+ status = srtp_protect(srtp_snd, mesg, &len);
+ if (status) {
+ return status;
+ } else if (len != 12 + 8) {
+ return srtp_err_status_fail;
+ }
+
+ /*
+ * create a receiver session context comparable to the one created
+ * above - we need to do this so that the replay checking doesn't
+ * complain
+ */
+ status = srtp_create(&srtp_recv, &policy);
+ if (status) {
+ return status;
+ }
+
+ /*
+ * unprotect ciphertext, then compare with plaintext
+ */
+ status = srtp_unprotect(srtp_recv, mesg, &len);
+ if (status) {
+ return status;
+ } else if (len != 12) {
+ return srtp_err_status_fail;
+ }
+
+ status = srtp_dealloc(srtp_snd);
+ if (status) {
+ return status;
+ }
+
+ status = srtp_dealloc(srtp_recv);
+ if (status) {
+ return status;
+ }
+
+ free(mesg);
+
+ return srtp_err_status_ok;
+}
+#endif // GCM
+
+srtp_err_status_t srtp_test_remove_stream()
+{
+ srtp_err_status_t status;
+ srtp_policy_t *policy_list, policy;
+ srtp_t session;
+ srtp_stream_t stream;
+
+ /*
+ * srtp_get_stream() is a libSRTP internal function that we declare
+ * here so that we can use it to verify the correct operation of the
+ * library
+ */
+ extern srtp_stream_t srtp_get_stream(srtp_t srtp, uint32_t ssrc);
+
+ status = srtp_create_big_policy(&policy_list);
+ if (status) {
+ return status;
+ }
+
+ status = srtp_create(&session, policy_list);
+ if (status) {
+ return status;
+ }
+
+ /*
+ * check for false positives by trying to remove a stream that's not
+ * in the session
+ */
+ status = srtp_remove_stream(session, htonl(0xaaaaaaaa));
+ if (status != srtp_err_status_no_ctx) {
+ return srtp_err_status_fail;
+ }
+
+ /*
+ * check for false negatives by removing stream 0x1, then
+ * searching for streams 0x0 and 0x2
+ */
+ status = srtp_remove_stream(session, htonl(0x1));
+ if (status != srtp_err_status_ok) {
+ return srtp_err_status_fail;
+ }
+ stream = srtp_get_stream(session, htonl(0x0));
+ if (stream == NULL) {
+ return srtp_err_status_fail;
+ }
+ stream = srtp_get_stream(session, htonl(0x2));
+ if (stream == NULL) {
+ return srtp_err_status_fail;
+ }
+
+ status = srtp_dealloc(session);
+ if (status != srtp_err_status_ok) {
+ return status;
+ }
+
+ status = srtp_dealloc_big_policy(policy_list);
+ if (status != srtp_err_status_ok) {
+ return status;
+ }
+
+ /* Now test adding and removing a single stream */
+ memset(&policy, 0, sizeof(policy));
+ srtp_crypto_policy_set_rtp_default(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ policy.ssrc.type = ssrc_specific;
+ policy.ssrc.value = 0xcafebabe;
+ policy.key = test_key;
+ policy.ekt = NULL;
+ policy.window_size = 128;
+ policy.allow_repeat_tx = 0;
+ policy.next = NULL;
+
+ status = srtp_create(&session, NULL);
+ if (status != srtp_err_status_ok) {
+ return status;
+ }
+
+ status = srtp_add_stream(session, &policy);
+ if (status != srtp_err_status_ok) {
+ return status;
+ }
+
+ status = srtp_remove_stream(session, htonl(0xcafebabe));
+ if (status != srtp_err_status_ok) {
+ return status;
+ }
+
+ status = srtp_dealloc(session);
+ if (status != srtp_err_status_ok) {
+ return status;
+ }
+
+ return srtp_err_status_ok;
+}
+
+// clang-format off
+unsigned char test_alt_key[46] = {
+ 0xe5, 0x19, 0x6f, 0x01, 0x5e, 0xf1, 0x9b, 0xe1,
+ 0xd7, 0x47, 0xa7, 0x27, 0x07, 0xd7, 0x47, 0x33,
+ 0x01, 0xc2, 0x35, 0x4d, 0x59, 0x6a, 0xf7, 0x84,
+ 0x96, 0x98, 0xeb, 0xaa, 0xac, 0xf6, 0xa1, 0x45,
+ 0xc7, 0x15, 0xe2, 0xea, 0xfe, 0x55, 0x67, 0x96,
+ 0xb6, 0x96, 0x0b, 0x3a, 0xab, 0xe6
+};
+// clang-format on
+
+/*
+ * srtp_test_update() verifies updating/rekeying exsisting streams.
+ * As stated in https://tools.ietf.org/html/rfc3711#section-3.3.1
+ * the value of the ROC must not be reset after a rekey, this test
+ * atempts to prove that srtp_update does not reset the ROC.
+ */
+
+srtp_err_status_t srtp_test_update()
+{
+ srtp_err_status_t status;
+ uint32_t ssrc = 0x12121212;
+ int msg_len_octets = 32;
+ int protected_msg_len_octets;
+ srtp_hdr_t *msg;
+ srtp_t srtp_snd, srtp_recv;
+ srtp_policy_t policy;
+
+ memset(&policy, 0, sizeof(policy));
+ srtp_crypto_policy_set_rtp_default(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ policy.ekt = NULL;
+ policy.window_size = 128;
+ policy.allow_repeat_tx = 0;
+ policy.next = NULL;
+ policy.ssrc.type = ssrc_any_outbound;
+ policy.key = test_key;
+
+ /* create a send and recive ctx with defualt profile and test_key */
+ status = srtp_create(&srtp_recv, &policy);
+ if (status)
+ return status;
+
+ policy.ssrc.type = ssrc_any_inbound;
+ status = srtp_create(&srtp_snd, &policy);
+ if (status)
+ return status;
+
+ /* protect and unprotect two msg's that will cause the ROC to be equal to 1
+ */
+ msg = srtp_create_test_packet(msg_len_octets, ssrc,
+ &protected_msg_len_octets);
+ if (msg == NULL)
+ return srtp_err_status_alloc_fail;
+ msg->seq = htons(65535);
+
+ status = srtp_protect(srtp_snd, msg, &protected_msg_len_octets);
+ if (status)
+ return srtp_err_status_fail;
+
+ status = srtp_unprotect(srtp_recv, msg, &protected_msg_len_octets);
+ if (status)
+ return status;
+
+ free(msg);
+
+ msg = srtp_create_test_packet(msg_len_octets, ssrc,
+ &protected_msg_len_octets);
+ if (msg == NULL)
+ return srtp_err_status_alloc_fail;
+ msg->seq = htons(1);
+
+ status = srtp_protect(srtp_snd, msg, &protected_msg_len_octets);
+ if (status)
+ return srtp_err_status_fail;
+
+ status = srtp_unprotect(srtp_recv, msg, &protected_msg_len_octets);
+ if (status)
+ return status;
+
+ free(msg);
+
+ /* update send ctx with same test_key t verify update works*/
+ policy.ssrc.type = ssrc_any_outbound;
+ policy.key = test_key;
+ status = srtp_update(srtp_snd, &policy);
+ if (status)
+ return status;
+
+ msg = srtp_create_test_packet(msg_len_octets, ssrc,
+ &protected_msg_len_octets);
+ if (msg == NULL)
+ return srtp_err_status_alloc_fail;
+ msg->seq = htons(2);
+
+ status = srtp_protect(srtp_snd, msg, &protected_msg_len_octets);
+ if (status)
+ return srtp_err_status_fail;
+
+ status = srtp_unprotect(srtp_recv, msg, &protected_msg_len_octets);
+ if (status)
+ return status;
+
+ free(msg);
+
+ /* update send ctx to use test_alt_key */
+ policy.ssrc.type = ssrc_any_outbound;
+ policy.key = test_alt_key;
+ status = srtp_update(srtp_snd, &policy);
+ if (status)
+ return status;
+
+ /* create and protect msg with new key and ROC still equal to 1 */
+ msg = srtp_create_test_packet(msg_len_octets, ssrc,
+ &protected_msg_len_octets);
+ if (msg == NULL)
+ return srtp_err_status_alloc_fail;
+ msg->seq = htons(3);
+
+ status = srtp_protect(srtp_snd, msg, &protected_msg_len_octets);
+ if (status)
+ return srtp_err_status_fail;
+
+ /* verify that recive ctx will fail to unprotect as it still uses test_key
+ */
+ status = srtp_unprotect(srtp_recv, msg, &protected_msg_len_octets);
+ if (status == srtp_err_status_ok)
+ return srtp_err_status_fail;
+
+ /* create a new recvieve ctx with test_alt_key but since it is new it will
+ * have ROC equal to 1
+ * and therefore should fail to unprotected */
+ {
+ srtp_t srtp_recv_roc_0;
+
+ policy.ssrc.type = ssrc_any_inbound;
+ policy.key = test_alt_key;
+ status = srtp_create(&srtp_recv_roc_0, &policy);
+ if (status)
+ return status;
+
+ status =
+ srtp_unprotect(srtp_recv_roc_0, msg, &protected_msg_len_octets);
+ if (status == srtp_err_status_ok)
+ return srtp_err_status_fail;
+
+ status = srtp_dealloc(srtp_recv_roc_0);
+ if (status)
+ return status;
+ }
+
+ /* update recive ctx to use test_alt_key */
+ policy.ssrc.type = ssrc_any_inbound;
+ policy.key = test_alt_key;
+ status = srtp_update(srtp_recv, &policy);
+ if (status)
+ return status;
+
+ /* verify that can still unprotect, therfore key is updated and ROC value is
+ * preserved */
+ status = srtp_unprotect(srtp_recv, msg, &protected_msg_len_octets);
+ if (status)
+ return status;
+
+ free(msg);
+
+ status = srtp_dealloc(srtp_snd);
+ if (status)
+ return status;
+
+ status = srtp_dealloc(srtp_recv);
+ if (status)
+ return status;
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_test_setup_protect_trailer_streams(
+ srtp_t *srtp_send,
+ srtp_t *srtp_send_mki,
+ srtp_t *srtp_send_aes_gcm,
+ srtp_t *srtp_send_aes_gcm_mki)
+{
+ srtp_err_status_t status;
+ srtp_policy_t policy;
+ srtp_policy_t policy_mki;
+
+#ifdef GCM
+ srtp_policy_t policy_aes_gcm;
+ srtp_policy_t policy_aes_gcm_mki;
+#endif // GCM
+
+ memset(&policy, 0, sizeof(policy));
+ srtp_crypto_policy_set_rtp_default(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ policy.ekt = NULL;
+ policy.window_size = 128;
+ policy.allow_repeat_tx = 0;
+ policy.next = NULL;
+ policy.ssrc.type = ssrc_any_outbound;
+ policy.key = test_key;
+
+ memset(&policy_mki, 0, sizeof(policy_mki));
+ srtp_crypto_policy_set_rtp_default(&policy_mki.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy_mki.rtcp);
+ policy_mki.ekt = NULL;
+ policy_mki.window_size = 128;
+ policy_mki.allow_repeat_tx = 0;
+ policy_mki.next = NULL;
+ policy_mki.ssrc.type = ssrc_any_outbound;
+ policy_mki.key = NULL;
+ policy_mki.keys = test_keys;
+ policy_mki.num_master_keys = 2;
+
+#ifdef GCM
+ memset(&policy_aes_gcm, 0, sizeof(policy_aes_gcm));
+ srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy_aes_gcm.rtp);
+ srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy_aes_gcm.rtcp);
+ policy_aes_gcm.ekt = NULL;
+ policy_aes_gcm.window_size = 128;
+ policy_aes_gcm.allow_repeat_tx = 0;
+ policy_aes_gcm.next = NULL;
+ policy_aes_gcm.ssrc.type = ssrc_any_outbound;
+ policy_aes_gcm.key = test_key;
+
+ memset(&policy_aes_gcm_mki, 0, sizeof(policy_aes_gcm_mki));
+ srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy_aes_gcm_mki.rtp);
+ srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy_aes_gcm_mki.rtcp);
+ policy_aes_gcm_mki.ekt = NULL;
+ policy_aes_gcm_mki.window_size = 128;
+ policy_aes_gcm_mki.allow_repeat_tx = 0;
+ policy_aes_gcm_mki.next = NULL;
+ policy_aes_gcm_mki.ssrc.type = ssrc_any_outbound;
+ policy_aes_gcm_mki.key = NULL;
+ policy_aes_gcm_mki.keys = test_keys;
+ policy_aes_gcm_mki.num_master_keys = 2;
+#endif // GCM
+
+ /* create a send ctx with defualt profile and test_key */
+ status = srtp_create(srtp_send, &policy);
+ if (status)
+ return status;
+
+ status = srtp_create(srtp_send_mki, &policy_mki);
+ if (status)
+ return status;
+
+#ifdef GCM
+ status = srtp_create(srtp_send_aes_gcm, &policy_aes_gcm);
+ if (status)
+ return status;
+
+ status = srtp_create(srtp_send_aes_gcm_mki, &policy_aes_gcm_mki);
+ if (status)
+ return status;
+#endif // GCM
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_test_protect_trailer_length()
+{
+ srtp_t srtp_send;
+ srtp_t srtp_send_mki;
+ srtp_t srtp_send_aes_gcm;
+ srtp_t srtp_send_aes_gcm_mki;
+ uint32_t length = 0;
+ srtp_err_status_t status;
+
+ srtp_test_setup_protect_trailer_streams(
+ &srtp_send, &srtp_send_mki, &srtp_send_aes_gcm, &srtp_send_aes_gcm_mki);
+
+ status = srtp_get_protect_trailer_length(srtp_send, 0, 0, &length);
+ if (status)
+ return status;
+
+ /* TAG Length: 10 bytes */
+ if (length != 10)
+ return srtp_err_status_fail;
+
+ status = srtp_get_protect_trailer_length(srtp_send_mki, 1, 1, &length);
+ if (status)
+ return status;
+
+ /* TAG Length: 10 bytes + MKI length: 4 bytes*/
+ if (length != 14)
+ return srtp_err_status_fail;
+
+#ifdef GCM
+ status = srtp_get_protect_trailer_length(srtp_send_aes_gcm, 0, 0, &length);
+ if (status)
+ return status;
+
+ /* TAG Length: 16 bytes */
+ if (length != 16)
+ return srtp_err_status_fail;
+
+ status =
+ srtp_get_protect_trailer_length(srtp_send_aes_gcm_mki, 1, 1, &length);
+ if (status)
+ return status;
+
+ /* TAG Length: 16 bytes + MKI length: 4 bytes*/
+ if (length != 20)
+ return srtp_err_status_fail;
+#endif // GCM
+
+ srtp_dealloc(srtp_send);
+ srtp_dealloc(srtp_send_mki);
+#ifdef GCM
+ srtp_dealloc(srtp_send_aes_gcm);
+ srtp_dealloc(srtp_send_aes_gcm_mki);
+#endif
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_test_protect_rtcp_trailer_length()
+{
+ srtp_t srtp_send;
+ srtp_t srtp_send_mki;
+ srtp_t srtp_send_aes_gcm;
+ srtp_t srtp_send_aes_gcm_mki;
+ uint32_t length = 0;
+ srtp_err_status_t status;
+
+ srtp_test_setup_protect_trailer_streams(
+ &srtp_send, &srtp_send_mki, &srtp_send_aes_gcm, &srtp_send_aes_gcm_mki);
+
+ status = srtp_get_protect_rtcp_trailer_length(srtp_send, 0, 0, &length);
+ if (status)
+ return status;
+
+ /* TAG Length: 10 bytes + SRTCP Trailer 4 bytes*/
+ if (length != 14)
+ return srtp_err_status_fail;
+
+ status = srtp_get_protect_rtcp_trailer_length(srtp_send_mki, 1, 1, &length);
+ if (status)
+ return status;
+
+ /* TAG Length: 10 bytes + SRTCP Trailer 4 bytes + MKI 4 bytes*/
+ if (length != 18)
+ return srtp_err_status_fail;
+
+#ifdef GCM
+ status =
+ srtp_get_protect_rtcp_trailer_length(srtp_send_aes_gcm, 0, 0, &length);
+ if (status)
+ return status;
+
+ /* TAG Length: 16 bytes + SRTCP Trailer 4 bytes*/
+ if (length != 20)
+ return srtp_err_status_fail;
+
+ status = srtp_get_protect_rtcp_trailer_length(srtp_send_aes_gcm_mki, 1, 1,
+ &length);
+ if (status)
+ return status;
+
+ /* TAG Length: 16 bytes + SRTCP Trailer 4 bytes + MKI 4 bytes*/
+ if (length != 24)
+ return srtp_err_status_fail;
+#endif // GCM
+
+ srtp_dealloc(srtp_send);
+ srtp_dealloc(srtp_send_mki);
+#ifdef GCM
+ srtp_dealloc(srtp_send_aes_gcm);
+ srtp_dealloc(srtp_send_aes_gcm_mki);
+#endif
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_test_get_roc()
+{
+ srtp_err_status_t status;
+ srtp_policy_t policy;
+ srtp_t session;
+ srtp_hdr_t *pkt;
+ uint32_t i;
+ uint32_t roc;
+ uint32_t ts;
+ uint16_t seq;
+
+ int msg_len_octets = 32;
+ int protected_msg_len_octets;
+
+ memset(&policy, 0, sizeof(policy));
+ srtp_crypto_policy_set_rtp_default(&policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&policy.rtcp);
+ policy.ssrc.type = ssrc_specific;
+ policy.ssrc.value = 0xcafebabe;
+ policy.key = test_key;
+ policy.window_size = 128;
+
+ /* Create a sender session */
+ status = srtp_create(&session, &policy);
+ if (status) {
+ return status;
+ }
+
+ /* Set start sequence so we roll over */
+ seq = 65535;
+ ts = 0;
+
+ for (i = 0; i < 2; i++) {
+ pkt = srtp_create_test_packet_extended(msg_len_octets,
+ policy.ssrc.value, seq, ts,
+ &protected_msg_len_octets);
+ status = srtp_protect(session, pkt, &protected_msg_len_octets);
+ free(pkt);
+ if (status) {
+ return status;
+ }
+
+ status = srtp_get_stream_roc(session, policy.ssrc.value, &roc);
+ if (status) {
+ return status;
+ }
+
+ if (roc != i) {
+ return srtp_err_status_fail;
+ }
+
+ seq++;
+ ts++;
+ }
+
+ /* Cleanup */
+ status = srtp_dealloc(session);
+ if (status) {
+ return status;
+ }
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t test_set_receiver_roc(uint32_t packets,
+ uint32_t roc_to_set)
+{
+ srtp_err_status_t status;
+
+ srtp_policy_t sender_policy;
+ srtp_t sender_session;
+
+ srtp_policy_t receiver_policy;
+ srtp_t receiver_session;
+
+ srtp_hdr_t *pkt_1;
+ unsigned char *recv_pkt_1;
+
+ srtp_hdr_t *pkt_2;
+ unsigned char *recv_pkt_2;
+
+ uint32_t i;
+ uint32_t ts;
+ uint16_t seq;
+
+ int msg_len_octets = 32;
+ int protected_msg_len_octets_1;
+ int protected_msg_len_octets_2;
+
+ /* Create sender */
+ memset(&sender_policy, 0, sizeof(sender_policy));
+ srtp_crypto_policy_set_rtp_default(&sender_policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&sender_policy.rtcp);
+ sender_policy.ssrc.type = ssrc_specific;
+ sender_policy.ssrc.value = 0xcafebabe;
+ sender_policy.key = test_key;
+ sender_policy.window_size = 128;
+
+ status = srtp_create(&sender_session, &sender_policy);
+ if (status) {
+ return status;
+ }
+
+ /* Create and protect packets */
+ seq = 0;
+ ts = 0;
+ for (i = 0; i < packets; i++) {
+ srtp_hdr_t *tmp_pkt;
+ int tmp_len;
+
+ tmp_pkt = srtp_create_test_packet_extended(
+ msg_len_octets, sender_policy.ssrc.value, seq, ts, &tmp_len);
+ status = srtp_protect(sender_session, tmp_pkt, &tmp_len);
+ free(tmp_pkt);
+ if (status) {
+ return status;
+ }
+
+ seq++;
+ ts++;
+ }
+
+ /* Create the first packet to decrypt and test for ROC change */
+ pkt_1 = srtp_create_test_packet_extended(msg_len_octets,
+ sender_policy.ssrc.value, seq, ts,
+ &protected_msg_len_octets_1);
+ status = srtp_protect(sender_session, pkt_1, &protected_msg_len_octets_1);
+ if (status) {
+ return status;
+ }
+
+ /* Create the second packet to decrypt and test for ROC change */
+ seq++;
+ ts++;
+ pkt_2 = srtp_create_test_packet_extended(msg_len_octets,
+ sender_policy.ssrc.value, seq, ts,
+ &protected_msg_len_octets_2);
+ status = srtp_protect(sender_session, pkt_2, &protected_msg_len_octets_2);
+ if (status) {
+ return status;
+ }
+
+ /* Create the receiver */
+ memset(&receiver_policy, 0, sizeof(receiver_policy));
+ srtp_crypto_policy_set_rtp_default(&receiver_policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&receiver_policy.rtcp);
+ receiver_policy.ssrc.type = ssrc_specific;
+ receiver_policy.ssrc.value = sender_policy.ssrc.value;
+ receiver_policy.key = test_key;
+ receiver_policy.window_size = 128;
+
+ status = srtp_create(&receiver_session, &receiver_policy);
+ if (status) {
+ return status;
+ }
+
+ /* Make a copy of the first sent protected packet */
+ recv_pkt_1 = malloc(protected_msg_len_octets_1);
+ if (recv_pkt_1 == NULL) {
+ return srtp_err_status_fail;
+ }
+ memcpy(recv_pkt_1, pkt_1, protected_msg_len_octets_1);
+
+ /* Make a copy of the second sent protected packet */
+ recv_pkt_2 = malloc(protected_msg_len_octets_2);
+ if (recv_pkt_2 == NULL) {
+ return srtp_err_status_fail;
+ }
+ memcpy(recv_pkt_2, pkt_2, protected_msg_len_octets_2);
+
+ /* Set the ROC to the wanted value */
+ status = srtp_set_stream_roc(receiver_session, receiver_policy.ssrc.value,
+ roc_to_set);
+ if (status) {
+ return status;
+ }
+
+ /* Unprotect the first packet */
+ status = srtp_unprotect(receiver_session, recv_pkt_1,
+ &protected_msg_len_octets_1);
+ if (status) {
+ return status;
+ }
+
+ /* Unprotect the second packet */
+ status = srtp_unprotect(receiver_session, recv_pkt_2,
+ &protected_msg_len_octets_2);
+ if (status) {
+ return status;
+ }
+
+ /* Cleanup */
+ status = srtp_dealloc(sender_session);
+ if (status) {
+ return status;
+ }
+
+ status = srtp_dealloc(receiver_session);
+ if (status) {
+ return status;
+ }
+
+ free(pkt_1);
+ free(recv_pkt_1);
+ free(pkt_2);
+ free(recv_pkt_2);
+
+ return srtp_err_status_ok;
+}
+
+static srtp_err_status_t test_set_sender_roc(uint16_t seq, uint32_t roc_to_set)
+{
+ srtp_err_status_t status;
+
+ srtp_policy_t sender_policy;
+ srtp_t sender_session;
+
+ srtp_policy_t receiver_policy;
+ srtp_t receiver_session;
+
+ srtp_hdr_t *pkt;
+ unsigned char *recv_pkt;
+
+ uint32_t ts;
+
+ int msg_len_octets = 32;
+ int protected_msg_len_octets;
+
+ /* Create sender */
+ memset(&sender_policy, 0, sizeof(sender_policy));
+ srtp_crypto_policy_set_rtp_default(&sender_policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&sender_policy.rtcp);
+ sender_policy.ssrc.type = ssrc_specific;
+ sender_policy.ssrc.value = 0xcafebabe;
+ sender_policy.key = test_key;
+ sender_policy.window_size = 128;
+
+ status = srtp_create(&sender_session, &sender_policy);
+ if (status) {
+ return status;
+ }
+
+ /* Set the ROC before encrypting the first packet */
+ status = srtp_set_stream_roc(sender_session, sender_policy.ssrc.value,
+ roc_to_set);
+ if (status != srtp_err_status_ok) {
+ return status;
+ }
+
+ /* Create the packet to decrypt */
+ ts = 0;
+ pkt = srtp_create_test_packet_extended(msg_len_octets,
+ sender_policy.ssrc.value, seq, ts,
+ &protected_msg_len_octets);
+ status = srtp_protect(sender_session, pkt, &protected_msg_len_octets);
+ if (status) {
+ return status;
+ }
+
+ /* Create the receiver */
+ memset(&receiver_policy, 0, sizeof(receiver_policy));
+ srtp_crypto_policy_set_rtp_default(&receiver_policy.rtp);
+ srtp_crypto_policy_set_rtcp_default(&receiver_policy.rtcp);
+ receiver_policy.ssrc.type = ssrc_specific;
+ receiver_policy.ssrc.value = sender_policy.ssrc.value;
+ receiver_policy.key = test_key;
+ receiver_policy.window_size = 128;
+
+ status = srtp_create(&receiver_session, &receiver_policy);
+ if (status) {
+ return status;
+ }
+
+ /* Make a copy of the sent protected packet */
+ recv_pkt = malloc(protected_msg_len_octets);
+ if (recv_pkt == NULL) {
+ return srtp_err_status_fail;
+ }
+ memcpy(recv_pkt, pkt, protected_msg_len_octets);
+
+ /* Set the ROC to the wanted value */
+ status = srtp_set_stream_roc(receiver_session, receiver_policy.ssrc.value,
+ roc_to_set);
+ if (status) {
+ return status;
+ }
+
+ status =
+ srtp_unprotect(receiver_session, recv_pkt, &protected_msg_len_octets);
+ if (status) {
+ return status;
+ }
+
+ /* Cleanup */
+ status = srtp_dealloc(sender_session);
+ if (status) {
+ return status;
+ }
+
+ status = srtp_dealloc(receiver_session);
+ if (status) {
+ return status;
+ }
+
+ free(pkt);
+ free(recv_pkt);
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_test_set_receiver_roc()
+{
+ int packets;
+ uint32_t roc;
+ srtp_err_status_t status;
+
+ /* First test does not rollover */
+ packets = 1;
+ roc = 0;
+
+ status = test_set_receiver_roc(packets - 1, roc);
+ if (status) {
+ return status;
+ }
+
+ status = test_set_receiver_roc(packets, roc);
+ if (status) {
+ return status;
+ }
+
+ status = test_set_receiver_roc(packets + 1, roc);
+ if (status) {
+ return status;
+ }
+
+ status = test_set_receiver_roc(packets + 60000, roc);
+ if (status) {
+ return status;
+ }
+
+ /* Second test should rollover */
+ packets = 65535;
+ roc = 0;
+
+ status = test_set_receiver_roc(packets - 1, roc);
+ if (status) {
+ return status;
+ }
+
+ status = test_set_receiver_roc(packets, roc);
+ if (status) {
+ return status;
+ }
+
+ /* Now the rollover counter should be 1 */
+ roc = 1;
+ status = test_set_receiver_roc(packets + 1, roc);
+ if (status) {
+ return status;
+ }
+
+ status = test_set_receiver_roc(packets + 60000, roc);
+ if (status) {
+ return status;
+ }
+
+ return srtp_err_status_ok;
+}
+
+srtp_err_status_t srtp_test_set_sender_roc()
+{
+ uint32_t roc;
+ uint16_t seq;
+ srtp_err_status_t status;
+
+ seq = 43210;
+ roc = 0;
+ status = test_set_sender_roc(seq, roc);
+ if (status) {
+ return status;
+ }
+
+ roc = 65535;
+ status = test_set_sender_roc(seq, roc);
+ if (status) {
+ return status;
+ }
+
+ roc = 0xffff;
+ status = test_set_sender_roc(seq, roc);
+ if (status) {
+ return status;
+ }
+
+ roc = 0xffff00;
+ status = test_set_sender_roc(seq, roc);
+ if (status) {
+ return status;
+ }
+
+ roc = 0xfffffff0;
+ status = test_set_sender_roc(seq, roc);
+ if (status) {
+ return status;
+ }
+
+ return srtp_err_status_ok;
+}
+
+/*
+ * srtp policy definitions - these definitions are used above
+ */
+
+// clang-format off
+unsigned char test_key[46] = {
+ 0xe1, 0xf9, 0x7a, 0x0d, 0x3e, 0x01, 0x8b, 0xe0,
+ 0xd6, 0x4f, 0xa3, 0x2c, 0x06, 0xde, 0x41, 0x39,
+ 0x0e, 0xc6, 0x75, 0xad, 0x49, 0x8a, 0xfe, 0xeb,
+ 0xb6, 0x96, 0x0b, 0x3a, 0xab, 0xe6, 0xc1, 0x73,
+ 0xc3, 0x17, 0xf2, 0xda, 0xbe, 0x35, 0x77, 0x93,
+ 0xb6, 0x96, 0x0b, 0x3a, 0xab, 0xe6
+};
+
+unsigned char test_key_2[46] = {
+ 0xf0, 0xf0, 0x49, 0x14, 0xb5, 0x13, 0xf2, 0x76,
+ 0x3a, 0x1b, 0x1f, 0xa1, 0x30, 0xf1, 0x0e, 0x29,
+ 0x98, 0xf6, 0xf6, 0xe4, 0x3e, 0x43, 0x09, 0xd1,
+ 0xe6, 0x22, 0xa0, 0xe3, 0x32, 0xb9, 0xf1, 0xb6,
+ 0xc3, 0x17, 0xf2, 0xda, 0xbe, 0x35, 0x77, 0x93,
+ 0xb6, 0x96, 0x0b, 0x3a, 0xab, 0xe6
+};
+
+unsigned char test_mki_id[TEST_MKI_ID_SIZE] = {
+ 0xe1, 0xf9, 0x7a, 0x0d
+};
+
+unsigned char test_mki_id_2[TEST_MKI_ID_SIZE] = {
+ 0xf3, 0xa1, 0x46, 0x71
+};
+// clang-format on
+
+const srtp_policy_t default_policy = {
+ { ssrc_any_outbound, 0 }, /* SSRC */
+ {
+ /* SRTP policy */
+ SRTP_AES_ICM_128, /* cipher type */
+ SRTP_AES_ICM_128_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_HMAC_SHA1, /* authentication func type */
+ 16, /* auth key length in octets */
+ 10, /* auth tag length in octets */
+ sec_serv_conf_and_auth /* security services flag */
+ },
+ {
+ /* SRTCP policy */
+ SRTP_AES_ICM_128, /* cipher type */
+ SRTP_AES_ICM_128_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_HMAC_SHA1, /* authentication func type */
+ 16, /* auth key length in octets */
+ 10, /* auth tag length in octets */
+ sec_serv_conf_and_auth /* security services flag */
+ },
+ NULL,
+ (srtp_master_key_t **)test_keys,
+ 2, /* indicates the number of Master keys */
+ NULL, /* indicates that EKT is not in use */
+ 128, /* replay window size */
+ 0, /* retransmission not allowed */
+ NULL, /* no encrypted extension headers */
+ 0, /* list of encrypted extension headers is empty */
+ NULL
+};
+
+const srtp_policy_t aes_only_policy = {
+ { ssrc_any_outbound, 0 }, /* SSRC */
+ {
+ SRTP_AES_ICM_128, /* cipher type */
+ SRTP_AES_ICM_128_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_NULL_AUTH, /* authentication func type */
+ 0, /* auth key length in octets */
+ 0, /* auth tag length in octets */
+ sec_serv_conf /* security services flag */
+ },
+ {
+ SRTP_AES_ICM_128, /* cipher type */
+ SRTP_AES_ICM_128_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_NULL_AUTH, /* authentication func type */
+ 0, /* auth key length in octets */
+ 0, /* auth tag length in octets */
+ sec_serv_conf /* security services flag */
+ },
+ NULL,
+ (srtp_master_key_t **)test_keys,
+ 2, /* indicates the number of Master keys */
+ NULL, /* indicates that EKT is not in use */
+ 128, /* replay window size */
+ 0, /* retransmission not allowed */
+ NULL, /* no encrypted extension headers */
+ 0, /* list of encrypted extension headers is empty */
+ NULL
+};
+
+const srtp_policy_t hmac_only_policy = {
+ { ssrc_any_outbound, 0 }, /* SSRC */
+ {
+ SRTP_NULL_CIPHER, /* cipher type */
+ 0, /* cipher key length in octets */
+ SRTP_HMAC_SHA1, /* authentication func type */
+ 20, /* auth key length in octets */
+ 4, /* auth tag length in octets */
+ sec_serv_auth /* security services flag */
+ },
+ {
+ SRTP_NULL_CIPHER, /* cipher type */
+ 0, /* cipher key length in octets */
+ SRTP_HMAC_SHA1, /* authentication func type */
+ 20, /* auth key length in octets */
+ 4, /* auth tag length in octets */
+ sec_serv_auth /* security services flag */
+ },
+ NULL,
+ (srtp_master_key_t **)test_keys,
+ 2, /* Number of Master keys associated with the policy */
+ NULL, /* indicates that EKT is not in use */
+ 128, /* replay window size */
+ 0, /* retransmission not allowed */
+ NULL, /* no encrypted extension headers */
+ 0, /* list of encrypted extension headers is empty */
+ NULL
+};
+
+#ifdef GCM
+const srtp_policy_t aes128_gcm_8_policy = {
+ { ssrc_any_outbound, 0 }, /* SSRC */
+ {
+ /* SRTP policy */
+ SRTP_AES_GCM_128, /* cipher type */
+ SRTP_AES_GCM_128_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_NULL_AUTH, /* authentication func type */
+ 0, /* auth key length in octets */
+ 8, /* auth tag length in octets */
+ sec_serv_conf_and_auth /* security services flag */
+ },
+ {
+ /* SRTCP policy */
+ SRTP_AES_GCM_128, /* cipher type */
+ SRTP_AES_GCM_128_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_NULL_AUTH, /* authentication func type */
+ 0, /* auth key length in octets */
+ 8, /* auth tag length in octets */
+ sec_serv_conf_and_auth /* security services flag */
+ },
+ NULL,
+ (srtp_master_key_t **)test_keys,
+ 2, /* indicates the number of Master keys */
+ NULL, /* indicates that EKT is not in use */
+ 128, /* replay window size */
+ 0, /* retransmission not allowed */
+ NULL, /* no encrypted extension headers */
+ 0, /* list of encrypted extension headers is empty */
+ NULL
+};
+
+const srtp_policy_t aes128_gcm_8_cauth_policy = {
+ { ssrc_any_outbound, 0 }, /* SSRC */
+ {
+ /* SRTP policy */
+ SRTP_AES_GCM_128, /* cipher type */
+ SRTP_AES_GCM_128_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_NULL_AUTH, /* authentication func type */
+ 0, /* auth key length in octets */
+ 8, /* auth tag length in octets */
+ sec_serv_conf_and_auth /* security services flag */
+ },
+ {
+ /* SRTCP policy */
+ SRTP_AES_GCM_128, /* cipher type */
+ SRTP_AES_GCM_128_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_NULL_AUTH, /* authentication func type */
+ 0, /* auth key length in octets */
+ 8, /* auth tag length in octets */
+ sec_serv_auth /* security services flag */
+ },
+ NULL,
+ (srtp_master_key_t **)test_keys,
+ 2, /* indicates the number of Master keys */
+ NULL, /* indicates that EKT is not in use */
+ 128, /* replay window size */
+ 0, /* retransmission not allowed */
+ NULL, /* no encrypted extension headers */
+ 0, /* list of encrypted extension headers is empty */
+ NULL
+};
+
+const srtp_policy_t aes256_gcm_8_policy = {
+ { ssrc_any_outbound, 0 }, /* SSRC */
+ {
+ /* SRTP policy */
+ SRTP_AES_GCM_256, /* cipher type */
+ SRTP_AES_GCM_256_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_NULL_AUTH, /* authentication func type */
+ 0, /* auth key length in octets */
+ 8, /* auth tag length in octets */
+ sec_serv_conf_and_auth /* security services flag */
+ },
+ {
+ /* SRTCP policy */
+ SRTP_AES_GCM_256, /* cipher type */
+ SRTP_AES_GCM_256_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_NULL_AUTH, /* authentication func type */
+ 0, /* auth key length in octets */
+ 8, /* auth tag length in octets */
+ sec_serv_conf_and_auth /* security services flag */
+ },
+ NULL,
+ (srtp_master_key_t **)test_keys,
+ 2, /* indicates the number of Master keys */
+ NULL, /* indicates that EKT is not in use */
+ 128, /* replay window size */
+ 0, /* retransmission not allowed */
+ NULL, /* no encrypted extension headers */
+ 0, /* list of encrypted extension headers is empty */
+ NULL
+};
+
+const srtp_policy_t aes256_gcm_8_cauth_policy = {
+ { ssrc_any_outbound, 0 }, /* SSRC */
+ {
+ /* SRTP policy */
+ SRTP_AES_GCM_256, /* cipher type */
+ SRTP_AES_GCM_256_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_NULL_AUTH, /* authentication func type */
+ 0, /* auth key length in octets */
+ 8, /* auth tag length in octets */
+ sec_serv_conf_and_auth /* security services flag */
+ },
+ {
+ /* SRTCP policy */
+ SRTP_AES_GCM_256, /* cipher type */
+ SRTP_AES_GCM_256_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_NULL_AUTH, /* authentication func type */
+ 0, /* auth key length in octets */
+ 8, /* auth tag length in octets */
+ sec_serv_auth /* security services flag */
+ },
+ NULL,
+ (srtp_master_key_t **)test_keys,
+ 2, /* indicates the number of Master keys */
+ NULL, /* indicates that EKT is not in use */
+ 128, /* replay window size */
+ 0, /* retransmission not allowed */
+ NULL, /* no encrypted extension headers */
+ 0, /* list of encrypted extension headers is empty */
+ NULL
+};
+#endif
+
+const srtp_policy_t null_policy = {
+ { ssrc_any_outbound, 0 }, /* SSRC */
+ {
+ SRTP_NULL_CIPHER, /* cipher type */
+ 0, /* cipher key length in octets */
+ SRTP_NULL_AUTH, /* authentication func type */
+ 0, /* auth key length in octets */
+ 0, /* auth tag length in octets */
+ sec_serv_none /* security services flag */
+ },
+ {
+ SRTP_NULL_CIPHER, /* cipher type */
+ 0, /* cipher key length in octets */
+ SRTP_NULL_AUTH, /* authentication func type */
+ 0, /* auth key length in octets */
+ 0, /* auth tag length in octets */
+ sec_serv_none /* security services flag */
+ },
+ NULL,
+ (srtp_master_key_t **)test_keys,
+ 2, /* indicates the number of Master keys */
+ NULL, /* indicates that EKT is not in use */
+ 128, /* replay window size */
+ 0, /* retransmission not allowed */
+ NULL, /* no encrypted extension headers */
+ 0, /* list of encrypted extension headers is empty */
+ NULL
+};
+
+// clang-format off
+unsigned char test_256_key[46] = {
+ 0xf0, 0xf0, 0x49, 0x14, 0xb5, 0x13, 0xf2, 0x76,
+ 0x3a, 0x1b, 0x1f, 0xa1, 0x30, 0xf1, 0x0e, 0x29,
+ 0x98, 0xf6, 0xf6, 0xe4, 0x3e, 0x43, 0x09, 0xd1,
+ 0xe6, 0x22, 0xa0, 0xe3, 0x32, 0xb9, 0xf1, 0xb6,
+
+ 0x3b, 0x04, 0x80, 0x3d, 0xe5, 0x1e, 0xe7, 0xc9,
+ 0x64, 0x23, 0xab, 0x5b, 0x78, 0xd2
+};
+
+unsigned char test_256_key_2[46] = {
+ 0xe1, 0xf9, 0x7a, 0x0d, 0x3e, 0x01, 0x8b, 0xe0,
+ 0xd6, 0x4f, 0xa3, 0x2c, 0x06, 0xde, 0x41, 0x39,
+ 0x0e, 0xc6, 0x75, 0xad, 0x49, 0x8a, 0xfe, 0xeb,
+ 0xb6, 0x96, 0x0b, 0x3a, 0xab, 0xe6, 0xc1, 0x73,
+ 0x3b, 0x04, 0x80, 0x3d, 0xe5, 0x1e, 0xe7, 0xc9,
+ 0x64, 0x23, 0xab, 0x5b, 0x78, 0xd2
+};
+
+srtp_master_key_t master_256_key_1 = {
+ test_256_key,
+ test_mki_id,
+ TEST_MKI_ID_SIZE
+};
+
+srtp_master_key_t master_256_key_2 = {
+ test_256_key_2,
+ test_mki_id_2,
+ TEST_MKI_ID_SIZE
+};
+
+srtp_master_key_t *test_256_keys[2] = {
+ &master_key_1,
+ &master_key_2
+};
+// clang-format on
+
+const srtp_policy_t aes_256_hmac_policy = {
+ { ssrc_any_outbound, 0 }, /* SSRC */
+ {
+ /* SRTP policy */
+ SRTP_AES_ICM_256, /* cipher type */
+ SRTP_AES_ICM_256_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_HMAC_SHA1, /* authentication func type */
+ 20, /* auth key length in octets */
+ 10, /* auth tag length in octets */
+ sec_serv_conf_and_auth /* security services flag */
+ },
+ {
+ /* SRTCP policy */
+ SRTP_AES_ICM_256, /* cipher type */
+ SRTP_AES_ICM_256_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_HMAC_SHA1, /* authentication func type */
+ 20, /* auth key length in octets */
+ 10, /* auth tag length in octets */
+ sec_serv_conf_and_auth /* security services flag */
+ },
+ NULL,
+ (srtp_master_key_t **)test_256_keys,
+ 2, /* indicates the number of Master keys */
+ NULL, /* indicates that EKT is not in use */
+ 128, /* replay window size */
+ 0, /* retransmission not allowed */
+ NULL, /* no encrypted extension headers */
+ 0, /* list of encrypted extension headers is empty */
+ NULL
+};
+
+// clang-format off
+uint8_t ekt_test_key[16] = {
+ 0x77, 0x26, 0x9d, 0xac, 0x16, 0xa3, 0x28, 0xca,
+ 0x8e, 0xc9, 0x68, 0x4b, 0xcc, 0xc4, 0xd2, 0x1b
+};
+// clang-format on
+
+#include "ekt.h"
+
+// clang-format off
+srtp_ekt_policy_ctx_t ekt_test_policy = {
+ 0xa5a5, /* SPI */
+ SRTP_EKT_CIPHER_AES_128_ECB,
+ ekt_test_key,
+ NULL
+};
+// clang-format on
+
+const srtp_policy_t hmac_only_with_ekt_policy = {
+ { ssrc_any_outbound, 0 }, /* SSRC */
+ {
+ SRTP_NULL_CIPHER, /* cipher type */
+ 0, /* cipher key length in octets */
+ SRTP_HMAC_SHA1, /* authentication func type */
+ 20, /* auth key length in octets */
+ 4, /* auth tag length in octets */
+ sec_serv_auth /* security services flag */
+ },
+ {
+ SRTP_NULL_CIPHER, /* cipher type */
+ 0, /* cipher key length in octets */
+ SRTP_HMAC_SHA1, /* authentication func type */
+ 20, /* auth key length in octets */
+ 4, /* auth tag length in octets */
+ sec_serv_auth /* security services flag */
+ },
+ NULL,
+ (srtp_master_key_t **)test_keys,
+ 2, /* indicates the number of Master keys */
+ &ekt_test_policy, /* indicates that EKT is not in use */
+ 128, /* replay window size */
+ 0, /* retransmission not allowed */
+ NULL, /* no encrypted extension headers */
+ 0, /* list of encrypted extension headers is empty */
+ NULL
+};
+
+/*
+ * an array of pointers to the policies listed above
+ *
+ * This array is used to test various aspects of libSRTP for
+ * different cryptographic policies. The order of the elements
+ * matters - the timing test generates output that can be used
+ * in a plot (see the gnuplot script file 'timing'). If you
+ * add to this list, you should do it at the end.
+ */
+
+// clang-format off
+const srtp_policy_t *policy_array[] = {
+ &hmac_only_policy,
+ &aes_only_policy,
+ &default_policy,
+#ifdef GCM
+ &aes128_gcm_8_policy,
+ &aes128_gcm_8_cauth_policy,
+ &aes256_gcm_8_policy,
+ &aes256_gcm_8_cauth_policy,
+#endif
+ &null_policy,
+ &aes_256_hmac_policy,
+ &hmac_only_with_ekt_policy,
+ NULL
+};
+// clang-format on
+
+const srtp_policy_t wildcard_policy = {
+ { ssrc_any_outbound, 0 }, /* SSRC */
+ {
+ /* SRTP policy */
+ SRTP_AES_ICM_128, /* cipher type */
+ SRTP_AES_ICM_128_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_HMAC_SHA1, /* authentication func type */
+ 16, /* auth key length in octets */
+ 10, /* auth tag length in octets */
+ sec_serv_conf_and_auth /* security services flag */
+ },
+ {
+ /* SRTCP policy */
+ SRTP_AES_ICM_128, /* cipher type */
+ SRTP_AES_ICM_128_KEY_LEN_WSALT, /* cipher key length in octets */
+ SRTP_HMAC_SHA1, /* authentication func type */
+ 16, /* auth key length in octets */
+ 10, /* auth tag length in octets */
+ sec_serv_conf_and_auth /* security services flag */
+ },
+ test_key,
+ NULL,
+ 0,
+ NULL,
+ 128, /* replay window size */
+ 0, /* retransmission not allowed */
+ NULL, /* no encrypted extension headers */
+ 0, /* list of encrypted extension headers is empty */
+ NULL
+};
diff --git a/netwerk/srtp/src/test/test_srtp.c b/netwerk/srtp/src/test/test_srtp.c
new file mode 100644
index 0000000000..cdc9b5d5b7
--- /dev/null
+++ b/netwerk/srtp/src/test/test_srtp.c
@@ -0,0 +1,185 @@
+/*
+ * test_srtp.c
+ *
+ * Unit tests for internal srtp functions
+ *
+ * Cisco Systems, Inc.
+ *
+ */
+
+/*
+ *
+ * Copyright (c) 2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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.
+ *
+ */
+
+/*
+ * Test specific.
+ */
+#include "cutest.h"
+
+/*
+ * libSRTP specific.
+ */
+#include "../srtp/srtp.c" // Get access to static functions
+
+/*
+ * Standard library.
+ */
+
+/*
+ * Forward declarations for all tests.
+ */
+
+void srtp_calc_aead_iv_srtcp_all_zero_input_yield_zero_output(void);
+void srtp_calc_aead_iv_srtcp_seq_num_over_0x7FFFFFFF_bad_param(void);
+void srtp_calc_aead_iv_srtcp_distinct_iv_per_sequence_number(void);
+
+/*
+ * NULL terminated array of tests.
+ * The first item in the array is a char[] which give some information about
+ * what is being tested and is displayed to the user during runtime, the second
+ * item is the test function.
+ */
+
+TEST_LIST = { { "srtp_calc_aead_iv_srtcp_all_zero_input_yield_zero_output()",
+ srtp_calc_aead_iv_srtcp_all_zero_input_yield_zero_output },
+ { "srtp_calc_aead_iv_srtcp_seq_num_over_0x7FFFFFFF_bad_param()",
+ srtp_calc_aead_iv_srtcp_seq_num_over_0x7FFFFFFF_bad_param },
+ { "srtp_calc_aead_iv_srtcp_distinct_iv_per_sequence_number()",
+ srtp_calc_aead_iv_srtcp_distinct_iv_per_sequence_number },
+ { NULL } /* End of tests */ };
+
+/*
+ * Implementation.
+ */
+
+void srtp_calc_aead_iv_srtcp_all_zero_input_yield_zero_output()
+{
+ // Preconditions
+ srtp_session_keys_t session_keys;
+ v128_t init_vector;
+ srtcp_hdr_t header;
+ uint32_t sequence_num;
+
+ // Postconditions
+ srtp_err_status_t status;
+ const v128_t zero_vector;
+ memset((v128_t *)&zero_vector, 0, sizeof(v128_t));
+
+ // Given
+ memset(&session_keys, 0, sizeof(srtp_session_keys_t));
+ memset(&init_vector, 0, sizeof(v128_t));
+ memset(&header, 0, sizeof(srtcp_hdr_t));
+ sequence_num = 0x0UL;
+
+ // When
+ status = srtp_calc_aead_iv_srtcp(&session_keys, &init_vector, sequence_num,
+ &header);
+
+ // Then
+ TEST_CHECK(status == srtp_err_status_ok);
+ TEST_CHECK(memcmp(&zero_vector, &init_vector, sizeof(v128_t)) == 0);
+}
+
+void srtp_calc_aead_iv_srtcp_seq_num_over_0x7FFFFFFF_bad_param()
+{
+ // Preconditions
+ srtp_session_keys_t session_keys;
+ v128_t init_vector;
+ srtcp_hdr_t header;
+ uint32_t sequence_num;
+
+ // Postconditions
+ srtp_err_status_t status;
+
+ // Given
+ memset(&session_keys, 0, sizeof(srtp_session_keys_t));
+ memset(&init_vector, 0, sizeof(v128_t));
+ memset(&header, 0, sizeof(srtcp_hdr_t));
+ sequence_num = 0x7FFFFFFFUL + 0x1UL;
+
+ // When
+ status = srtp_calc_aead_iv_srtcp(&session_keys, &init_vector, sequence_num,
+ &header);
+
+ // Then
+ TEST_CHECK(status == srtp_err_status_bad_param);
+}
+
+/*
+ * Regression test for issue #256:
+ * Srtcp IV calculation incorrectly masks high bit of sequence number for
+ * little-endian platforms.
+ * Ensure that for each valid sequence number where the most significant bit is
+ * high that we get an expected and unique IV.
+ */
+void srtp_calc_aead_iv_srtcp_distinct_iv_per_sequence_number()
+{
+#define SAMPLE_COUNT (3)
+ // Preconditions
+ // Test each significant bit high in each full byte.
+ srtp_session_keys_t session_keys;
+ srtcp_hdr_t header;
+ v128_t output_iv[SAMPLE_COUNT];
+ memset(&output_iv, 0, SAMPLE_COUNT * sizeof(v128_t));
+ uint32_t sequence_num[SAMPLE_COUNT];
+ sequence_num[0] = 0xFF;
+ sequence_num[1] = 0xFF00;
+ sequence_num[2] = 0xFF0000;
+
+ // Postconditions
+ v128_t final_iv[SAMPLE_COUNT];
+ memset(&final_iv, 0, SAMPLE_COUNT * sizeof(v128_t));
+ final_iv[0].v8[11] = 0xFF;
+ final_iv[1].v8[10] = 0xFF;
+ final_iv[2].v8[9] = 0xFF;
+
+ // Given
+ memset(&session_keys, 0, sizeof(srtp_session_keys_t));
+ memset(&header, 0, sizeof(srtcp_hdr_t));
+
+ // When
+ size_t i = 0;
+ for (i = 0; i < SAMPLE_COUNT; i++) {
+ TEST_CHECK(srtp_calc_aead_iv_srtcp(&session_keys, &output_iv[i],
+ sequence_num[i],
+ &header) == srtp_err_status_ok);
+ }
+
+ // Then all IVs are as expected
+ for (i = 0; i < SAMPLE_COUNT; i++) {
+ TEST_CHECK(memcmp(&final_iv[i], &output_iv[i], sizeof(v128_t)) == 0);
+ }
+#undef SAMPLE_COUNT
+}
diff --git a/netwerk/srtp/src/test/util.c b/netwerk/srtp/src/test/util.c
new file mode 100644
index 0000000000..4465e90ccd
--- /dev/null
+++ b/netwerk/srtp/src/test/util.c
@@ -0,0 +1,211 @@
+/*
+ * util.c
+ *
+ * Utilities used by the test apps
+ *
+ * John A. Foley
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2014-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 "util.h"
+
+#include <string.h>
+#include <stdint.h>
+
+/* include space for null terminator */
+char bit_string[MAX_PRINT_STRING_LEN + 1];
+
+static inline int hex_char_to_nibble(uint8_t c)
+{
+ switch (c) {
+ case ('0'):
+ return 0x0;
+ case ('1'):
+ return 0x1;
+ case ('2'):
+ return 0x2;
+ case ('3'):
+ return 0x3;
+ case ('4'):
+ return 0x4;
+ case ('5'):
+ return 0x5;
+ case ('6'):
+ return 0x6;
+ case ('7'):
+ return 0x7;
+ case ('8'):
+ return 0x8;
+ case ('9'):
+ return 0x9;
+ case ('a'):
+ return 0xa;
+ case ('A'):
+ return 0xa;
+ case ('b'):
+ return 0xb;
+ case ('B'):
+ return 0xb;
+ case ('c'):
+ return 0xc;
+ case ('C'):
+ return 0xc;
+ case ('d'):
+ return 0xd;
+ case ('D'):
+ return 0xd;
+ case ('e'):
+ return 0xe;
+ case ('E'):
+ return 0xe;
+ case ('f'):
+ return 0xf;
+ case ('F'):
+ return 0xf;
+ default:
+ return -1; /* this flags an error */
+ }
+ /* NOTREACHED */
+ return -1; /* this keeps compilers from complaining */
+}
+
+uint8_t nibble_to_hex_char(uint8_t nibble)
+{
+ char buf[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ return buf[nibble & 0xF];
+}
+
+/*
+ * hex_string_to_octet_string converts a hexadecimal string
+ * of length 2 * len to a raw octet string of length len
+ */
+int hex_string_to_octet_string(char *raw, char *hex, int len)
+{
+ uint8_t x;
+ int tmp;
+ int hex_len;
+
+ hex_len = 0;
+ while (hex_len < len) {
+ tmp = hex_char_to_nibble(hex[0]);
+ if (tmp == -1) {
+ return hex_len;
+ }
+ x = (tmp << 4);
+ hex_len++;
+ tmp = hex_char_to_nibble(hex[1]);
+ if (tmp == -1) {
+ return hex_len;
+ }
+ x |= (tmp & 0xff);
+ hex_len++;
+ *raw++ = x;
+ hex += 2;
+ }
+ return hex_len;
+}
+
+char *octet_string_hex_string(const void *s, int length)
+{
+ const uint8_t *str = (const uint8_t *)s;
+ int i;
+
+ /* double length, since one octet takes two hex characters */
+ length *= 2;
+
+ /* truncate string if it would be too long */
+ if (length > MAX_PRINT_STRING_LEN) {
+ length = MAX_PRINT_STRING_LEN;
+ }
+
+ for (i = 0; i < length; i += 2) {
+ bit_string[i] = nibble_to_hex_char(*str >> 4);
+ bit_string[i + 1] = nibble_to_hex_char(*str++ & 0xF);
+ }
+ bit_string[i] = 0; /* null terminate string */
+ return bit_string;
+}
+
+static const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static int base64_block_to_octet_triple(char *out, char *in)
+{
+ unsigned char sextets[4] = { 0 };
+ int j = 0;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ char *p = strchr(b64chars, in[i]);
+ if (p != NULL) {
+ sextets[i] = p - b64chars;
+ } else {
+ j++;
+ }
+ }
+
+ out[0] = (sextets[0] << 2) | (sextets[1] >> 4);
+ if (j < 2) {
+ out[1] = (sextets[1] << 4) | (sextets[2] >> 2);
+ }
+ if (j < 1) {
+ out[2] = (sextets[2] << 6) | sextets[3];
+ }
+ return j;
+}
+
+int base64_string_to_octet_string(char *out, int *pad, char *in, int len)
+{
+ int k = 0;
+ int i = 0;
+ int j = 0;
+
+ if (len % 4 != 0) {
+ return 0;
+ }
+
+ while (i < len && j == 0) {
+ j = base64_block_to_octet_triple(out + k, in + i);
+ k += 3;
+ i += 4;
+ }
+ *pad = j;
+ return i;
+}
diff --git a/netwerk/srtp/src/test/util.h b/netwerk/srtp/src/test/util.h
new file mode 100644
index 0000000000..d04b279b3a
--- /dev/null
+++ b/netwerk/srtp/src/test/util.h
@@ -0,0 +1,53 @@
+/*
+ * util.h
+ *
+ * Utilities used by the test apps
+ *
+ * John A. Foley
+ * Cisco Systems, Inc.
+ */
+/*
+ *
+ * Copyright (c) 2014-2017, Cisco Systems, Inc.
+ * 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 the 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 HOLDERS 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 SRTP_TEST_UTIL_H
+#define SRTP_TEST_UTIL_H
+
+#define MAX_PRINT_STRING_LEN 1024
+
+int hex_string_to_octet_string(char *raw, char *hex, int len);
+char *octet_string_hex_string(const void *s, int length);
+int base64_string_to_octet_string(char *raw, int *pad, char *base64, int len);
+
+#endif
diff --git a/netwerk/srtp/src/test/words.txt b/netwerk/srtp/src/test/words.txt
new file mode 100644
index 0000000000..fe99c2d465
--- /dev/null
+++ b/netwerk/srtp/src/test/words.txt
@@ -0,0 +1,250 @@
+abducing
+acidheads
+acidness
+actons
+admixtures
+affidavit
+agelastic
+alated
+alimentary
+alleviated
+allseed
+annexure
+arragonite
+atonements
+autacoid
+axe
+axon
+ayres
+beathing
+blazonry
+bottom
+braising
+brehon
+brindisi
+broadcasts
+buds
+bulnbulns
+bushcraft
+calamander
+calipee
+casing
+caveat
+chaffings
+citifies
+clappers
+claques
+clavate
+colonial
+colonials
+commonalty
+compares
+consequent
+consumed
+contango
+courtierly
+creamery
+cruddiest
+cue
+cultish
+cumin
+cyclus
+dahlias
+dentitions
+derailers
+devitrify
+dibs
+diphysite
+disjunes
+drolleries
+dubitated
+dupion
+earliness
+eductor
+elenctic
+empresses
+entames
+epaulettes
+epicanthic
+epochal
+estated
+eurhythmic
+exfoliated
+extremity
+fayence
+figgery
+flaming
+foes
+forelays
+forewings
+forfeits
+fratches
+gardened
+gentile
+glumpish
+glyph
+goatherd
+grow
+gulden
+gumming
+hackling
+hanapers
+hared
+hatters
+hectare
+hedger
+heel
+heterodox
+hidden
+histologic
+howe
+inglobe
+inliers
+inuredness
+iotacism
+japed
+jelled
+jiffy
+jollies
+judgeship
+karite
+kart
+kenophobia
+kittens
+lactarian
+lancets
+leasable
+leep
+leming
+licorice
+listing
+lividly
+lobectomy
+lysosome
+madders
+maderizing
+manacle
+mangels
+marshiest
+maulstick
+meliorates
+mercy
+mikados
+monarchise
+moultings
+mucro
+munnions
+mystic
+myxoedemic
+nointing
+nong
+nonsense
+ochidore
+octuor
+officering
+opaqued
+oragious
+outtell
+oxeye
+pads
+palamae
+pansophy
+parazoa
+pepsines
+perimetric
+pheasant
+phonotypy
+pitarah
+plaintful
+poinders
+poke
+politer
+poonces
+populism
+pouty
+praedial
+presence
+prompter
+pummelled
+punishing
+quippish
+radicality
+radiuses
+rebuffing
+recorded
+redips
+regulators
+replay
+retrocedes
+rigors
+risen
+rootstocks
+rotenone
+rudenesses
+ruggedest
+runabout
+ruthfully
+sagacious
+scapes
+sclera
+sclerotium
+scumbering
+secondi
+serial
+shampoo
+showed
+sights
+sirenised
+sized
+slave
+socle
+solidness
+some
+spetches
+spiels
+squiring
+staminode
+stay
+stewpot
+stunsails
+subhumid
+subprogram
+supawn
+surplusage
+swimming
+swineherd
+tabun
+talliths
+taroks
+tensed
+thinnings
+three
+tipper
+toko
+tomahawks
+tombolos
+torpefy
+torulae
+touns
+travails
+tsarist
+unbeseems
+unblamably
+unbooked
+unnailed
+updates
+valorise
+viability
+virtue
+vulturns
+vulvate
+warran
+weakness
+westernise
+whingeings
+wrenching
+written
+yak
+yate
+yaupon
+zendiks
diff --git a/netwerk/srtp/srtp_update.log b/netwerk/srtp/srtp_update.log
new file mode 100644
index 0000000000..3ebbbfcbe1
--- /dev/null
+++ b/netwerk/srtp/srtp_update.log
@@ -0,0 +1,3 @@
+srtp updated from CVS on Fri Sep 21 14:51:37 EDT 2012
+srtp updated to revision 8f38517394a45678cd4468febf69f75722f35d00 from git on Wed Nov 22 14:15:32 PST 2017
+srtp updated to revision bb0412ee84ebe3d2916b45b19de72fabb183d9db from git on Tue Sep 11 21:51:05 PDT 2018
diff --git a/netwerk/srtp/update_srtp.sh b/netwerk/srtp/update_srtp.sh
new file mode 100644
index 0000000000..61fa7c4518
--- /dev/null
+++ b/netwerk/srtp/update_srtp.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# assume $1 is the directory with a git checkout of the libsrtp source
+#
+# srtp source is available at: https://github.com/cisco/libsrtp/
+#
+# also assumes we've made *NO* changes to the SRTP sources! If we do, we have to merge by
+# hand after this process, or use a more complex one.
+#
+# For example, one could update an srtp library import head, and merge back to default. Or keep a
+# separate repo with this in it, and pull from there to m-c and merge.
+if [ "$1" ] ; then
+ DATE=`date`
+ REVISION=`(cd $1; git log --pretty=oneline | head -1 | cut -c 1-40)`
+ cp -rf $1/srtp $1/crypto $1/include $1/test $1/LICENSE $1/README.md netwerk/srtp/src
+
+ hg addremove netwerk/srtp/src --include "netwerk/srtp/src/VERSION" --include "netwerk/srtp/src/LICENSE" --include "netwerk/srtp/src/README.md" --include "**.c" --include "**.h" --similarity 90
+
+ echo "srtp updated to revision $REVISION from git on $DATE" >> netwerk/srtp/srtp_update.log
+ echo "srtp updated to revision $REVISION from git on $DATE"
+ echo "WARNING: reapply any local patches!"
+else
+ echo "usage: $0 path_to_srtp_directory"
+ echo "run from the root of your m-c clone"
+fi
diff --git a/netwerk/streamconv/converters/ParseFTPList.cpp b/netwerk/streamconv/converters/ParseFTPList.cpp
new file mode 100644
index 0000000000..892ca91e6e
--- /dev/null
+++ b/netwerk/streamconv/converters/ParseFTPList.cpp
@@ -0,0 +1,1493 @@
+/* -*- 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 "ParseFTPList.h"
+#include <algorithm>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "plstr.h"
+#include "nsDebug.h"
+#include "prprf.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/TextUtils.h"
+#include "mozilla/Sprintf.h"
+
+/* ==================================================================== */
+
+using mozilla::CheckedInt;
+using mozilla::IsAsciiAlpha;
+using mozilla::IsAsciiAlphanumeric;
+using mozilla::IsAsciiDigit;
+using mozilla::IsAsciiLowercaseAlpha;
+
+static const int kMaxFTPListLen = 32768;
+
+static inline int ParsingFailed(struct list_state* state) {
+ if (state->parsed_one || state->lstyle) /* junk if we fail to parse */
+ return '?'; /* this time but had previously parsed successfully */
+ return '"'; /* its part of a comment or error message */
+}
+
+void FixupYear(PRExplodedTime* aTime) {
+ /* if year has only two digits then assume that
+ 00-79 is 2000-2079
+ 80-99 is 1980-1999 */
+ if (aTime->tm_year < 80) {
+ aTime->tm_year += 2000;
+ } else if (aTime->tm_year < 100) {
+ aTime->tm_year += 1900;
+ }
+}
+
+int ParseFTPList(const char* line, struct list_state* state,
+ struct list_result* result, PRTimeParamFn timeParam,
+ NowTimeFn nowTimeFn) {
+ unsigned int carry_buf_len; /* copy of state->carry_buf_len */
+ unsigned int pos;
+ const char* p;
+
+ if (!line || !state || !result) return 0;
+
+ memset(result, 0, sizeof(*result));
+ state->numlines++;
+
+ /* carry buffer is only valid from one line to the next */
+ carry_buf_len = state->carry_buf_len;
+ state->carry_buf_len = 0;
+
+ /* strip leading whitespace */
+ while (*line == ' ' || *line == '\t') line++;
+
+ /* line is terminated at first '\0' or '\n' */
+ p = line;
+ while (*p && *p != '\n') p++;
+ unsigned int linelen = p - line;
+
+ if (linelen > 0 && *p == '\n' && *(p - 1) == '\r') linelen--;
+
+ /* DON'T strip trailing whitespace. */
+
+ if (linelen > kMaxFTPListLen) {
+ return ParsingFailed(state);
+ }
+
+ if (linelen > 0) {
+ static const char* month_names = "JanFebMarAprMayJunJulAugSepOctNovDec";
+ const char* tokens[16]; /* 16 is more than enough */
+ unsigned int toklen[(sizeof(tokens) / sizeof(tokens[0]))];
+ unsigned int linelen_sans_wsp; // line length sans whitespace
+ unsigned int numtoks = 0;
+ unsigned int tokmarker = 0; /* extra info for lstyle handler */
+ unsigned int month_num = 0;
+ char tbuf[4];
+ int lstyle = 0;
+
+ if (carry_buf_len) /* VMS long filename carryover buffer */
+ {
+ tokens[0] = state->carry_buf;
+ toklen[0] = carry_buf_len;
+ numtoks++;
+ }
+
+ pos = 0;
+ while (pos < linelen && numtoks < (sizeof(tokens) / sizeof(tokens[0]))) {
+ while (pos < linelen &&
+ (line[pos] == ' ' || line[pos] == '\t' || line[pos] == '\r'))
+ pos++;
+ if (pos < linelen) {
+ tokens[numtoks] = &line[pos];
+ while (pos < linelen &&
+ (line[pos] != ' ' && line[pos] != '\t' && line[pos] != '\r'))
+ pos++;
+ if (tokens[numtoks] != &line[pos]) {
+ toklen[numtoks] = (&line[pos] - tokens[numtoks]);
+ numtoks++;
+ }
+ }
+ }
+
+ if (!numtoks) return ParsingFailed(state);
+
+ linelen_sans_wsp = &(tokens[numtoks - 1][toklen[numtoks - 1]]) - tokens[0];
+ if (numtoks == (sizeof(tokens) / sizeof(tokens[0]))) {
+ pos = linelen;
+ while (pos > 0 && (line[pos - 1] == ' ' || line[pos - 1] == '\t')) pos--;
+ linelen_sans_wsp = pos;
+ }
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_EPLF)
+ /* EPLF handling must come somewhere before /bin/dls handling. */
+ if (!lstyle && (!state->lstyle || state->lstyle == 'E')) {
+ if (*line == '+' && linelen > 4 && numtoks >= 2) {
+ pos = 1;
+ while (pos < (linelen - 1)) {
+ p = &line[pos++];
+ if (*p == '/')
+ result->fe_type = 'd'; /* its a dir */
+ else if (*p == 'r')
+ result->fe_type = 'f'; /* its a file */
+ else if (*p == 'm') {
+ if (IsAsciiDigit(line[pos])) {
+ while (pos < linelen && IsAsciiDigit(line[pos])) pos++;
+ if (pos < linelen && line[pos] == ',') {
+ PRTime t;
+ PRTime seconds;
+ PR_sscanf(p + 1, "%llu", &seconds);
+ t = seconds * PR_USEC_PER_SEC;
+ PR_ExplodeTime(t, timeParam, &(result->fe_time));
+ }
+ }
+ } else if (*p == 's') {
+ if (IsAsciiDigit(line[pos])) {
+ while (pos < linelen && IsAsciiDigit(line[pos])) pos++;
+ if (pos < linelen && line[pos] == ',' &&
+ ((&line[pos]) - (p + 1)) < int(sizeof(result->fe_size) - 1)) {
+ memcpy(result->fe_size, p + 1,
+ (unsigned)(&line[pos] - (p + 1)));
+ result->fe_size[(&line[pos] - (p + 1))] = '\0';
+ }
+ }
+ } else if (IsAsciiAlpha(
+ *p)) /* 'i'/'up' or unknown "fact" (property) */
+ {
+ while (pos < linelen && *++p != ',') pos++;
+ } else if (*p != '\t' || (p + 1) != tokens[1]) {
+ break; /* its not EPLF after all */
+ } else {
+ state->parsed_one = 1;
+ state->lstyle = lstyle = 'E';
+
+ p = &(line[linelen_sans_wsp]);
+ result->fe_fname = tokens[1];
+ result->fe_fnlen = p - tokens[1];
+
+ if (!result->fe_type) /* access denied */
+ {
+ result->fe_type = 'f'; /* is assuming 'f'ile correct? */
+ return '?'; /* NO! junk it. */
+ }
+ return result->fe_type;
+ }
+ if (pos >= (linelen - 1) || line[pos] != ',') break;
+ pos++;
+ } /* while (pos < linelen) */
+ memset(result, 0, sizeof(*result));
+ } /* if (*line == '+' && linelen > 4 && numtoks >= 2) */
+ } /* if (!lstyle && (!state->lstyle || state->lstyle == 'E')) */
+#endif /* SUPPORT_EPLF */
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_VMS)
+ if (!lstyle &&
+ (!state->lstyle ||
+ state->lstyle == 'V')) { /* try VMS Multinet/UCX/CMS server */
+ /*
+ * Legal characters in a VMS file/dir spec are [A-Z0-9$.-_~].
+ * '$' cannot begin a filename and `-' cannot be used as the first
+ * or last character. '.' is only valid as a directory separator
+ * and <file>.<type> separator. A canonical filename spec might look
+ * like this: DISK$VOL:[DIR1.DIR2.DIR3]FILE.TYPE;123
+ * All VMS FTP servers LIST in uppercase.
+ *
+ * We need to be picky about this in order to support
+ * multi-line listings correctly.
+ */
+ if (!state->parsed_one &&
+ (numtoks == 1 || (numtoks == 2 && toklen[0] == 9 &&
+ memcmp(tokens[0], "Directory", 9) == 0))) {
+ /* If no dirstyle has been detected yet, and this line is a
+ * VMS list's dirname, then turn on VMS dirstyle.
+ * eg "ACA:[ANONYMOUS]", "DISK$FTP:[ANONYMOUS]", "SYS$ANONFTP:"
+ */
+ p = tokens[0];
+ pos = toklen[0];
+ if (numtoks == 2) {
+ p = tokens[1];
+ pos = toklen[1];
+ }
+ pos--;
+ if (pos >= 3) {
+ while (pos > 0 && p[pos] != '[') {
+ pos--;
+ if (p[pos] == '-' || p[pos] == '$') {
+ if (pos == 0 || p[pos - 1] == '[' || p[pos - 1] == '.' ||
+ (p[pos] == '-' && (p[pos + 1] == ']' || p[pos + 1] == '.')))
+ break;
+ } else if (p[pos] != '.' && p[pos] != '~' &&
+ !IsAsciiAlphanumeric(p[pos]))
+ break;
+ else if (IsAsciiLowercaseAlpha(p[pos]))
+ break;
+ }
+ if (pos > 0) {
+ pos--;
+ if (p[pos] != ':' || p[pos + 1] != '[') pos = 0;
+ }
+ }
+ if (pos > 0 && p[pos] == ':') {
+ while (pos > 0) {
+ pos--;
+ if (p[pos] != '$' && p[pos] != '_' && p[pos] != '-' &&
+ p[pos] != '~' && !IsAsciiAlphanumeric(p[pos]))
+ break;
+ else if (IsAsciiLowercaseAlpha(p[pos]))
+ break;
+ }
+ if (pos == 0) {
+ state->lstyle = 'V';
+ return '?'; /* its junk */
+ }
+ }
+ /* fallthrough */
+ } else if ((tokens[0][toklen[0] - 1]) != ';') {
+ if (numtoks == 1 && (state->lstyle == 'V' && !carry_buf_len))
+ lstyle = 'V';
+ else if (numtoks < 4)
+ ;
+ else if (toklen[1] >= 10 && memcmp(tokens[1], "%RMS-E-PRV", 10) == 0)
+ lstyle = 'V';
+ else if ((&line[linelen] - tokens[1]) >= 22 &&
+ memcmp(tokens[1], "insufficient privilege", 22) == 0)
+ lstyle = 'V';
+ else if (numtoks != 4 && numtoks != 6)
+ ;
+ else if (numtoks == 6 &&
+ (toklen[5] < 4 || *tokens[5] != '(' || /* perms */
+ (tokens[5][toklen[5] - 1]) != ')'))
+ ;
+ else if ((toklen[2] == 10 || toklen[2] == 11) &&
+ (tokens[2][toklen[2] - 5]) == '-' &&
+ (tokens[2][toklen[2] - 9]) == '-' &&
+ (((toklen[3] == 4 || toklen[3] == 5 || toklen[3] == 7 ||
+ toklen[3] == 8) &&
+ (tokens[3][toklen[3] - 3]) == ':') ||
+ ((toklen[3] == 10 || toklen[3] == 11) &&
+ (tokens[3][toklen[3] - 3]) ==
+ '.')) && /* time in [H]H:MM[:SS[.CC]] format */
+ IsAsciiDigit(*tokens[1]) && /* size */
+ IsAsciiDigit(*tokens[2]) && /* date */
+ IsAsciiDigit(*tokens[3]) /* time */
+ ) {
+ lstyle = 'V';
+ }
+ if (lstyle == 'V') {
+ // clang-format off
+ /*
+ * MultiNet FTP:
+ * LOGIN.COM;2 1 4-NOV-1994 04:09 [ANONYMOUS] (RWE,RWE,,)
+ * PUB.DIR;1 1 27-JAN-1994 14:46 [ANONYMOUS] (RWE,RWE,RE,RWE)
+ * README.FTP;1 %RMS-E-PRV, insufficient privilege or file protection violation
+ * ROUSSOS.DIR;1 1 27-JAN-1994 14:48 [CS,ROUSSOS] (RWE,RWE,RE,R)
+ * S67-50903.JPG;1 328 22-SEP-1998 16:19 [ANONYMOUS] (RWED,RWED,,)
+ * UCX FTP:
+ * CII-MANUAL.TEX;1 213/216 29-JAN-1996 03:33:12 [ANONYMOU,ANONYMOUS] (RWED,RWED,,)
+ * CMU/VMS-IP FTP
+ * [VMSSERV.FILES]ALARM.DIR;1 1/3 5-MAR-1993 18:09
+ * TCPware FTP
+ * FOO.BAR;1 4 5-MAR-1993 18:09:01.12
+ * Long filename example:
+ * THIS-IS-A-LONG-VMS-FILENAME.AND-THIS-IS-A-LONG-VMS-FILETYPE\r\n
+ * 213[/nnn] 29-JAN-1996 03:33[:nn] [ANONYMOU,ANONYMOUS] (RWED,RWED,,)
+ */
+ // clang-format on
+ tokmarker = 0;
+ p = tokens[0];
+ pos = 0;
+ if (*p == '[' && toklen[0] >= 4) /* CMU style */
+ {
+ if (p[1] != ']') {
+ p++;
+ pos++;
+ }
+ while (lstyle && pos < toklen[0] && *p != ']') {
+ if (*p != '$' && *p != '.' && *p != '_' && *p != '-' &&
+ *p != '~' && !IsAsciiAlphanumeric(*p))
+ lstyle = 0;
+ pos++;
+ p++;
+ }
+ if (lstyle && pos < (toklen[0] - 1)) {
+ /* ']' was found and there is at least one character after it */
+ NS_ASSERTION(*p == ']', "unexpected state");
+ pos++;
+ p++;
+ tokmarker = pos; /* length of leading "[DIR1.DIR2.etc]" */
+ } else {
+ /* not a CMU style listing */
+ lstyle = 0;
+ }
+ }
+ while (lstyle && pos < toklen[0] && *p != ';') {
+ if (*p != '$' && *p != '.' && *p != '_' && *p != '-' && *p != '~' &&
+ !IsAsciiAlphanumeric(*p))
+ lstyle = 0;
+ else if (IsAsciiLowercaseAlpha(*p))
+ lstyle = 0;
+ p++;
+ pos++;
+ }
+ if (lstyle && *p == ';') {
+ if (pos == 0 || pos == (toklen[0] - 1)) lstyle = 0;
+ for (pos++; lstyle && pos < toklen[0]; pos++) {
+ if (!IsAsciiDigit(tokens[0][pos])) lstyle = 0;
+ }
+ }
+ pos = (p - tokens[0]); /* => fnlength sans ";####" */
+ pos -= tokmarker; /* => fnlength sans "[DIR1.DIR2.etc]" */
+ p = &(tokens[0][tokmarker]); /* offset of basename */
+
+ if (!lstyle || pos == 0 ||
+ pos > 80) /* VMS filenames can't be longer than that */
+ {
+ lstyle = 0;
+ } else if (numtoks == 1) {
+ /* if VMS has been detected and there is only one token and that
+ * token was a VMS filename then this is a multiline VMS LIST entry.
+ */
+ if (pos >= (sizeof(state->carry_buf) - 1))
+ pos = (sizeof(state->carry_buf) - 1); /* shouldn't happen */
+ memcpy(state->carry_buf, p, pos);
+ state->carry_buf_len = pos;
+ return '?'; /* tell caller to treat as junk */
+ } else if (IsAsciiDigit(*tokens[1])) /* not no-privs message */
+ {
+ for (pos = 0; lstyle && pos < (toklen[1]); pos++) {
+ if (!IsAsciiDigit((tokens[1][pos])) && (tokens[1][pos]) != '/')
+ lstyle = 0;
+ }
+ if (lstyle && numtoks > 4) /* Multinet or UCX but not CMU */
+ {
+ for (pos = 1; lstyle && pos < (toklen[5] - 1); pos++) {
+ p = &(tokens[5][pos]);
+ if (*p != 'R' && *p != 'W' && *p != 'E' && *p != 'D' &&
+ *p != ',')
+ lstyle = 0;
+ }
+ }
+ }
+ } /* passed initial tests */
+ } /* else if ((tokens[0][toklen[0]-1]) != ';') */
+
+ if (lstyle == 'V') {
+ state->parsed_one = 1;
+ state->lstyle = lstyle;
+
+ if (IsAsciiDigit(*tokens[1])) /* not permission denied etc */
+ {
+ /* strip leading directory name */
+ if (*tokens[0] == '[') /* CMU server */
+ {
+ pos = toklen[0] - 1;
+ p = tokens[0] + 1;
+ while (*p != ']') {
+ p++;
+ pos--;
+ }
+ toklen[0] = --pos;
+ tokens[0] = ++p;
+ }
+ pos = 0;
+ while (pos < toklen[0] && (tokens[0][pos]) != ';') pos++;
+
+ result->fe_cinfs = 1;
+ result->fe_type = 'f';
+ result->fe_fname = tokens[0];
+ result->fe_fnlen = pos;
+
+ if (pos > 4) {
+ p = &(tokens[0][pos - 4]);
+ if (p[0] == '.' && p[1] == 'D' && p[2] == 'I' && p[3] == 'R') {
+ result->fe_fnlen -= 4;
+ result->fe_type = 'd';
+ }
+ }
+
+ if (result->fe_type != 'd') {
+ /* #### or used/allocated form. If used/allocated form, then
+ * 'used' is the size in bytes if and only if 'used'<=allocated.
+ * If 'used' is size in bytes then it can be > 2^32
+ * If 'used' is not size in bytes then it is size in blocks.
+ */
+ pos = 0;
+ while (pos < toklen[1] && (tokens[1][pos]) != '/') pos++;
+
+ /*
+ * On OpenVMS, the size is given in blocks. A block is 512
+ * bytes. This can only approximate the size of the file,
+ * but that's better than not showing a size at all.
+ * numBlocks is clamped to UINT32_MAX to make 32-bit and
+ * 64-bit builds return consistent results.
+ */
+ uint64_t numBlocks = strtoul(tokens[1], nullptr, 10);
+ numBlocks = std::min(numBlocks, (uint64_t)UINT32_MAX);
+ uint64_t fileSize = numBlocks * 512;
+ SprintfLiteral(result->fe_size, "%" PRIu64, fileSize);
+ } /* if (result->fe_type != 'd') */
+
+ p = tokens[2] + 2;
+ if (*p == '-') p++;
+ tbuf[0] = p[0];
+ tbuf[1] = ToLowerCaseASCII(p[1]);
+ tbuf[2] = ToLowerCaseASCII(p[2]);
+ month_num = 0;
+ for (pos = 0; pos < (12 * 3); pos += 3) {
+ if (tbuf[0] == month_names[pos + 0] &&
+ tbuf[1] == month_names[pos + 1] &&
+ tbuf[2] == month_names[pos + 2])
+ break;
+ month_num++;
+ }
+ if (month_num >= 12) month_num = 0;
+ result->fe_time.tm_month = month_num;
+ result->fe_time.tm_mday = atoi(tokens[2]);
+ result->fe_time.tm_year = atoi(p + 4); // NSPR wants year as XXXX
+
+ p = tokens[3] + 2;
+ if (*p == ':') p++;
+ if (p[2] == ':') result->fe_time.tm_sec = atoi(p + 3);
+ result->fe_time.tm_hour = atoi(tokens[3]);
+ result->fe_time.tm_min = atoi(p);
+
+ return result->fe_type;
+
+ } /* if (IsAsciiDigit(*tokens[1])) */
+
+ return '?'; /* junk */
+
+ } /* if (lstyle == 'V') */
+ } /* if (!lstyle && (!state->lstyle || state->lstyle == 'V')) */
+#endif
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_CMS)
+ /* Virtual Machine/Conversational Monitor System (IBM Mainframe) */
+ if (!lstyle && (!state->lstyle || state->lstyle == 'C')) /* VM/CMS */
+ {
+ /* LISTing according to mirror.pl
+ * Filename FileType Fm Format Lrecl Records Blocks Date Time
+ * LASTING GLOBALV A1 V 41 21 1 9/16/91 15:10:32
+ * J43401 NETLOG A0 V 77 1 1 9/12/91 12:36:04
+ * PROFILE EXEC A1 V 17 3 1 9/12/91 12:39:07
+ * DIRUNIX SCRIPT A1 V 77 1216 17 1/04/93 20:30:47
+ * MAIL PROFILE A2 F 80 1 1 10/14/92 16:12:27
+ * BADY2K TEXT A0 V 1 1 1 1/03/102 10:11:12
+ * AUTHORS A1 DIR - - - 9/20/99 10:31:11
+ *
+ * LISTing from vm.marist.edu and vm.sc.edu
+ * 220-FTPSERVE IBM VM Level 420 at VM.MARIST.EDU, 04:58:12 EDT WEDNESDAY
+ * 2002-07-10 AUTHORS DIR - - -
+ * 1999-09-20 10:31:11 - HARRINGTON DIR - - -
+ * 1997-02-12 15:33:28 - PICS DIR - - -
+ * 2000-10-12 15:43:23 - SYSFILE DIR - - -
+ * 2000-07-20 17:48:01 - WELCNVT EXEC V 72 9 1
+ * 1999-09-20 17:16:18 - WELCOME EREADME F 80 21 1
+ * 1999-12-27 16:19:00 - WELCOME README V 82 21 1
+ * 1999-12-27 16:19:04 - README ANONYMOU V 71 26 1
+ * 1997-04-02 12:33:20 TCP291 README ANONYOLD V 71 15 1
+ * 1995-08-25 16:04:27 TCP291
+ */
+ if (numtoks >= 7 && (toklen[0] + toklen[1]) <= 16) {
+ for (pos = 1; !lstyle && (pos + 5) < numtoks; pos++) {
+ p = tokens[pos];
+ if ((toklen[pos] == 1 && (*p == 'F' || *p == 'V')) ||
+ (toklen[pos] == 3 && *p == 'D' && p[1] == 'I' && p[2] == 'R')) {
+ if (toklen[pos + 5] == 8 && (tokens[pos + 5][2]) == ':' &&
+ (tokens[pos + 5][5]) == ':') {
+ p = tokens[pos + 4];
+ if ((toklen[pos + 4] == 10 && p[4] == '-' && p[7] == '-') ||
+ (toklen[pos + 4] >= 7 && toklen[pos + 4] <= 9 &&
+ p[((p[1] != '/') ? (2) : (1))] == '/' &&
+ p[((p[1] != '/') ? (5) : (4))] == '/'))
+ /* Y2K bugs possible ("7/06/102" or "13/02/101") */
+ {
+ if ((*tokens[pos + 1] == '-' && *tokens[pos + 2] == '-' &&
+ *tokens[pos + 3] == '-') ||
+ (IsAsciiDigit(*tokens[pos + 1]) &&
+ IsAsciiDigit(*tokens[pos + 2]) &&
+ IsAsciiDigit(*tokens[pos + 3]))) {
+ lstyle = 'C';
+ tokmarker = pos;
+ }
+ }
+ }
+ }
+ } /* for (pos = 1; !lstyle && (pos+5) < numtoks; pos++) */
+ } /* if (numtoks >= 7) */
+
+ /* extra checking if first pass */
+ if (lstyle && !state->lstyle) {
+ for (pos = 0, p = tokens[0]; lstyle && pos < toklen[0]; pos++, p++) {
+ if (IsAsciiLowercaseAlpha(*p)) lstyle = 0;
+ }
+ for (pos = tokmarker + 1; pos <= tokmarker + 3; pos++) {
+ if (!(toklen[pos] == 1 && *tokens[pos] == '-')) {
+ for (p = tokens[pos]; lstyle && p < (tokens[pos] + toklen[pos]);
+ p++) {
+ if (!IsAsciiDigit(*p)) lstyle = 0;
+ }
+ }
+ }
+ for (pos = 0, p = tokens[tokmarker + 4];
+ lstyle && pos < toklen[tokmarker + 4]; pos++, p++) {
+ if (*p == '/') {
+ /* There may be Y2K bugs in the date. Don't simplify to
+ * pos != (len-3) && pos != (len-6) like time is done.
+ */
+ if ((tokens[tokmarker + 4][1]) == '/') {
+ if (pos != 1 && pos != 4) lstyle = 0;
+ } else if (pos != 2 && pos != 5)
+ lstyle = 0;
+ } else if (*p != '-' && !IsAsciiDigit(*p))
+ lstyle = 0;
+ else if (*p == '-' && pos != 4 && pos != 7)
+ lstyle = 0;
+ }
+ for (pos = 0, p = tokens[tokmarker + 5];
+ lstyle && pos < toklen[tokmarker + 5]; pos++, p++) {
+ if (*p != ':' && !IsAsciiDigit(*p))
+ lstyle = 0;
+ else if (*p == ':' && pos != (toklen[tokmarker + 5] - 3) &&
+ pos != (toklen[tokmarker + 5] - 6))
+ lstyle = 0;
+ }
+ } /* initial if() */
+
+ if (lstyle == 'C') {
+ state->parsed_one = 1;
+ state->lstyle = lstyle;
+
+ p = tokens[tokmarker + 4];
+ if (toklen[tokmarker + 4] == 10) /* newstyle: YYYY-MM-DD format */
+ {
+ result->fe_time.tm_year = atoi(p + 0);
+ result->fe_time.tm_month = atoi(p + 5) - 1;
+ result->fe_time.tm_mday = atoi(p + 8);
+ } else /* oldstyle: [M]M/DD/YY format */
+ {
+ pos = toklen[tokmarker + 4];
+ result->fe_time.tm_month = atoi(p) - 1;
+ result->fe_time.tm_mday = atoi((p + pos) - 5);
+ result->fe_time.tm_year = atoi((p + pos) - 2);
+ FixupYear(&result->fe_time);
+ }
+
+ p = tokens[tokmarker + 5];
+ pos = toklen[tokmarker + 5];
+ result->fe_time.tm_hour = atoi(p);
+ result->fe_time.tm_min = atoi((p + pos) - 5);
+ result->fe_time.tm_sec = atoi((p + pos) - 2);
+
+ result->fe_cinfs = 1;
+ result->fe_fname = tokens[0];
+ result->fe_fnlen = toklen[0];
+ result->fe_type = 'f';
+
+ p = tokens[tokmarker];
+ if (toklen[tokmarker] == 3 && *p == 'D' && p[1] == 'I' && p[2] == 'R')
+ result->fe_type = 'd';
+
+ if ((/*newstyle*/ toklen[tokmarker + 4] == 10 && tokmarker > 1) ||
+ (/*oldstyle*/ toklen[tokmarker + 4] != 10 &&
+ tokmarker > 2)) { /* have a filetype column */
+ char* dot;
+ p = &(tokens[0][toklen[0]]);
+ memcpy(&dot, &p, sizeof(dot)); /* NASTY! */
+ *dot++ = '.';
+ p = tokens[1];
+ for (pos = 0; pos < toklen[1]; pos++) *dot++ = *p++;
+ result->fe_fnlen += 1 + toklen[1];
+ }
+
+ /* oldstyle LISTING:
+ * files/dirs not on the 'A' minidisk are not RETRievable/CHDIRable
+ if (toklen[tokmarker+4] != 10 && *tokens[tokmarker-1] != 'A')
+ return '?';
+ */
+
+ /* VM/CMS LISTings have no usable filesize field.
+ * Have to use the 'SIZE' command for that.
+ */
+ return result->fe_type;
+
+ } /* if (lstyle == 'C' && (!state->lstyle || state->lstyle == lstyle)) */
+ } /* VM/CMS */
+#endif
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_DOS) /* WinNT DOS dirstyle */
+ if (!lstyle && (!state->lstyle || state->lstyle == 'W')) {
+ // clang-format off
+ /*
+ * "10-23-00 01:27PM <DIR> veronist"
+ * "06-15-00 07:37AM <DIR> zoe"
+ * "07-14-00 01:35PM 2094926 canprankdesk.tif"
+ * "07-21-00 01:19PM 95077 Jon Kauffman Enjoys the Good Life.jpg"
+ * "07-21-00 01:19PM 52275 Name Plate.jpg"
+ * "07-14-00 01:38PM 2250540 Valentineoffprank-HiRes.jpg"
+ */
+ // Microsoft FTP server with FtpDirBrowseShowLongDate set returns year
+ // in 4-digit format:
+ // "10-10-2014 10:10AM <DIR> FTP"
+ // Windows CE FTP server returns time in 24-hour format:
+ // "05-03-13 22:01 <DIR> APPS"
+ // clang-format on
+ if ((numtoks >= 4) && (toklen[0] == 8 || toklen[0] == 10) &&
+ (toklen[1] == 5 || toklen[1] == 7) &&
+ (*tokens[2] == '<' || IsAsciiDigit(*tokens[2]))) {
+ p = tokens[0];
+ if (IsAsciiDigit(p[0]) && IsAsciiDigit(p[1]) && p[2] == '-' &&
+ IsAsciiDigit(p[3]) && IsAsciiDigit(p[4]) && p[5] == '-' &&
+ IsAsciiDigit(p[6]) && IsAsciiDigit(p[7])) {
+ p = tokens[1];
+ if (IsAsciiDigit(p[0]) && IsAsciiDigit(p[1]) && p[2] == ':' &&
+ IsAsciiDigit(p[3]) && IsAsciiDigit(p[4]) &&
+ (toklen[1] == 5 ||
+ (toklen[1] == 7 && (p[5] == 'A' || p[5] == 'P') &&
+ p[6] == 'M'))) {
+ lstyle = 'W';
+ if (!state->lstyle) {
+ p = tokens[2];
+ /* <DIR> or <JUNCTION> */
+ if (*p != '<' || p[toklen[2] - 1] != '>') {
+ for (pos = 1; (lstyle && pos < toklen[2]); pos++) {
+ if (!IsAsciiDigit(*++p)) lstyle = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (lstyle == 'W') {
+ state->parsed_one = 1;
+ state->lstyle = lstyle;
+
+ p = &(line[linelen]); /* line end */
+ result->fe_cinfs = 1;
+ result->fe_fname = tokens[3];
+ result->fe_fnlen = p - tokens[3];
+ result->fe_type = 'd';
+
+ if (*tokens[2] != '<') /* not <DIR> or <JUNCTION> */
+ {
+ // try to handle correctly spaces at the beginning of the filename
+ // filesize (token[2]) must end at offset 38
+ if (tokens[2] + toklen[2] - line == 38) {
+ result->fe_fname = &(line[39]);
+ result->fe_fnlen = p - result->fe_fname;
+ }
+ result->fe_type = 'f';
+ pos = toklen[2];
+ if (pos > (sizeof(result->fe_size) - 1)) {
+ pos = (sizeof(result->fe_size) - 1);
+ }
+ memcpy(result->fe_size, tokens[2], pos);
+ result->fe_size[pos] = '\0';
+ } else {
+ // try to handle correctly spaces at the beginning of the filename
+ // token[2] must begin at offset 24, the length is 5 or 10
+ // token[3] must begin at offset 39 or higher
+ if (tokens[2] - line == 24 && (toklen[2] == 5 || toklen[2] == 10) &&
+ tokens[3] - line >= 39) {
+ result->fe_fname = &(line[39]);
+ result->fe_fnlen = p - result->fe_fname;
+ }
+
+ if ((tokens[2][1]) != 'D') /* not <DIR> */
+ {
+ result->fe_type = '?'; /* unknown until junc for sure */
+ if (result->fe_fnlen > 4) {
+ p = result->fe_fname;
+ for (pos = result->fe_fnlen - 4; pos > 0; pos--) {
+ if (p[0] == ' ' && p[3] == ' ' && p[2] == '>' &&
+ (p[1] == '=' || p[1] == '-')) {
+ result->fe_type = 'l';
+ result->fe_fnlen = p - result->fe_fname;
+ result->fe_lname = p + 4;
+ result->fe_lnlen = &(line[linelen]) - result->fe_lname;
+ break;
+ }
+ p++;
+ }
+ }
+ }
+ }
+
+ result->fe_time.tm_month = atoi(tokens[0] + 0);
+ if (result->fe_time.tm_month != 0) {
+ result->fe_time.tm_month--;
+ result->fe_time.tm_mday = atoi(tokens[0] + 3);
+ result->fe_time.tm_year = atoi(tokens[0] + 6);
+ FixupYear(&result->fe_time);
+ }
+
+ result->fe_time.tm_hour = atoi(tokens[1] + 0);
+ result->fe_time.tm_min = atoi(tokens[1] + 3);
+ if (toklen[1] == 7) {
+ if ((tokens[1][5]) == 'P' && result->fe_time.tm_hour < 12)
+ result->fe_time.tm_hour += 12;
+ else if ((tokens[1][5]) == 'A' && result->fe_time.tm_hour == 12)
+ result->fe_time.tm_hour = 0;
+ }
+
+ /* the caller should do this (if dropping "." and ".." is desired)
+ if (result->fe_type == 'd' && result->fe_fname[0] == '.' &&
+ (result->fe_fnlen == 1 || (result->fe_fnlen == 2 &&
+ result->fe_fname[1] == '.')))
+ return '?';
+ */
+
+ return result->fe_type;
+ } /* if (lstyle == 'W' && (!state->lstyle || state->lstyle == lstyle)) */
+ } /* if (!lstyle && (!state->lstyle || state->lstyle == 'W')) */
+#endif
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_OS2)
+ if (!lstyle && (!state->lstyle || state->lstyle == 'O')) /* OS/2 test */
+ {
+ /* 220 server IBM TCP/IP for OS/2 - FTP Server ver 23:04:36 on Jan 15 1997
+ *ready. fixed position, space padded columns. I have only a vague idea of
+ *what the contents between col 18 and 34 might be: All I can infer is
+ *that there may be attribute flags in there and there may be a " DIR" in
+ *there.
+ *
+ * 1 2 3 4 5 6
+ *0123456789012345678901234567890123456789012345678901234567890123456789
+ *----- size -------|??????????????? MM-DD-YY| HH:MM| nnnnnnnnn....
+ * 0 DIR 04-11-95 16:26 .
+ * 0 DIR 04-11-95 16:26 ..
+ * 0 DIR 04-11-95 16:26 ADDRESS
+ * 612 RHSA 07-28-95 16:45 air_tra1.bag
+ * 195 A 08-09-95 10:23 Alfa1.bag
+ * 0 RHS DIR 04-11-95 16:26 ATTACH
+ * 372 A 08-09-95 10:26 Aussie_1.bag
+ * 310992 06-28-94 09:56 INSTALL.EXE
+ * 1 2 3 4
+ * 01234567890123456789012345678901234567890123456789
+ * dirlist from the mirror.pl project, col positions from Mozilla.
+ */
+ p = &(line[toklen[0]]);
+ /* \s(\d\d-\d\d-\d\d)\s+(\d\d:\d\d)\s */
+ if (numtoks >= 4 && toklen[0] <= 18 && IsAsciiDigit(*tokens[0]) &&
+ (linelen - toklen[0]) >= (54 - 18) && p[18 - 18] == ' ' &&
+ p[34 - 18] == ' ' && p[37 - 18] == '-' && p[40 - 18] == '-' &&
+ p[43 - 18] == ' ' && p[45 - 18] == ' ' && p[48 - 18] == ':' &&
+ p[51 - 18] == ' ' && IsAsciiDigit(p[35 - 18]) &&
+ IsAsciiDigit(p[36 - 18]) && IsAsciiDigit(p[38 - 18]) &&
+ IsAsciiDigit(p[39 - 18]) && IsAsciiDigit(p[41 - 18]) &&
+ IsAsciiDigit(p[42 - 18]) && IsAsciiDigit(p[46 - 18]) &&
+ IsAsciiDigit(p[47 - 18]) && IsAsciiDigit(p[49 - 18]) &&
+ IsAsciiDigit(p[50 - 18]) &&
+ (linelen_sans_wsp - toklen[0]) > (53 - 18)) {
+ lstyle = 'O'; /* OS/2 */
+ if (!state->lstyle) {
+ for (pos = 1; lstyle && pos < toklen[0]; pos++) {
+ if (!IsAsciiDigit(tokens[0][pos])) lstyle = 0;
+ }
+ }
+ }
+
+ if (lstyle == 'O') {
+ state->parsed_one = 1;
+ state->lstyle = lstyle;
+
+ p = &(line[toklen[0]]);
+
+ result->fe_cinfs = 1;
+ result->fe_fname = &p[53 - 18];
+ result->fe_fnlen = (&(line[linelen_sans_wsp])) - (result->fe_fname);
+ result->fe_type = 'f';
+
+ /* I don't have a real listing to determine exact pos, so scan. */
+ for (pos = (18 - 18); pos < ((35 - 18) - 4); pos++) {
+ if (p[pos + 0] == ' ' && p[pos + 1] == 'D' && p[pos + 2] == 'I' &&
+ p[pos + 3] == 'R') {
+ result->fe_type = 'd';
+ break;
+ }
+ }
+
+ if (result->fe_type != 'd') {
+ pos = toklen[0];
+ if (pos > (sizeof(result->fe_size) - 1))
+ pos = (sizeof(result->fe_size) - 1);
+ memcpy(result->fe_size, tokens[0], pos);
+ result->fe_size[pos] = '\0';
+ }
+
+ result->fe_time.tm_month = atoi(&p[35 - 18]) - 1;
+ result->fe_time.tm_mday = atoi(&p[38 - 18]);
+ result->fe_time.tm_year = atoi(&p[41 - 18]);
+ FixupYear(&result->fe_time);
+ result->fe_time.tm_hour = atoi(&p[46 - 18]);
+ result->fe_time.tm_min = atoi(&p[49 - 18]);
+
+ /* the caller should do this (if dropping "." and ".." is desired)
+ if (result->fe_type == 'd' && result->fe_fname[0] == '.' &&
+ (result->fe_fnlen == 1 || (result->fe_fnlen == 2 &&
+ result->fe_fname[1] == '.')))
+ return '?';
+ */
+
+ return result->fe_type;
+ } /* if (lstyle == 'O') */
+
+ } /* if (!lstyle && (!state->lstyle || state->lstyle == 'O')) */
+#endif
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_LSL)
+ if (!lstyle && (!state->lstyle || state->lstyle == 'U')) /* /bin/ls & co. */
+ {
+ /* UNIX-style listing, without inum and without blocks
+ * "-rw-r--r-- 1 root other 531 Jan 29 03:26 README"
+ * "dr-xr-xr-x 2 root other 512 Apr 8 1994 etc"
+ * "dr-xr-xr-x 2 root 512 Apr 8 1994 etc"
+ * "lrwxrwxrwx 1 root other 7 Jan 25 00:17 bin -> usr/bin"
+ * Also produced by Microsoft's FTP servers for Windows:
+ * "---------- 1 owner group 1803128 Jul 10 10:18 ls-lR.Z"
+ * "d--------- 1 owner group 0 May 9 19:45 Softlib"
+ * Also WFTPD for MSDOS:
+ * "-rwxrwxrwx 1 noone nogroup 322 Aug 19 1996 message.ftp"
+ * Hellsoft for NetWare:
+ * "d[RWCEMFA] supervisor 512 Jan 16 18:53 login"
+ * "-[RWCEMFA] rhesus 214059 Oct 20 15:27 cx.exe"
+ * Newer Hellsoft for NetWare: (netlab2.usu.edu)
+ * - [RWCEAFMS] NFAUUser 192 Apr 27 15:21 HEADER.html
+ * d [RWCEAFMS] jrd 512 Jul 11 03:01 allupdates
+ * Also NetPresenz for the Mac:
+ * "-------r-- 326 1391972 1392298 Nov 22 1995 MegaPhone.sit"
+ * "drwxrwxr-x folder 2 May 10 1996 network"
+ * Protected directory:
+ * "drwx-wx-wt 2 root wheel 512 Jul 1 02:15 incoming"
+ * uid/gid instead of username/groupname:
+ * "drwxr-xr-x 2 0 0 512 May 28 22:17 etc"
+ */
+
+ bool is_old_Hellsoft = false;
+
+ if (numtoks >= 6) {
+ /* there are two perm formats (Hellsoft/NetWare and *IX strmode(3)).
+ * Scan for size column only if the perm format is one or the other.
+ */
+ if (toklen[0] == 1 || (tokens[0][1]) == '[') {
+ if (*tokens[0] == 'd' || *tokens[0] == '-') {
+ pos = toklen[0] - 1;
+ p = tokens[0] + 1;
+ if (pos == 0) {
+ p = tokens[1];
+ pos = toklen[1];
+ }
+ if ((pos == 9 || pos == 10) && (*p == '[' && p[pos - 1] == ']') &&
+ (p[1] == 'R' || p[1] == '-') && (p[2] == 'W' || p[2] == '-') &&
+ (p[3] == 'C' || p[3] == '-') && (p[4] == 'E' || p[4] == '-')) {
+ /* rest is FMA[S] or AFM[S] */
+ lstyle = 'U'; /* very likely one of the NetWare servers */
+ if (toklen[0] == 10) is_old_Hellsoft = true;
+ }
+ }
+ } else if ((toklen[0] == 10 || toklen[0] == 11) &&
+ strchr("-bcdlpsw?DFam", *tokens[0])) {
+ p = &(tokens[0][1]);
+ if ((p[0] == 'r' || p[0] == '-') && (p[1] == 'w' || p[1] == '-') &&
+ (p[3] == 'r' || p[3] == '-') && (p[4] == 'w' || p[4] == '-') &&
+ (p[6] == 'r' || p[6] == '-') && (p[7] == 'w' || p[7] == '-'))
+ /* 'x'/p[9] can be S|s|x|-|T|t or implementation specific */
+ {
+ lstyle = 'U'; /* very likely /bin/ls */
+ }
+ }
+ }
+ if (lstyle == 'U') /* first token checks out */
+ {
+ lstyle = 0;
+ for (pos = (numtoks - 5); !lstyle && pos > 1; pos--) {
+ /* scan for: (\d+)\s+([A-Z][a-z][a-z])\s+
+ * (\d\d\d\d|\d\:\d\d|\d\d\:\d\d|\d\:\d\d\:\d\d|\d\d\:\d\d\:\d\d)
+ * \s+(.+)$
+ */
+ if (IsAsciiDigit(*tokens[pos]) /* size */
+ /* (\w\w\w) */
+ && toklen[pos + 1] == 3 && IsAsciiAlpha(*tokens[pos + 1]) &&
+ IsAsciiAlpha(tokens[pos + 1][1]) &&
+ IsAsciiAlpha(tokens[pos + 1][2])
+ /* (\d|\d\d) */
+ && IsAsciiDigit(*tokens[pos + 2]) &&
+ (toklen[pos + 2] == 1 ||
+ (toklen[pos + 2] == 2 && IsAsciiDigit(tokens[pos + 2][1]))) &&
+ toklen[pos + 3] >= 4 &&
+ IsAsciiDigit(*tokens[pos + 3])
+ /* (\d\:\d\d\:\d\d|\d\d\:\d\d\:\d\d) */
+ && (toklen[pos + 3] <= 5 ||
+ ((toklen[pos + 3] == 7 || toklen[pos + 3] == 8) &&
+ (tokens[pos + 3][toklen[pos + 3] - 3]) == ':')) &&
+ IsAsciiDigit(tokens[pos + 3][toklen[pos + 3] - 2]) &&
+ IsAsciiDigit(tokens[pos + 3][toklen[pos + 3] - 1]) &&
+ (
+ /* (\d\d\d\d) */
+ ((toklen[pos + 3] == 4 || toklen[pos + 3] == 5) &&
+ IsAsciiDigit(tokens[pos + 3][1]) &&
+ IsAsciiDigit(tokens[pos + 3][2]))
+ /* (\d\:\d\d|\d\:\d\d\:\d\d) */
+ || ((toklen[pos + 3] == 4 || toklen[pos + 3] == 7) &&
+ (tokens[pos + 3][1]) == ':' &&
+ IsAsciiDigit(tokens[pos + 3][2]) &&
+ IsAsciiDigit(tokens[pos + 3][3]))
+ /* (\d\d\:\d\d|\d\d\:\d\d\:\d\d) */
+ || ((toklen[pos + 3] == 5 || toklen[pos + 3] == 8) &&
+ IsAsciiDigit(tokens[pos + 3][1]) &&
+ (tokens[pos + 3][2]) == ':' &&
+ IsAsciiDigit(tokens[pos + 3][3]) &&
+ IsAsciiDigit(tokens[pos + 3][4])))) {
+ lstyle = 'U'; /* assume /bin/ls or variant format */
+ tokmarker = pos;
+
+ /* check that size is numeric */
+ p = tokens[tokmarker];
+ unsigned int i;
+ for (i = 0; i < toklen[tokmarker]; i++) {
+ if (!IsAsciiDigit(*p++)) {
+ lstyle = 0;
+ break;
+ }
+ }
+ if (lstyle) {
+ month_num = 0;
+ p = tokens[tokmarker + 1];
+ for (i = 0; i < (12 * 3); i += 3) {
+ if (p[0] == month_names[i + 0] && p[1] == month_names[i + 1] &&
+ p[2] == month_names[i + 2])
+ break;
+ month_num++;
+ }
+ if (month_num >= 12) lstyle = 0;
+ }
+ } /* relative position test */
+ } /* for (pos = (numtoks-5); !lstyle && pos > 1; pos--) */
+ } /* if (lstyle == 'U') */
+
+ if (lstyle == 'U') {
+ state->parsed_one = 1;
+ state->lstyle = lstyle;
+
+ result->fe_cinfs = 0;
+ result->fe_type = '?';
+ if (*tokens[0] == 'd' || *tokens[0] == 'l')
+ result->fe_type = *tokens[0];
+ else if (*tokens[0] == 'D')
+ result->fe_type = 'd';
+ else if (*tokens[0] == '-' || *tokens[0] == 'F')
+ result->fe_type = 'f'; /* (hopefully a regular file) */
+
+ if (result->fe_type != 'd') {
+ pos = toklen[tokmarker];
+ if (pos > (sizeof(result->fe_size) - 1))
+ pos = (sizeof(result->fe_size) - 1);
+ memcpy(result->fe_size, tokens[tokmarker], pos);
+ result->fe_size[pos] = '\0';
+ }
+
+ result->fe_time.tm_month = month_num;
+ result->fe_time.tm_mday = atoi(tokens[tokmarker + 2]);
+ if (result->fe_time.tm_mday == 0) result->fe_time.tm_mday++;
+
+ p = tokens[tokmarker + 3];
+ pos = (unsigned int)atoi(p);
+ if (p[1] == ':') /* one digit hour */
+ p--;
+ if (p[2] != ':') /* year */
+ {
+ result->fe_time.tm_year = pos;
+ } else {
+ result->fe_time.tm_hour = pos;
+ result->fe_time.tm_min = atoi(p + 3);
+ if (p[5] == ':') result->fe_time.tm_sec = atoi(p + 6);
+
+ if (!state->now_time) {
+ state->now_time = nowTimeFn();
+ PR_ExplodeTime((state->now_time), timeParam, &(state->now_tm));
+ }
+
+ result->fe_time.tm_year = state->now_tm.tm_year;
+ if (((state->now_tm.tm_month << 5) + state->now_tm.tm_mday) <
+ ((result->fe_time.tm_month << 5) + result->fe_time.tm_mday))
+ result->fe_time.tm_year--;
+
+ } /* time/year */
+
+ // The length of the whole date string should be 12. On AIX the length
+ // is only 11 when the year is present in the date string and there is
+ // 1 padding space at the end of the string. In both cases the filename
+ // starts at offset 13 from the start of the date string.
+ // Don't care about leading spaces when the date string has different
+ // format or when old Hellsoft output was detected.
+ {
+ const char* date_start = tokens[tokmarker + 1];
+ const char* date_end = tokens[tokmarker + 3] + toklen[tokmarker + 3];
+ if (!is_old_Hellsoft &&
+ ((date_end - date_start) == 12 ||
+ ((date_end - date_start) == 11 && date_end[1] == ' ')))
+ result->fe_fname = date_start + 13;
+ else
+ result->fe_fname = tokens[tokmarker + 4];
+ }
+
+ result->fe_fnlen = (&(line[linelen])) - (result->fe_fname);
+
+ if (result->fe_type == 'l' && result->fe_fnlen > 4) {
+ /* First try to use result->fe_size to find " -> " sequence.
+ This can give proper result for cases like "aaa -> bbb -> ccc". */
+ uintptr_t fe_size = atoi(result->fe_size);
+ CheckedInt<uintptr_t> arrow_start(result->fe_fnlen);
+ arrow_start -= fe_size;
+ arrow_start -= 4;
+
+ if (arrow_start.isValid() &&
+ PL_strncmp(result->fe_fname + arrow_start.value(), " -> ", 4) ==
+ 0) {
+ result->fe_lname = result->fe_fname + (result->fe_fnlen - fe_size);
+ result->fe_lnlen = (&(line[linelen])) - (result->fe_lname);
+ result->fe_fnlen = arrow_start.value();
+ } else {
+ /* Search for sequence " -> " from the end for case when there are
+ more occurrences. F.e. if ftpd returns "a -> b -> c" assume
+ "a -> b" as a name. Powerusers can remove unnecessary parts
+ manually but there is no way to follow the link when some
+ essential part is missing. */
+ p = result->fe_fname + (result->fe_fnlen - 5);
+ for (pos = (result->fe_fnlen - 5); pos > 0; pos--) {
+ if (PL_strncmp(p, " -> ", 4) == 0) {
+ result->fe_lname = p + 4;
+ result->fe_lnlen = (&(line[linelen])) - (result->fe_lname);
+ result->fe_fnlen = pos;
+ break;
+ }
+ p--;
+ }
+ }
+ }
+
+# if defined(SUPPORT_LSLF) /* some (very rare) servers return ls -lF */
+ if (result->fe_fnlen > 1) {
+ p = result->fe_fname[result->fe_fnlen - 1];
+ pos = result->fe_type;
+ if (pos == 'd') {
+ if (*p == '/') result->fe_fnlen--; /* directory */
+ } else if (pos == 'l') {
+ if (*p == '@') result->fe_fnlen--; /* symlink */
+ } else if (pos == 'f') {
+ if (*p == '*') result->fe_fnlen--; /* executable */
+ } else if (*p == '=' || *p == '%' || *p == '|') {
+ result->fe_fnlen--; /* socket, whiteout, fifo */
+ }
+ }
+# endif
+
+ /* the caller should do this (if dropping "." and ".." is desired)
+ if (result->fe_type == 'd' && result->fe_fname[0] == '.' &&
+ (result->fe_fnlen == 1 || (result->fe_fnlen == 2 &&
+ result->fe_fname[1] == '.')))
+ return '?';
+ */
+
+ return result->fe_type;
+
+ } /* if (lstyle == 'U') */
+
+ } /* if (!lstyle && (!state->lstyle || state->lstyle == 'U')) */
+#endif
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_W16) /* 16bit Windows */
+ if (!lstyle &&
+ (!state->lstyle ||
+ state->lstyle == 'w')) { /* old SuperTCP suite FTP server for Win3.1 */
+ /* old NetManage Chameleon TCP/IP suite FTP server for Win3.1 */
+ /*
+ * SuperTCP dirlist from the mirror.pl project
+ * mon/day/year separator may be '/' or '-'.
+ * . <DIR> 11-16-94 17:16
+ * .. <DIR> 11-16-94 17:16
+ * INSTALL <DIR> 11-16-94 17:17
+ * CMT <DIR> 11-21-94 10:17
+ * DESIGN1.DOC 11264 05-11-95 14:20
+ * README.TXT 1045 05-10-95 11:01
+ * WPKIT1.EXE 960338 06-21-95 17:01
+ * CMT.CSV 0 07-06-95 14:56
+ *
+ * Chameleon dirlist guessed from lynx
+ * . <DIR> Nov 16 1994 17:16
+ * .. <DIR> Nov 16 1994 17:16
+ * INSTALL <DIR> Nov 16 1994 17:17
+ * CMT <DIR> Nov 21 1994 10:17
+ * DESIGN1.DOC 11264 May 11 1995 14:20 A
+ * README.TXT 1045 May 10 1995 11:01
+ * WPKIT1.EXE 960338 Jun 21 1995 17:01 R
+ * CMT.CSV 0 Jul 06 1995 14:56 RHA
+ */
+ if (numtoks >= 4 && toklen[0] < 13 &&
+ ((toklen[1] == 5 && *tokens[1] == '<') || IsAsciiDigit(*tokens[1]))) {
+ if (numtoks == 4 && (toklen[2] == 8 || toklen[2] == 9) &&
+ (((tokens[2][2]) == '/' && (tokens[2][5]) == '/') ||
+ ((tokens[2][2]) == '-' && (tokens[2][5]) == '-')) &&
+ (toklen[3] == 4 || toklen[3] == 5) &&
+ (tokens[3][toklen[3] - 3]) == ':' && IsAsciiDigit(tokens[2][0]) &&
+ IsAsciiDigit(tokens[2][1]) && IsAsciiDigit(tokens[2][3]) &&
+ IsAsciiDigit(tokens[2][4]) && IsAsciiDigit(tokens[2][6]) &&
+ IsAsciiDigit(tokens[2][7]) &&
+ (toklen[2] < 9 || IsAsciiDigit(tokens[2][8])) &&
+ IsAsciiDigit(tokens[3][toklen[3] - 1]) &&
+ IsAsciiDigit(tokens[3][toklen[3] - 2]) &&
+ IsAsciiDigit(tokens[3][toklen[3] - 4]) &&
+ IsAsciiDigit(*tokens[3])) {
+ lstyle = 'w';
+ } else if ((numtoks == 6 || numtoks == 7) && toklen[2] == 3 &&
+ toklen[3] == 2 && toklen[4] == 4 && toklen[5] == 5 &&
+ (tokens[5][2]) == ':' && IsAsciiAlpha(tokens[2][0]) &&
+ IsAsciiAlpha(tokens[2][1]) && IsAsciiAlpha(tokens[2][2]) &&
+ IsAsciiDigit(tokens[3][0]) && IsAsciiDigit(tokens[3][1]) &&
+ IsAsciiDigit(tokens[4][0]) && IsAsciiDigit(tokens[4][1]) &&
+ IsAsciiDigit(tokens[4][2]) && IsAsciiDigit(tokens[4][3]) &&
+ IsAsciiDigit(tokens[5][0]) && IsAsciiDigit(tokens[5][1]) &&
+ IsAsciiDigit(tokens[5][3]) && IsAsciiDigit(tokens[5][4])
+ /* could also check that (&(tokens[5][5]) - tokens[2]) == 17
+ */
+ ) {
+ lstyle = 'w';
+ }
+ if (lstyle && state->lstyle != lstyle) /* first time */
+ {
+ p = tokens[1];
+ if (toklen[1] != 5 || p[0] != '<' || p[1] != 'D' || p[2] != 'I' ||
+ p[3] != 'R' || p[4] != '>') {
+ for (pos = 0; lstyle && pos < toklen[1]; pos++) {
+ if (!IsAsciiDigit(*p++)) lstyle = 0;
+ }
+ } /* not <DIR> */
+ } /* if (first time) */
+ } /* if (numtoks == ...) */
+
+ if (lstyle == 'w') {
+ state->parsed_one = 1;
+ state->lstyle = lstyle;
+
+ result->fe_cinfs = 1;
+ result->fe_fname = tokens[0];
+ result->fe_fnlen = toklen[0];
+ result->fe_type = 'd';
+
+ p = tokens[1];
+ if (IsAsciiDigit(*p)) {
+ result->fe_type = 'f';
+ pos = toklen[1];
+ if (pos > (sizeof(result->fe_size) - 1))
+ pos = sizeof(result->fe_size) - 1;
+ memcpy(result->fe_size, p, pos);
+ result->fe_size[pos] = '\0';
+ }
+
+ p = tokens[2];
+ if (toklen[2] == 3) /* Chameleon */
+ {
+ tbuf[0] = ToUpperCaseASCII(p[0]);
+ tbuf[1] = ToLowerCaseASCII(p[1]);
+ tbuf[2] = ToLowerCaseASCII(p[2]);
+ for (pos = 0; pos < (12 * 3); pos += 3) {
+ if (tbuf[0] == month_names[pos + 0] &&
+ tbuf[1] == month_names[pos + 1] &&
+ tbuf[2] == month_names[pos + 2]) {
+ result->fe_time.tm_month = pos / 3;
+ result->fe_time.tm_mday = atoi(tokens[3]);
+ result->fe_time.tm_year = atoi(tokens[4]);
+ break;
+ }
+ }
+ pos = 5; /* Chameleon toknum of date field */
+ } else {
+ result->fe_time.tm_month = atoi(p + 0) - 1;
+ result->fe_time.tm_mday = atoi(p + 3);
+ result->fe_time.tm_year = atoi(p + 6);
+ FixupYear(&result->fe_time); /* SuperTCP */
+
+ pos = 3; /* SuperTCP toknum of date field */
+ }
+
+ result->fe_time.tm_hour = atoi(tokens[pos]);
+ result->fe_time.tm_min = atoi(&(tokens[pos][toklen[pos] - 2]));
+
+ /* the caller should do this (if dropping "." and ".." is desired)
+ if (result->fe_type == 'd' && result->fe_fname[0] == '.' &&
+ (result->fe_fnlen == 1 || (result->fe_fnlen == 2 &&
+ result->fe_fname[1] == '.')))
+ return '?';
+ */
+
+ return result->fe_type;
+ } /* (lstyle == 'w') */
+
+ } /* if (!lstyle && (!state->lstyle || state->lstyle == 'w')) */
+#endif
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_DLS) /* dls -dtR */
+ if (!lstyle &&
+ (state->lstyle == 'D' || (!state->lstyle && state->numlines == 1)))
+ /* /bin/dls lines have to be immediately recognizable (first line) */
+ {
+ /* I haven't seen an FTP server that delivers a /bin/dls listing,
+ * but can infer the format from the lynx and mirror.pl projects.
+ * Both formats are supported.
+ *
+ * Lynx says:
+ * README 763 Information about this server\0
+ * bin/ - \0
+ * etc/ = \0
+ * ls-lR 0 \0
+ * ls-lR.Z 3 \0
+ * pub/ = Public area\0
+ * usr/ - \0
+ * morgan 14 -> ../real/morgan\0
+ * TIMIT.mostlikely.Z\0
+ * 79215 \0
+ *
+ * mirror.pl says:
+ * filename: ^(\S*)\s+
+ * size: (\-|\=|\d+)\s+
+ * month/day: ((\w\w\w\s+\d+|\d+\s+\w\w\w)\s+
+ * time/year: (\d+:\d+|\d\d\d\d))\s+
+ * rest: (.+)
+ *
+ * README 763 Jul 11 21:05 Information about this server
+ * bin/ - Apr 28 1994
+ * etc/ = 11 Jul 21:04
+ * ls-lR 0 6 Aug 17:14
+ * ls-lR.Z 3 05 Sep 1994
+ * pub/ = Jul 11 21:04 Public area
+ * usr/ - Sep 7 09:39
+ * morgan 14 Apr 18 09:39 -> ../real/morgan
+ * TIMIT.mostlikely.Z
+ * 79215 Jul 11 21:04
+ */
+ if (!state->lstyle && line[linelen - 1] == ':' && linelen >= 2 &&
+ toklen[numtoks - 1] != 1) {
+ /* code in mirror.pl suggests that a listing may be preceded
+ * by a PWD line in the form "/some/dir/names/here:"
+ * but does not necessarily begin with '/'. *sigh*
+ */
+ pos = 0;
+ p = line;
+ while (pos < (linelen - 1)) {
+ /* illegal (or extremely unusual) chars in a dirspec */
+ if (*p == '<' || *p == '|' || *p == '>' || *p == '?' || *p == '*' ||
+ *p == '\\')
+ break;
+ if (*p == '/' && pos < (linelen - 2) && p[1] == '/') break;
+ pos++;
+ p++;
+ }
+ if (pos == (linelen - 1)) {
+ state->lstyle = 'D';
+ return '?';
+ }
+ }
+
+ if (!lstyle && numtoks >= 2) {
+ pos = 22; /* pos of (\d+|-|=) if this is not part of a multiline */
+ if (state->lstyle && carry_buf_len) /* first is from previous line */
+ pos = toklen[1] - 1; /* and is 'as-is' (may contain whitespace) */
+
+ if (linelen > pos) {
+ p = &line[pos];
+ if ((*p == '-' || *p == '=' || IsAsciiDigit(*p)) &&
+ ((linelen == (pos + 1)) ||
+ (linelen >= (pos + 3) && p[1] == ' ' && p[2] == ' '))) {
+ tokmarker = 1;
+ if (!carry_buf_len) {
+ pos = 1;
+ while (pos < numtoks && (tokens[pos] + toklen[pos]) < (&line[23]))
+ pos++;
+ tokmarker = 0;
+ if ((tokens[pos] + toklen[pos]) == (&line[23])) tokmarker = pos;
+ }
+ if (tokmarker) {
+ lstyle = 'D';
+ if (*tokens[tokmarker] == '-' || *tokens[tokmarker] == '=') {
+ if (toklen[tokmarker] != 1 ||
+ (tokens[tokmarker - 1][toklen[tokmarker - 1] - 1]) != '/')
+ lstyle = 0;
+ } else {
+ for (pos = 0; lstyle && pos < toklen[tokmarker]; pos++) {
+ if (!IsAsciiDigit(tokens[tokmarker][pos])) lstyle = 0;
+ }
+ }
+ if (lstyle && !state->lstyle) /* first time */
+ {
+ /* scan for illegal (or incredibly unusual) chars in fname */
+ for (p = tokens[0];
+ lstyle &&
+ p < &(tokens[tokmarker - 1][toklen[tokmarker - 1]]);
+ p++) {
+ if (*p == '<' || *p == '|' || *p == '>' || *p == '?' ||
+ *p == '*' || *p == '/' || *p == '\\')
+ lstyle = 0;
+ }
+ }
+
+ } /* size token found */
+ } /* expected chars behind expected size token */
+ } /* if (linelen > pos) */
+ } /* if (!lstyle && numtoks >= 2) */
+
+ if (!lstyle && state->lstyle == 'D' && !carry_buf_len) {
+ /* the filename of a multi-line entry can be identified
+ * correctly only if dls format had been previously established.
+ * This should always be true because there should be entries
+ * for '.' and/or '..' and/or CWD that precede the rest of the
+ * listing.
+ */
+ pos = linelen;
+ if (pos > (sizeof(state->carry_buf) - 1))
+ pos = sizeof(state->carry_buf) - 1;
+ memcpy(state->carry_buf, line, pos);
+ state->carry_buf_len = pos;
+ return '?';
+ }
+
+ if (lstyle == 'D') {
+ state->parsed_one = 1;
+ state->lstyle = lstyle;
+
+ p = &(tokens[tokmarker - 1][toklen[tokmarker - 1]]);
+ result->fe_fname = tokens[0];
+ result->fe_fnlen = p - tokens[0];
+ result->fe_type = 'f';
+
+ if (result->fe_fname[result->fe_fnlen - 1] == '/') {
+ if (result->fe_lnlen == 1)
+ result->fe_type = '?';
+ else {
+ result->fe_fnlen--;
+ result->fe_type = 'd';
+ }
+ } else if (IsAsciiDigit(*tokens[tokmarker])) {
+ pos = toklen[tokmarker];
+ if (pos > (sizeof(result->fe_size) - 1))
+ pos = sizeof(result->fe_size) - 1;
+ memcpy(result->fe_size, tokens[tokmarker], pos);
+ result->fe_size[pos] = '\0';
+ }
+
+ if ((tokmarker + 3) < numtoks &&
+ (&(tokens[numtoks - 1][toklen[numtoks - 1]]) -
+ tokens[tokmarker + 1]) >= (1 + 1 + 3 + 1 + 4)) {
+ pos = (tokmarker + 3);
+ p = tokens[pos];
+ pos = toklen[pos];
+
+ if ((pos == 4 || pos == 5) && IsAsciiDigit(*p) &&
+ IsAsciiDigit(p[pos - 1]) && IsAsciiDigit(p[pos - 2]) &&
+ ((pos == 5 && p[2] == ':') ||
+ (pos == 4 && (IsAsciiDigit(p[1]) || p[1] == ':')))) {
+ month_num = tokmarker + 1; /* assumed position of month field */
+ pos = tokmarker + 2; /* assumed position of mday field */
+ if (IsAsciiDigit(*tokens[month_num])) /* positions are reversed */
+ {
+ month_num++;
+ pos--;
+ }
+ p = tokens[month_num];
+ if (IsAsciiDigit(*tokens[pos]) &&
+ (toklen[pos] == 1 ||
+ (toklen[pos] == 2 && IsAsciiDigit(tokens[pos][1]))) &&
+ toklen[month_num] == 3 && IsAsciiAlpha(*p) &&
+ IsAsciiAlpha(p[1]) && IsAsciiAlpha(p[2])) {
+ pos = atoi(tokens[pos]);
+ if (pos > 0 && pos <= 31) {
+ result->fe_time.tm_mday = pos;
+ month_num = 1;
+ for (pos = 0; pos < (12 * 3); pos += 3) {
+ if (p[0] == month_names[pos + 0] &&
+ p[1] == month_names[pos + 1] &&
+ p[2] == month_names[pos + 2])
+ break;
+ month_num++;
+ }
+ if (month_num > 12)
+ result->fe_time.tm_mday = 0;
+ else
+ result->fe_time.tm_month = month_num - 1;
+ }
+ }
+ if (result->fe_time.tm_mday) {
+ tokmarker += 3; /* skip mday/mon/yrtime (to find " -> ") */
+ p = tokens[tokmarker];
+
+ pos = atoi(p);
+ if (pos > 24)
+ result->fe_time.tm_year = pos;
+ else {
+ if (p[1] == ':') p--;
+ result->fe_time.tm_hour = pos;
+ result->fe_time.tm_min = atoi(p + 3);
+ if (!state->now_time) {
+ state->now_time = nowTimeFn();
+ PR_ExplodeTime((state->now_time), timeParam,
+ &(state->now_tm));
+ }
+ result->fe_time.tm_year = state->now_tm.tm_year;
+ if (((state->now_tm.tm_month << 4) + state->now_tm.tm_mday) <
+ ((result->fe_time.tm_month << 4) + result->fe_time.tm_mday))
+ result->fe_time.tm_year--;
+ } /* got year or time */
+ } /* got month/mday */
+ } /* may have year or time */
+ } /* enough remaining to possibly have date/time */
+
+ if (numtoks > (tokmarker + 2)) {
+ pos = tokmarker + 1;
+ p = tokens[pos];
+ if (toklen[pos] == 2 && *p == '-' && p[1] == '>') {
+ p = &(tokens[numtoks - 1][toklen[numtoks - 1]]);
+ result->fe_type = 'l';
+ result->fe_lname = tokens[pos + 1];
+ result->fe_lnlen = p - result->fe_lname;
+ if (result->fe_lnlen > 1 &&
+ result->fe_lname[result->fe_lnlen - 1] == '/')
+ result->fe_lnlen--;
+ }
+ } /* if (numtoks > (tokmarker+2)) */
+
+ /* the caller should do this (if dropping "." and ".." is desired)
+ if (result->fe_type == 'd' && result->fe_fname[0] == '.' &&
+ (result->fe_fnlen == 1 || (result->fe_fnlen == 2 &&
+ result->fe_fname[1] == '.')))
+ return '?';
+ */
+
+ return result->fe_type;
+
+ } /* if (lstyle == 'D') */
+ } /* if (!lstyle && (!state->lstyle || state->lstyle == 'D')) */
+#endif
+
+ /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+ } /* if (linelen > 0) */
+
+ return ParsingFailed(state);
+}
diff --git a/netwerk/streamconv/converters/ParseFTPList.h b/netwerk/streamconv/converters/ParseFTPList.h
new file mode 100644
index 0000000000..610db04d31
--- /dev/null
+++ b/netwerk/streamconv/converters/ParseFTPList.h
@@ -0,0 +1,102 @@
+/* -*- 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 ParseRTPList_h___
+#define ParseRTPList_h___
+
+#include <stdint.h>
+#include <string.h>
+#include "prtime.h"
+
+/* ParseFTPList() parses lines from an FTP LIST command.
+**
+** Written July 2002 by Cyrus Patel <cyp@fb14.uni-mainz.de>
+** with acknowledgements to squid, lynx, wget and ftpmirror.
+**
+** Arguments:
+** 'line': line of FTP data connection output. The line is assumed
+** to end at the first '\0' or '\n' or '\r\n'.
+** 'state': a structure used internally to track state between
+** lines. Needs to be bzero()'d at LIST begin.
+** 'result': where ParseFTPList will store the results of the parse
+** if 'line' is not a comment and is not junk.
+**
+** Returns one of the following:
+** 'd' - LIST line is a directory entry ('result' is valid)
+** 'f' - LIST line is a file's entry ('result' is valid)
+** 'l' - LIST line is a symlink's entry ('result' is valid)
+** '?' - LIST line is junk. (cwd, non-file/dir/link, etc)
+** '"' - its not a LIST line (its a "comment")
+**
+** It may be advisable to let the end-user see "comments" (particularly when
+** the listing results in ONLY such lines) because such a listing may be:
+** - an unknown LIST format (NLST or "custom" format for example)
+** - an error msg (EPERM,ENOENT,ENFILE,EMFILE,ENOTDIR,ENOTBLK,EEXDEV etc).
+** - an empty directory and the 'comment' is a "total 0" line or similar.
+** (warning: a "total 0" can also mean the total size is unknown).
+**
+** ParseFTPList() supports all known FTP LISTing formats:
+** - '/bin/ls -l' and all variants (including Hellsoft FTP for NetWare);
+** - EPLF (Easily Parsable List Format);
+** - Windows NT's default "DOS-dirstyle";
+** - OS/2 basic server format LIST format;
+** - VMS (MultiNet, UCX, and CMU) LIST format (including multi-line format);
+** - IBM VM/CMS, VM/ESA LIST format (two known variants);
+** - SuperTCP FTP Server for Win16 LIST format;
+** - NetManage Chameleon (NEWT) for Win16 LIST format;
+** - '/bin/dls' (two known variants, plus multi-line) LIST format;
+** If there are others, then I'd like to hear about them (send me a sample).
+**
+** NLSTings are not supported explicitely because they cannot be machine
+** parsed consistently: NLSTings do not have unique characteristics - even
+** the assumption that there won't be whitespace on the line does not hold
+** because some nlistings have more than one filename per line and/or
+** may have filenames that have spaces in them. Moreover, distinguishing
+** between an error message and an NLST line would require ParseList() to
+** recognize all the possible strerror() messages in the world.
+*/
+
+/* #undef anything you don't want to support */
+#define SUPPORT_LSL /* /bin/ls -l and dozens of variations therof */
+#define SUPPORT_DLS /* /bin/dls format (very, Very, VERY rare) */
+#define SUPPORT_EPLF /* Extraordinarily Pathetic List Format */
+#define SUPPORT_DOS /* WinNT server in 'site dirstyle' dos */
+#define SUPPORT_VMS /* VMS (all: MultiNet, UCX, CMU-IP) */
+#define SUPPORT_CMS /* IBM VM/CMS,VM/ESA (z/VM and LISTING forms) */
+#define SUPPORT_OS2 /* IBM TCP/IP for OS/2 - FTP Server */
+#define SUPPORT_W16 /* win16 hosts: SuperTCP or NetManage Chameleon */
+
+struct list_state {
+ list_state() { memset(this, 0, sizeof(*this)); }
+
+ PRTime now_time; /* needed for year determination */
+ PRExplodedTime now_tm; /* needed for year determination */
+ int32_t lstyle; /* LISTing style */
+ int32_t parsed_one; /* returned anything yet? */
+ char carry_buf[84]; /* for VMS multiline */
+ uint32_t carry_buf_len; /* length of name in carry_buf */
+ uint32_t numlines; /* number of lines seen */
+};
+
+struct list_result {
+ int32_t fe_type; /* 'd'(dir) or 'l'(link) or 'f'(file) */
+ const char* fe_fname; /* pointer to filename */
+ uint32_t fe_fnlen; /* length of filename */
+ const char* fe_lname; /* pointer to symlink name */
+ uint32_t fe_lnlen; /* length of symlink name */
+ char fe_size[40]; /* size of file in bytes (<= (2^128 - 1)) */
+ PRExplodedTime fe_time; /* last-modified time */
+ int32_t fe_cinfs; /* file system is definitely case insensitive */
+ /* (converting all-upcase names may be desirable) */
+};
+
+typedef PRTime (*NowTimeFn)();
+
+int ParseFTPList(const char* line, struct list_state* state,
+ struct list_result* result,
+ PRTimeParamFn timeParam = PR_LocalTimeParameters,
+ NowTimeFn nowTimeFn = PR_Now);
+
+#endif /* !ParseRTPList_h___ */
diff --git a/netwerk/streamconv/converters/moz.build b/netwerk/streamconv/converters/moz.build
new file mode 100644
index 0000000000..46b1d4a4dc
--- /dev/null
+++ b/netwerk/streamconv/converters/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 += ["nsICompressConvStats.idl"]
+
+EXPORTS += [
+ "nsUnknownDecoder.h",
+]
+
+XPIDL_MODULE = "necko_http"
+
+UNIFIED_SOURCES += [
+ "mozTXTToHTMLConv.cpp",
+ "nsDirIndex.cpp",
+ "nsDirIndexParser.cpp",
+ "nsFTPDirListingConv.cpp",
+ "nsHTTPCompressConv.cpp",
+ "nsIndexedToHTML.cpp",
+ "nsMultiMixedConv.cpp",
+ "nsUnknownDecoder.cpp",
+ "ParseFTPList.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..1ab51adb82
--- /dev/null
+++ b/netwerk/streamconv/converters/mozTXTToHTMLConv.cpp
@@ -0,0 +1,1260 @@
+/* -*- 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 "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;
+
+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;
+ } else if (ItMatchesDelimited(aInString, aInLength, u"ftp.", 4, LT_IGNORE,
+ LT_IGNORE)) {
+ aOutString.AssignLiteral("ftp://");
+ 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: {
+ nsString 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: {
+ nsString 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) {
+ mozilla::unicode::ClusterIterator ci(aInString, aInLength);
+ ci.Next();
+ ignoreLen = ci - aInString;
+ }
+
+ 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]);
+ }
+
+ if ((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))
+ return false;
+
+ return true;
+}
+
+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;
+
+ const char16_t* end = aInString + aInStringLength;
+ for (mozilla::unicode::ClusterIterator ci(aInString, aInStringLength);
+ !ci.AtEnd(); ci.Next()) {
+ if (ItMatchesDelimited(ci, end - ci, 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
+ else 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 char* imageName, nsString& outputHTML,
+ int32_t& glyphTextLen) {
+ if (!aInString || !tagTXT || !imageName) 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.AppendLiteral("<span class=\""); // <span class="
+ outputHTML.AppendASCII(imageName); // e.g. smiley-frown
+ outputHTML.AppendLiteral("\" title=\""); // " title="
+ outputHTML.AppendASCII(tagTXT); // smiley tooltip
+ outputHTML.AppendLiteral("\"><span>"); // "><span>
+ outputHTML.AppendASCII(tagTXT); // original text
+ outputHTML.AppendLiteral("</span></span>"); // </span></span>
+ 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, ":-)",
+ "moz-smiley-s1", // smile
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":)",
+ "moz-smiley-s1", // smile
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-D",
+ "moz-smiley-s5", // laughing
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-(",
+ "moz-smiley-s2", // frown
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":(",
+ "moz-smiley-s2", // frown
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-[",
+ "moz-smiley-s6", // embarassed
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ";-)",
+ "moz-smiley-s3", // wink
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, col0, ";)",
+ "moz-smiley-s3", // wink
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-\\",
+ "moz-smiley-s7", // undecided
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-P",
+ "moz-smiley-s4", // tongue
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ";-P",
+ "moz-smiley-s4", // tongue
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, "=-O",
+ "moz-smiley-s8", // surprise
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-*",
+ "moz-smiley-s9", // kiss
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ">:o",
+ "moz-smiley-s10", // yell
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ">:-o",
+ "moz-smiley-s10", // yell
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, "8-)",
+ "moz-smiley-s11", // cool
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-$",
+ "moz-smiley-s12", // money
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-!",
+ "moz-smiley-s13", // foot
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, "O:-)",
+ "moz-smiley-s14", // innocent
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":'(",
+ "moz-smiley-s15", // cry
+ outputHTML, glyphTextLen) ||
+
+ SmilyHit(aInString, aInLength, bArg, ":-X",
+ "moz-smiley-s16", // sealed
+ 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,
+ 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();
+
+ for (mozilla::unicode::ClusterIterator ci(rawInputString, inLength);
+ !ci.AtEnd();) {
+ uint32_t i = ci - rawInputString;
+ if (doGlyphSubstitution) {
+ int32_t glyphTextLen;
+ if (GlyphHit(&rawInputString[i], inLength - i, i == 0, aOutString,
+ glyphTextLen)) {
+ i += glyphTextLen;
+ while (ci < rawInputString + i) {
+ ci.Next();
+ }
+ continue;
+ }
+ }
+
+ if (doStructPhrase) {
+ const char16_t* newOffset = rawInputString;
+ int32_t newLength = aInString.Length();
+ if (i > 0) // skip the first element?
+ {
+ mozilla::unicode::ClusterReverseIterator ri(rawInputString, i);
+ ri.Next();
+ newOffset = ri;
+ newLength = aInString.Length() - (ri - rawInputString);
+ }
+
+ switch (aInString[i]) // Performance increase
+ {
+ case '*':
+ if (StructPhraseHit(newOffset, newLength, i == 0, u"*", 1, "b",
+ "class=\"moz-txt-star\"", aOutString,
+ structPhrase_strong)) {
+ ci.Next();
+ continue;
+ }
+ break;
+ case '/':
+ if (StructPhraseHit(newOffset, newLength, i == 0, u"/", 1, "i",
+ "class=\"moz-txt-slash\"", aOutString,
+ structPhrase_italic)) {
+ ci.Next();
+ continue;
+ }
+ break;
+ case '_':
+ if (StructPhraseHit(newOffset, newLength, i == 0, u"_", 1,
+ "span" /* <u> is deprecated */,
+ "class=\"moz-txt-underscore\"", aOutString,
+ structPhrase_underline)) {
+ ci.Next();
+ continue;
+ }
+ break;
+ case '|':
+ if (StructPhraseHit(newOffset, newLength, i == 0, u"|", 1, "code",
+ "class=\"moz-txt-verticalline\"", aOutString,
+ structPhrase_code)) {
+ 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 += replaceAfter + 1;
+ while (ci < rawInputString + i) {
+ ci.Next();
+ }
+ continue;
+ }
+ }
+ break;
+ } // switch
+ }
+
+ switch (aInString[i]) {
+ // Special symbols
+ case '<':
+ case '>':
+ case '&':
+ EscapeChar(aInString[i], aOutString, false);
+ ci.Next();
+ break;
+ // Normal characters
+ default: {
+ const char16_t* start = ci;
+ ci.Next();
+ aOutString += Substring(start, (const char16_t*)ci);
+ 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.Find("</a>", true, 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("-->", false, 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.Find("</style>", true, 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.Find("</script>", true, 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.Find("</head>", true, 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;
+
+ nsString 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::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..23c3dac30f
--- /dev/null
+++ b/netwerk/streamconv/converters/mozTXTToHTMLConv.h
@@ -0,0 +1,284 @@
+/* -*- 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 "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_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& aOutputString, 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 char* 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& aOutString, 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..c6e89fd7c5
--- /dev/null
+++ b/netwerk/streamconv/converters/nsDirIndex.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/. */
+
+#include "nsDirIndex.h"
+
+NS_IMPL_ISUPPORTS(nsDirIndex, nsIDirIndex)
+
+nsDirIndex::nsDirIndex()
+ : mType(TYPE_UNKNOWN), mSize(UINT64_MAX), mLastModified(-1LL) {}
+
+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::GetContentType(nsACString& aContentType) {
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndex::SetContentType(const nsACString& aContentType) {
+ mContentType = aContentType;
+ 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::GetDescription(nsAString& aDescription) {
+ aDescription = mDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndex::SetDescription(const nsAString& aDescription) {
+ mDescription = aDescription;
+ 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..2ff411de54
--- /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();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDIRINDEX
+
+ protected:
+ uint32_t mType;
+ nsCString mContentType;
+ nsCString mLocation;
+ nsString mDescription;
+ int64_t mSize;
+ PRTime mLastModified;
+};
+
+#endif
diff --git a/netwerk/streamconv/converters/nsDirIndexParser.cpp b/netwerk/streamconv/converters/nsDirIndexParser.cpp
new file mode 100644
index 0000000000..ed53cc45fb
--- /dev/null
+++ b/netwerk/streamconv/converters/nsDirIndexParser.cpp
@@ -0,0 +1,444 @@
+/* -*- 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/Encoding.h"
+#include "prprf.h"
+#include "nsCRT.h"
+#include "nsDirIndex.h"
+#include "nsEscape.h"
+#include "nsIDirIndex.h"
+#include "nsIInputStream.h"
+#include "nsITextToSubURI.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/intl/LocaleService.h"
+
+using namespace mozilla;
+
+struct EncodingProp {
+ const char* const mKey;
+ NotNull<const Encoding*> mValue;
+};
+
+static const EncodingProp localesFallbacks[] = {
+ {"ar", WINDOWS_1256_ENCODING}, {"ba", WINDOWS_1251_ENCODING},
+ {"be", WINDOWS_1251_ENCODING}, {"bg", WINDOWS_1251_ENCODING},
+ {"cs", WINDOWS_1250_ENCODING}, {"el", ISO_8859_7_ENCODING},
+ {"et", WINDOWS_1257_ENCODING}, {"fa", WINDOWS_1256_ENCODING},
+ {"he", WINDOWS_1255_ENCODING}, {"hr", WINDOWS_1250_ENCODING},
+ {"hu", ISO_8859_2_ENCODING}, {"ja", SHIFT_JIS_ENCODING},
+ {"kk", WINDOWS_1251_ENCODING}, {"ko", EUC_KR_ENCODING},
+ {"ku", WINDOWS_1254_ENCODING}, {"ky", WINDOWS_1251_ENCODING},
+ {"lt", WINDOWS_1257_ENCODING}, {"lv", WINDOWS_1257_ENCODING},
+ {"mk", WINDOWS_1251_ENCODING}, {"pl", ISO_8859_2_ENCODING},
+ {"ru", WINDOWS_1251_ENCODING}, {"sah", WINDOWS_1251_ENCODING},
+ {"sk", WINDOWS_1250_ENCODING}, {"sl", ISO_8859_2_ENCODING},
+ {"sr", WINDOWS_1251_ENCODING}, {"tg", WINDOWS_1251_ENCODING},
+ {"th", WINDOWS_874_ENCODING}, {"tr", WINDOWS_1254_ENCODING},
+ {"tt", WINDOWS_1251_ENCODING}, {"uk", WINDOWS_1251_ENCODING},
+ {"vi", WINDOWS_1258_ENCODING}, {"zh", GBK_ENCODING}};
+
+static NotNull<const Encoding*>
+GetFTPFallbackEncodingDoNotAddNewCallersToThisFunction() {
+ nsAutoCString locale;
+ mozilla::intl::LocaleService::GetInstance()->GetAppLocaleAsBCP47(locale);
+
+ // Let's lower case the string just in case unofficial language packs
+ // don't stick to conventions.
+ ToLowerCase(locale); // ASCII lowercasing with CString input!
+
+ // Special case Traditional Chinese before throwing away stuff after the
+ // language itself. Today we only ship zh-TW, but be defensive about
+ // possible future values.
+ if (locale.EqualsLiteral("zh-tw") || locale.EqualsLiteral("zh-hk") ||
+ locale.EqualsLiteral("zh-mo") || locale.EqualsLiteral("zh-hant")) {
+ return BIG5_ENCODING;
+ }
+
+ // Throw away regions and other variants to accommodate weird stuff seen
+ // in telemetry--apparently unofficial language packs.
+ int32_t hyphenIndex = locale.FindChar('-');
+ if (hyphenIndex >= 0) {
+ locale.Truncate(hyphenIndex);
+ }
+
+ size_t index;
+ if (BinarySearchIf(
+ localesFallbacks, 0, ArrayLength(localesFallbacks),
+ [&locale](const EncodingProp& aProperty) {
+ return locale.Compare(aProperty.mKey);
+ },
+ &index)) {
+ return localesFallbacks[index].mValue;
+ }
+ return WINDOWS_1252_ENCODING;
+}
+
+NS_IMPL_ISUPPORTS(nsDirIndexParser, nsIRequestObserver, nsIStreamListener,
+ nsIDirIndexParser)
+
+nsDirIndexParser::nsDirIndexParser() : mLineStart(0), mHasDescription(false) {}
+
+nsresult nsDirIndexParser::Init() {
+ mLineStart = 0;
+ mHasDescription = false;
+ mFormat[0] = -1;
+ auto encoding = GetFTPFallbackEncodingDoNotAddNewCallersToThisFunction();
+ encoding->Name(mEncoding);
+
+ nsresult rv;
+ // XXX not threadsafe
+ if (gRefCntParser++ == 0)
+ rv = CallGetService(NS_ITEXTTOSUBURI_CONTRACTID, &gTextToSubURI);
+ else
+ rv = NS_OK;
+
+ return rv;
+}
+
+nsDirIndexParser::~nsDirIndexParser() {
+ // XXX not threadsafe
+ if (--gRefCntParser == 0) {
+ NS_IF_RELEASE(gTextToSubURI);
+ }
+}
+
+NS_IMETHODIMP
+nsDirIndexParser::SetListener(nsIDirIndexListener* aListener) {
+ mListener = aListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndexParser::GetListener(nsIDirIndexListener** aListener) {
+ NS_IF_ADDREF(*aListener = mListener.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndexParser::GetComment(char** aComment) {
+ *aComment = ToNewCString(mComment, mozilla::fallible);
+
+ if (!*aComment) return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndexParser::SetEncoding(const char* aEncoding) {
+ mEncoding.Assign(aEncoding);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirIndexParser::GetEncoding(char** aEncoding) {
+ *aEncoding = ToNewCString(mEncoding, mozilla::fallible);
+
+ if (!*aEncoding) return NS_ERROR_OUT_OF_MEMORY;
+
+ 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, nullptr);
+ }
+
+ return NS_OK;
+}
+
+nsDirIndexParser::Field nsDirIndexParser::gFieldTable[] = {
+ {"Filename", FIELD_FILENAME},
+ {"Description", FIELD_DESCRIPTION},
+ {"Content-Length", FIELD_CONTENTLENGTH},
+ {"Last-Modified", FIELD_LASTMODIFIED},
+ {"Content-Type", FIELD_CONTENTTYPE},
+ {"File-Type", FIELD_FILETYPE},
+ {nullptr, FIELD_UNKNOWN}};
+
+nsrefcnt nsDirIndexParser::gRefCntParser = 0;
+nsITextToSubURI* nsDirIndexParser::gTextToSubURI;
+
+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()));
+
+ // All tokens are case-insensitive -
+ // http://www.mozilla.org/projects/netlib/dirindexformat.html
+ if (name.LowerCaseEqualsLiteral("description")) mHasDescription = true;
+
+ 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;
+
+ bool success = false;
+
+ nsAutoString entryuri;
+
+ if (gTextToSubURI) {
+ nsAutoString result;
+ if (NS_SUCCEEDED(gTextToSubURI->UnEscapeAndConvert(
+ mEncoding, filename, result))) {
+ if (!result.IsEmpty()) {
+ aIdx->SetLocation(filename);
+ if (!mHasDescription) aIdx->SetDescription(result);
+ success = true;
+ }
+ } else {
+ NS_WARNING("UnEscapeAndConvert error");
+ }
+ }
+
+ if (!success) {
+ // if unsuccessfully at charset conversion, then
+ // just fallback to unescape'ing in-place
+ // XXX - this shouldn't be using UTF8, should it?
+ // when can we fail to get the service, anyway? - bbaetz
+ aIdx->SetLocation(filename);
+ if (!mHasDescription) {
+ aIdx->SetDescription(NS_ConvertUTF8toUTF16(value));
+ }
+ }
+ } break;
+ case FIELD_DESCRIPTION:
+ nsUnescape(value);
+ aIdx->SetDescription(NS_ConvertUTF8toUTF16(value));
+ 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_CONTENTTYPE:
+ aIdx->SetContentType(nsDependentCString(value));
+ 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;
+
+ int32_t len = mBuf.Length();
+
+ // Ensure that our mBuf has capacity to hold the data we're about to
+ // read.
+ 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, nullptr);
+}
+
+nsresult nsDirIndexParser::ProcessData(nsIRequest* aRequest,
+ nsISupports* aCtxt) {
+ if (!mListener) return NS_ERROR_FAILURE;
+
+ int32_t numItems = 0;
+
+ while (true) {
+ ++numItems;
+
+ 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] == '1') {
+ if (buf[1] == '0') {
+ if (buf[2] == '0' && buf[3] == ':') {
+ // 100. Human-readable comment line. Ignore
+ } else if (buf[2] == '1' && buf[3] == ':') {
+ // 101. Human-readable information line.
+ mComment.Append(buf + 4);
+
+ char* value = ((char*)buf) + 4;
+ nsUnescape(value);
+ mListener->OnInformationAvailable(aRequest, aCtxt,
+ NS_ConvertUTF8toUTF16(value));
+
+ } else if (buf[2] == '2' && buf[3] == ':') {
+ // 102. Human-readable information line, HTML.
+ mComment.Append(buf + 4);
+ }
+ }
+ } else 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, aCtxt, idx);
+ }
+ }
+ } else if (buf[0] == '3') {
+ if (buf[1] == '0') {
+ if (buf[2] == '0' && buf[3] == ':') {
+ // 300. Self-referring URL
+ } else if (buf[2] == '1' && buf[3] == ':') {
+ // 301. OUR EXTENSION - encoding
+ int i = 4;
+ while (buf[i] && nsCRT::IsAsciiSpace(buf[i])) ++i;
+
+ if (buf[i]) SetEncoding(buf + i);
+ }
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/netwerk/streamconv/converters/nsDirIndexParser.h b/netwerk/streamconv/converters/nsDirIndexParser.h
new file mode 100644
index 0000000000..c2dfe3c160
--- /dev/null
+++ b/netwerk/streamconv/converters/nsDirIndexParser.h
@@ -0,0 +1,75 @@
+/* -*- 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();
+
+ nsDirIndexParser();
+ nsresult Init();
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIDIRINDEXPARSER
+
+ static already_AddRefed<nsIDirIndexParser> CreateInstance() {
+ RefPtr<nsDirIndexParser> parser = new nsDirIndexParser();
+ if (NS_FAILED(parser->Init())) {
+ return nullptr;
+ }
+ return parser.forget();
+ }
+
+ enum fieldType {
+ FIELD_UNKNOWN = 0, // MUST be 0
+ FIELD_FILENAME,
+ FIELD_DESCRIPTION,
+ FIELD_CONTENTLENGTH,
+ FIELD_LASTMODIFIED,
+ FIELD_CONTENTTYPE,
+ FIELD_FILETYPE
+ };
+
+ protected:
+ nsCOMPtr<nsIDirIndexListener> mListener;
+
+ nsCString mEncoding;
+ nsCString mComment;
+ nsCString mBuf;
+ int32_t mLineStart;
+ bool mHasDescription;
+ int mFormat[8];
+
+ nsresult ProcessData(nsIRequest* aRequest, nsISupports* aCtxt);
+ void ParseFormat(const char* buf);
+ void ParseData(nsIDirIndex* aIdx, char* aDataStr, int32_t lineLen);
+
+ struct Field {
+ const char* mName;
+ fieldType mType;
+ };
+
+ static Field gFieldTable[];
+
+ static nsrefcnt gRefCntParser;
+ static nsITextToSubURI* gTextToSubURI;
+};
+
+#endif
diff --git a/netwerk/streamconv/converters/nsFTPDirListingConv.cpp b/netwerk/streamconv/converters/nsFTPDirListingConv.cpp
new file mode 100644
index 0000000000..d155481f55
--- /dev/null
+++ b/netwerk/streamconv/converters/nsFTPDirListingConv.cpp
@@ -0,0 +1,342 @@
+/* -*- 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 "nsFTPDirListingConv.h"
+#include "nsMemory.h"
+#include "plstr.h"
+#include "mozilla/Logging.h"
+#include "nsCOMPtr.h"
+#include "nsEscape.h"
+#include "nsStringStream.h"
+#include "nsIStreamListener.h"
+#include "nsCRT.h"
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "nsIURIMutator.h"
+
+#include "ParseFTPList.h"
+#include <algorithm>
+
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/Unused.h"
+
+//
+// Log module for FTP dir listing stream converter logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=nsFTPDirListConv:5
+// set MOZ_LOG_FILE=network.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file network.log.
+//
+static mozilla::LazyLogModule gFTPDirListConvLog("nsFTPDirListingConv");
+using namespace mozilla;
+
+// nsISupports implementation
+NS_IMPL_ISUPPORTS(nsFTPDirListingConv, nsIStreamConverter, nsIStreamListener,
+ nsIRequestObserver)
+
+// nsIStreamConverter implementation
+NS_IMETHODIMP
+nsFTPDirListingConv::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
+nsFTPDirListingConv::AsyncConvertData(const char* aFromType,
+ const char* aToType,
+ nsIStreamListener* aListener,
+ nsISupports* aCtxt) {
+ NS_ASSERTION(aListener && aFromType && aToType,
+ "null pointer passed into FTP dir listing converter");
+
+ // hook up our final listener. this guy gets the various On*() calls we want
+ // to throw at him.
+ mFinalListener = aListener;
+ NS_ADDREF(mFinalListener);
+
+ MOZ_LOG(gFTPDirListConvLog, LogLevel::Debug,
+ ("nsFTPDirListingConv::AsyncConvertData() converting FROM raw, TO "
+ "application/http-index-format\n"));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFTPDirListingConv::GetConvertedType(const nsACString& aFromType,
+ nsIChannel* aChannel,
+ nsACString& aToType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsIStreamListener implementation
+NS_IMETHODIMP
+nsFTPDirListingConv::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
+ uint64_t sourceOffset, uint32_t count) {
+ NS_ASSERTION(request, "FTP dir listing stream converter needs a request");
+
+ nsresult rv;
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t read, streamLen;
+
+ uint64_t streamLen64;
+ rv = inStr->Available(&streamLen64);
+ NS_ENSURE_SUCCESS(rv, rv);
+ streamLen = (uint32_t)std::min(streamLen64, uint64_t(UINT32_MAX - 1));
+
+ auto buffer = MakeUniqueFallible<char[]>(streamLen + 1);
+ NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = inStr->Read(buffer.get(), streamLen, &read);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // the dir listings are ascii text, null terminate this sucker.
+ buffer[streamLen] = '\0';
+
+ MOZ_LOG(gFTPDirListConvLog, LogLevel::Debug,
+ ("nsFTPDirListingConv::OnData(request = %p, inStr = %p, "
+ "sourceOffset = %" PRIu64 ", count = %u)\n",
+ request, inStr, sourceOffset, count));
+
+ if (!mBuffer.IsEmpty()) {
+ // we have data left over from a previous OnDataAvailable() call.
+ // combine the buffers so we don't lose any data.
+ mBuffer.Append(buffer.get());
+
+ buffer = MakeUniqueFallible<char[]>(mBuffer.Length() + 1);
+ NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY);
+
+ strncpy(buffer.get(), mBuffer.get(), mBuffer.Length() + 1);
+ mBuffer.Truncate();
+ }
+
+ MOZ_LOG(gFTPDirListConvLog, LogLevel::Debug,
+ ("::OnData() received the following %d bytes...\n\n%s\n\n", streamLen,
+ buffer.get()));
+
+ nsAutoCString indexFormat;
+ if (!mSentHeading) {
+ // build up the 300: line
+ nsCOMPtr<nsIURI> uri;
+ rv = channel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetHeaders(indexFormat, uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSentHeading = true;
+ }
+
+ char* line = buffer.get();
+ line = DigestBufferLines(line, indexFormat);
+
+ MOZ_LOG(gFTPDirListConvLog, LogLevel::Debug,
+ ("::OnData() sending the following %d bytes...\n\n%s\n\n",
+ indexFormat.Length(), indexFormat.get()));
+
+ // if there's any data left over, buffer it.
+ if (line && *line) {
+ mBuffer.Append(line);
+ MOZ_LOG(gFTPDirListConvLog, LogLevel::Debug,
+ ("::OnData() buffering the following %zu bytes...\n\n%s\n\n",
+ strlen(line), line));
+ }
+
+ // send the converted data out.
+ nsCOMPtr<nsIInputStream> inputData;
+
+ rv = NS_NewCStringInputStream(getter_AddRefs(inputData), indexFormat);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mFinalListener->OnDataAvailable(request, inputData, 0,
+ indexFormat.Length());
+
+ return rv;
+}
+
+// nsIRequestObserver implementation
+NS_IMETHODIMP
+nsFTPDirListingConv::OnStartRequest(nsIRequest* request) {
+ // we don't care about start. move along... but start masqeurading
+ // as the http-index channel now.
+ return mFinalListener->OnStartRequest(request);
+}
+
+NS_IMETHODIMP
+nsFTPDirListingConv::OnStopRequest(nsIRequest* request, nsresult aStatus) {
+ // we don't care about stop. move along...
+
+ return mFinalListener->OnStopRequest(request, aStatus);
+}
+
+// nsFTPDirListingConv methods
+nsFTPDirListingConv::nsFTPDirListingConv() {
+ mFinalListener = nullptr;
+ mSentHeading = false;
+}
+
+nsFTPDirListingConv::~nsFTPDirListingConv() { NS_IF_RELEASE(mFinalListener); }
+
+nsresult nsFTPDirListingConv::GetHeaders(nsACString& headers, nsIURI* uri) {
+ nsresult rv = NS_OK;
+ // build up 300 line
+ headers.AppendLiteral("300: ");
+
+ // Bug 111117 - don't print the password
+ nsAutoCString pw;
+ nsAutoCString spec;
+ uri->GetPassword(pw);
+ if (!pw.IsEmpty()) {
+ nsCOMPtr<nsIURI> noPassURI;
+ rv = NS_MutateURI(uri).SetPassword(""_ns).Finalize(noPassURI);
+ if (NS_FAILED(rv)) return rv;
+ rv = noPassURI->GetAsciiSpec(spec);
+ if (NS_FAILED(rv)) return rv;
+ headers.Append(spec);
+ } else {
+ rv = uri->GetAsciiSpec(spec);
+ if (NS_FAILED(rv)) return rv;
+
+ headers.Append(spec);
+ }
+ headers.Append(char(nsCRT::LF));
+ // END 300:
+
+ // build up the column heading; 200:
+ headers.AppendLiteral(
+ "200: filename content-length last-modified file-type\n");
+ // END 200:
+ return rv;
+}
+
+char* nsFTPDirListingConv::DigestBufferLines(char* aBuffer,
+ nsCString& aString) {
+ char* line = aBuffer;
+ char* eol;
+ bool cr = false;
+
+ list_state state;
+
+ // while we have new lines, parse 'em into application/http-index-format.
+ while (line && (eol = PL_strchr(line, nsCRT::LF))) {
+ // yank any carriage returns too.
+ if (eol > line && *(eol - 1) == nsCRT::CR) {
+ eol--;
+ *eol = '\0';
+ cr = true;
+ } else {
+ *eol = '\0';
+ cr = false;
+ }
+
+ list_result result;
+
+ int type = ParseFTPList(line, &state, &result);
+
+ // if it is other than a directory, file, or link -OR- if it is a
+ // directory named . or .., skip over this line.
+ if ((type != 'd' && type != 'f' && type != 'l') ||
+ (result.fe_type == 'd' && result.fe_fname[0] == '.' &&
+ (result.fe_fnlen == 1 ||
+ (result.fe_fnlen == 2 && result.fe_fname[1] == '.')))) {
+ if (cr)
+ line = eol + 2;
+ else
+ line = eol + 1;
+
+ continue;
+ }
+
+ // blast the index entry into the indexFormat buffer as a 201: line.
+ aString.AppendLiteral("201: ");
+ // FILENAME
+
+ // parsers for styles 'U' and 'W' handle sequence " -> " themself
+ if (state.lstyle != 'U' && state.lstyle != 'W') {
+ const char* offset = strstr(result.fe_fname, " -> ");
+ if (offset) {
+ result.fe_fnlen = offset - result.fe_fname;
+ }
+ }
+
+ nsAutoCString buf;
+ aString.Append('\"');
+ aString.Append(NS_EscapeURL(
+ Substring(result.fe_fname, result.fe_fname + result.fe_fnlen),
+ esc_Minimal | esc_OnlyASCII | esc_Forced, buf));
+ aString.AppendLiteral("\" ");
+
+ // CONTENT LENGTH
+
+ if (type != 'd') {
+ for (char& fe : result.fe_size) {
+ if (fe != '\0') aString.Append((const char*)&fe, 1);
+ }
+
+ aString.Append(' ');
+ } else
+ aString.AppendLiteral("0 ");
+
+ // MODIFIED DATE
+ char buffer[256] = "";
+
+ // ParseFTPList can return time structure with invalid values.
+ // PR_NormalizeTime will set all values into valid limits.
+ result.fe_time.tm_params.tp_gmt_offset = 0;
+ result.fe_time.tm_params.tp_dst_offset = 0;
+ PR_NormalizeTime(&result.fe_time, PR_GMTParameters);
+
+ // Note: The below is the RFC822/1123 format, as required by
+ // the application/http-index-format specs
+ // viewers of such a format can then reformat this into the
+ // current locale (or anything else they choose)
+ PR_FormatTimeUSEnglish(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S GMT",
+ &result.fe_time);
+
+ nsAutoCString escaped;
+ Unused << NS_WARN_IF(
+ !NS_Escape(nsDependentCString(buffer), escaped, url_Path));
+ aString.Append(escaped);
+ aString.Append(' ');
+
+ // ENTRY TYPE
+ if (type == 'd')
+ aString.AppendLiteral("DIRECTORY");
+ else if (type == 'l')
+ aString.AppendLiteral("SYMBOLIC-LINK");
+ else
+ aString.AppendLiteral("FILE");
+
+ aString.Append(' ');
+
+ aString.Append(char(nsCRT::LF)); // complete this line
+ // END 201:
+
+ if (cr)
+ line = eol + 2;
+ else
+ line = eol + 1;
+ } // end while(eol)
+
+ return line;
+}
+
+nsresult NS_NewFTPDirListingConv(nsFTPDirListingConv** aFTPDirListingConv) {
+ MOZ_ASSERT(aFTPDirListingConv != nullptr, "null ptr");
+ if (!aFTPDirListingConv) return NS_ERROR_NULL_POINTER;
+
+ RefPtr<nsFTPDirListingConv> conv = new nsFTPDirListingConv();
+ conv.forget(aFTPDirListingConv);
+ return NS_OK;
+}
diff --git a/netwerk/streamconv/converters/nsFTPDirListingConv.h b/netwerk/streamconv/converters/nsFTPDirListingConv.h
new file mode 100644
index 0000000000..57ca806d11
--- /dev/null
+++ b/netwerk/streamconv/converters/nsFTPDirListingConv.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 __nsftpdirlistingdconv__h__
+#define __nsftpdirlistingdconv__h__
+
+#include "nsIStreamConverter.h"
+#include "nsString.h"
+
+class nsIURI;
+
+#define NS_FTPDIRLISTINGCONVERTER_CID \
+ { /* 14C0E880-623E-11d3-A178-0050041CAF44 */ \
+ 0x14c0e880, 0x623e, 0x11d3, { \
+ 0xa1, 0x78, 0x00, 0x50, 0x04, 0x1c, 0xaf, 0x44 \
+ } \
+ }
+
+class nsFTPDirListingConv : public nsIStreamConverter {
+ public:
+ // nsISupports methods
+ NS_DECL_ISUPPORTS
+
+ // nsIStreamConverter methods
+ NS_DECL_NSISTREAMCONVERTER
+
+ // nsIStreamListener methods
+ NS_DECL_NSISTREAMLISTENER
+
+ // nsIRequestObserver methods
+ NS_DECL_NSIREQUESTOBSERVER
+
+ // nsFTPDirListingConv methods
+ nsFTPDirListingConv();
+
+ private:
+ virtual ~nsFTPDirListingConv();
+
+ // Get the application/http-index-format headers
+ nsresult GetHeaders(nsACString& str, nsIURI* uri);
+ char* DigestBufferLines(char* aBuffer, nsCString& aString);
+
+ // member data
+ nsCString mBuffer; // buffered data.
+ bool mSentHeading; // have we sent 100, 101, 200, and 300 lines yet?
+
+ nsIStreamListener* mFinalListener; // this guy gets the converted data via
+ // his OnDataAvailable()
+};
+
+#endif /* __nsftpdirlistingdconv__h__ */
diff --git a/netwerk/streamconv/converters/nsHTTPCompressConv.cpp b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp
new file mode 100644
index 0000000000..1de4737866
--- /dev/null
+++ b/netwerk/streamconv/converters/nsHTTPCompressConv.cpp
@@ -0,0 +1,722 @@
+/* -*- 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 "nsMemory.h"
+#include "plstr.h"
+#include "nsCOMPtr.h"
+#include "nsError.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsComponentManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Logging.h"
+#include "nsIForcePendingChannel.h"
+#include "nsIRequest.h"
+
+// brotli headers
+#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)
+
+// nsISupports implementation
+NS_IMPL_ISUPPORTS(nsHTTPCompressConv, nsIStreamConverter, nsIStreamListener,
+ nsIRequestObserver, nsICompressConvStats,
+ nsIThreadRetargetableStreamListener)
+
+// nsFTPDirListingConv methods
+nsHTTPCompressConv::nsHTTPCompressConv()
+ : mMode(HTTP_COMPRESS_IDENTITY),
+ mOutBuffer(nullptr),
+ mInpBuffer(nullptr),
+ mOutBufferLen(0),
+ mInpBufferLen(0),
+ mCheckHeaderDone(false),
+ mStreamEnded(false),
+ mStreamInitialized(false),
+ mDummyStreamInitialised(false),
+ d_stream{},
+ mLen(0),
+ hMode(0),
+ mSkipCount(0),
+ mFlags(0),
+ mDecodedDataLength(0),
+ mMutex("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 (!PL_strncasecmp(aFromType, HTTP_COMPRESS_TYPE,
+ sizeof(HTTP_COMPRESS_TYPE) - 1) ||
+ !PL_strncasecmp(aFromType, HTTP_X_COMPRESS_TYPE,
+ sizeof(HTTP_X_COMPRESS_TYPE) - 1)) {
+ mMode = HTTP_COMPRESS_COMPRESS;
+ } else if (!PL_strncasecmp(aFromType, HTTP_GZIP_TYPE,
+ sizeof(HTTP_GZIP_TYPE) - 1) ||
+ !PL_strncasecmp(aFromType, HTTP_X_GZIP_TYPE,
+ sizeof(HTTP_X_GZIP_TYPE) - 1)) {
+ mMode = HTTP_COMPRESS_GZIP;
+ } else if (!PL_strncasecmp(aFromType, HTTP_DEFLATE_TYPE,
+ sizeof(HTTP_DEFLATE_TYPE) - 1)) {
+ mMode = HTTP_COMPRESS_DEFLATE;
+ } else if (!PL_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);
+ }
+ if (mBrotli && (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;
+ }
+ }
+ if (outSize > 0) {
+ nsresult rv = self->do_OnDataAvailable(
+ self->mBrotli->mRequest, self->mBrotli->mContext,
+ self->mBrotli->mSourceOffset,
+ reinterpret_cast<const char*>(outBuffer.get()), outSize);
+ LOG(("nsHttpCompressConv %p BrotliHandler ODA rv=%" PRIx32, self,
+ static_cast<uint32_t>(rv)));
+ if (NS_FAILED(rv)) {
+ self->mBrotli->mStatus = rv;
+ 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, nullptr, aSourceOffset,
+ (char*)mOutBuffer, bytesWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ inflateEnd(&d_stream);
+ mStreamEnded = true;
+ break;
+ } else if (code == Z_OK) {
+ if (bytesWritten) {
+ rv = do_OnDataAvailable(request, nullptr, aSourceOffset,
+ (char*)mOutBuffer, bytesWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ } else if (code == Z_BUF_ERROR) {
+ if (bytesWritten) {
+ rv = do_OnDataAvailable(request, nullptr, 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, nullptr, aSourceOffset,
+ (char*)mOutBuffer, bytesWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ inflateEnd(&d_stream);
+ mStreamEnded = true;
+ break;
+ } else if (code == Z_OK) {
+ if (bytesWritten) {
+ rv = do_OnDataAvailable(request, nullptr, aSourceOffset,
+ (char*)mOutBuffer, bytesWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ } else if (code == Z_BUF_ERROR) {
+ if (bytesWritten) {
+ rv = do_OnDataAvailable(request, nullptr, 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,
+ nsISupports* context,
+ 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();
+}
+
+} // 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..1ad34bbfab
--- /dev/null
+++ b/netwerk/streamconv/converters/nsHTTPCompressConv.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/. */
+
+#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"
+
+// brotli includes
+# undef assert
+# include "assert.h"
+# include "state.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 {
+
+typedef enum {
+ HTTP_COMPRESS_GZIP,
+ HTTP_COMPRESS_DEFLATE,
+ HTTP_COMPRESS_COMPRESS,
+ HTTP_COMPRESS_BROTLI,
+ HTTP_COMPRESS_IDENTITY
+} CompressMode;
+
+class BrotliWrapper {
+ public:
+ BrotliWrapper()
+ : mTotalOut(0),
+ mStatus(NS_OK),
+ mBrotliStateIsStreamEnd(false),
+ mRequest(nullptr),
+ mContext(nullptr),
+ mSourceOffset(0) {
+ BrotliDecoderStateInit(&mState, 0, 0, 0);
+ }
+ ~BrotliWrapper() { BrotliDecoderStateCleanup(&mState); }
+
+ BrotliDecoderState mState;
+ Atomic<size_t, Relaxed> mTotalOut;
+ nsresult mStatus;
+ Atomic<bool, Relaxed> mBrotliStateIsStreamEnd;
+
+ nsIRequest* mRequest;
+ nsISupports* mContext;
+ uint64_t mSourceOffset;
+};
+
+class nsHTTPCompressConv : public nsIStreamConverter,
+ public nsICompressConvStats,
+ public nsIThreadRetargetableStreamListener {
+ 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();
+
+ private:
+ virtual ~nsHTTPCompressConv();
+
+ nsCOMPtr<nsIStreamListener>
+ mListener; // this guy gets the converted data via his OnDataAvailable ()
+ Atomic<CompressMode, Relaxed> mMode;
+
+ unsigned char* mOutBuffer;
+ unsigned char* mInpBuffer;
+
+ uint32_t mOutBufferLen;
+ uint32_t mInpBufferLen;
+
+ 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, nsISupports* aContext,
+ uint64_t aSourceOffset, const char* buffer,
+ uint32_t aCount);
+
+ bool mCheckHeaderDone;
+ Atomic<bool> mStreamEnded;
+ bool mStreamInitialized;
+ bool mDummyStreamInitialised;
+ bool mFailUncleanStops;
+
+ z_stream d_stream;
+ unsigned mLen, hMode, mSkipCount, mFlags;
+
+ uint32_t check_header(nsIInputStream* iStr, uint32_t streamLen, nsresult* rv);
+
+ Atomic<uint32_t, Relaxed> mDecodedDataLength;
+
+ mutable mozilla::Mutex mMutex;
+};
+
+} // 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..53e85970fa
--- /dev/null
+++ b/netwerk/streamconv/converters/nsIndexedToHTML.cpp
@@ -0,0 +1,847 @@
+/* -*- 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 "DateTimeFormat.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/intl/LocaleService.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"
+
+using mozilla::intl::LocaleService;
+
+NS_IMPL_ISUPPORTS(nsIndexedToHTML, nsIDirIndexListener, nsIStreamConverter,
+ 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(';');
+ }
+ }
+}
+
+nsIndexedToHTML::nsIndexedToHTML() : mExpectAbsLoc(false) {}
+
+nsresult nsIndexedToHTML::Create(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult) {
+ nsresult rv;
+ if (aOuter) return NS_ERROR_NO_AGGREGATION;
+
+ 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, nullptr, 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, nullptr, buffer);
+ return rv;
+}
+
+nsresult nsIndexedToHTML::DoOnStartRequest(nsIRequest* request,
+ nsISupports* aContext,
+ 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("ftp")) {
+ // strip out the password here, so it doesn't show in the page title
+ // This is done by the 300: line generation in ftp, but we don't use
+ // that - see above
+
+ nsAutoCString pw;
+ rv = titleURL->GetPassword(pw);
+ if (NS_FAILED(rv)) return rv;
+ if (!pw.IsEmpty()) {
+ nsCOMPtr<nsIURI> newUri;
+ rv = NS_MutateURI(titleURL).SetPassword(""_ns).Finalize(titleURL);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ nsAutoCString path;
+ rv = uri->GetPathQueryRef(path);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!path.EqualsLiteral("//") && !path.LowerCaseEqualsLiteral("/%2f")) {
+ rv = uri->Resolve(".."_ns, parentStr);
+ if (NS_FAILED(rv)) return rv;
+ }
+ } else 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"
+ " padding: 0;\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, nullptr, buffer);
+ }
+
+ mParser->OnStopRequest(request, aStatus);
+ mParser = nullptr;
+
+ return mListener->OnStopRequest(request, aStatus);
+}
+
+nsresult nsIndexedToHTML::SendToListener(nsIRequest* aRequest,
+ nsISupports* aContext,
+ 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);
+}
+
+static nsresult FormatTime(const nsDateFormatSelector aDateFormatSelector,
+ const nsTimeFormatSelector aTimeFormatSelector,
+ 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()) {
+ return mozilla::DateTimeFormat::FormatPRTime(
+ aDateFormatSelector, aTimeFormatSelector, aPrTime, aStringOut);
+ }
+
+ PRExplodedTime prExplodedTime;
+ PR_ExplodeTime(aPrTime, PR_GMTParameters, &prExplodedTime);
+ return mozilla::DateTimeFormat::FormatPRExplodedTime(
+ aDateFormatSelector, aTimeFormatSelector, &prExplodedTime, aStringOut);
+}
+
+NS_IMETHODIMP
+nsIndexedToHTML::OnIndexAvailable(nsIRequest* aRequest, nsISupports* aCtxt,
+ 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("\">");
+ nsAutoString formatted;
+ FormatTime(kDateFormatShort, kTimeFormatNone, t, formatted);
+ AppendNonAsciiToNCR(formatted, pushBuffer);
+ pushBuffer.AppendLiteral("</td>\n <td>");
+ FormatTime(kDateFormatNone, kTimeFormatLong, t, formatted);
+ // use NCR to show date in any doc charset
+ AppendNonAsciiToNCR(formatted, pushBuffer);
+ }
+
+ pushBuffer.AppendLiteral("</td>\n</tr>");
+
+ return SendToListener(aRequest, aCtxt, pushBuffer);
+}
+
+NS_IMETHODIMP
+nsIndexedToHTML::OnInformationAvailable(nsIRequest* aRequest,
+ nsISupports* aCtxt,
+ const nsAString& aInfo) {
+ nsAutoCString pushBuffer;
+ nsAutoCString escapedUtf8;
+ nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(aInfo), escapedUtf8);
+ pushBuffer.AppendLiteral("<tr>\n <td>");
+ // escaped is provided in Unicode, so write hex NCRs as necessary
+ // to prevent the HTML parser from applying a character set.
+ AppendNonAsciiToNCR(NS_ConvertUTF8toUTF16(escapedUtf8), pushBuffer);
+ pushBuffer.AppendLiteral(
+ "</td>\n <td></td>\n <td></td>\n <td></td>\n</tr>\n");
+
+ return SendToListener(aRequest, aCtxt, 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..6173ecb523
--- /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 "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_NSIDIRINDEXLISTENER
+
+ nsIndexedToHTML();
+
+ nsresult Init(nsIStreamListener* aListener);
+
+ static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+ protected:
+ void FormatSizeString(int64_t inSize, nsCString& outSizeString);
+ nsresult SendToListener(nsIRequest* aRequest, nsISupports* aContext,
+ const nsACString& aBuffer);
+ // Helper to properly implement OnStartRequest
+ nsresult DoOnStartRequest(nsIRequest* request, nsISupports* aContext,
+ 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;
+
+ virtual ~nsIndexedToHTML() = default;
+};
+
+#endif
diff --git a/netwerk/streamconv/converters/nsMultiMixedConv.cpp b/netwerk/streamconv/converters/nsMultiMixedConv.cpp
new file mode 100644
index 0000000000..8acc130421
--- /dev/null
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.cpp
@@ -0,0 +1,1038 @@
+/* -*- 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 "plstr.h"
+#include "nsIHttpChannel.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"
+
+nsPartChannel::nsPartChannel(nsIChannel* aMultipartChannel, uint32_t aPartID,
+ nsIStreamListener* aListener)
+ : mMultipartChannel(aMultipartChannel),
+ mListener(aListener),
+ mStatus(NS_OK),
+ mLoadFlags(0),
+ mContentDisposition(0),
+ mContentLength(UINT64_MAX),
+ mIsByteRangeRequest(false),
+ mByteRangeStart(0),
+ mByteRangeEnd(0),
+ mPartID(aPartID),
+ mIsLastPart(false) {
+ // 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::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 = mLoadGroup;
+ NS_IF_ADDREF(*aLoadGroup);
+
+ 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(nsISupports** 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::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 = mMultipartChannel;
+ NS_IF_ADDREF(*aReturn);
+ return NS_OK;
+}
+
+// nsISupports implementation
+NS_IMPL_ISUPPORTS(nsMultiMixedConv, nsIStreamConverter, nsIStreamListener,
+ 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::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()
+ : mCurrentPartID(0),
+ mInOnDataAvailable(false),
+ mResponseHeader(HEADER_UNKNOWN),
+ // 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)) {
+ mContentLength = UINT64_MAX;
+ mByteRangeStart = 0;
+ mByteRangeEnd = 0;
+ mTotalSent = 0;
+ mIsByteRangeRequest = false;
+ mParserState = INIT;
+ mRawData = nullptr;
+ mRequestListenerNotified = false;
+}
+
+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++, partListener);
+ if (!newChannel) return NS_ERROR_OUT_OF_MEMORY;
+
+ 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 (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..feb93ff360
--- /dev/null
+++ b/netwerk/streamconv/converters/nsMultiMixedConv.h
@@ -0,0 +1,256 @@
+/* -*- 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"
+
+#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,
+ 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;
+ UniquePtr<mozilla::net::nsHttpResponseHead> mResponseHead;
+
+ nsresult mStatus;
+ nsLoadFlags mLoadFlags;
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+
+ nsCString mContentType;
+ nsCString mContentCharset;
+ uint32_t mContentDisposition;
+ nsString mContentDispositionFilename;
+ nsCString mContentDispositionHeader;
+ uint64_t mContentLength;
+
+ bool mIsByteRangeRequest;
+ int64_t mByteRangeStart;
+ int64_t mByteRangeEnd;
+
+ uint32_t mPartID; // unique ID that can be used to identify
+ // this part of the multipart document
+ bool mIsLastPart;
+};
+
+// 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_NSIREQUESTOBSERVER
+
+ explicit nsMultiMixedConv();
+
+ protected:
+ typedef mozilla::IncrementalTokenizer::Token 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_t mTotalSent;
+
+ // The following members are for tracking the byte ranges in
+ // multipart/mixed content which specified the 'Content-Range:'
+ // header...
+ int64_t mByteRangeStart;
+ int64_t mByteRangeEnd;
+ bool mIsByteRangeRequest;
+ // 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;
+
+ uint32_t mCurrentPartID;
+
+ // Flag preventing reenter of OnDataAvailable in case the target listener
+ // ends up spinning the event loop.
+ bool mInOnDataAvailable;
+
+ // 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;
+
+ // 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;
+ // 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;
+ nsACString::size_type mRawDataLength;
+
+ // 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..3ba51c8dc4
--- /dev/null
+++ b/netwerk/streamconv/converters/nsUnknownDecoder.cpp
@@ -0,0 +1,894 @@
+/* -*- 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 "nsIPrefBranch.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 <algorithm>
+
+#define MAX_BUFFER_SIZE 512u
+
+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()
+ : mBuffer(nullptr),
+ mBufferLen(0),
+ mRequireHTMLsuffix(false),
+ mMutex("nsUnknownDecoder"),
+ mDecodedData("") {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ bool val;
+ if (NS_SUCCEEDED(prefs->GetBoolPref("security.requireHTMLsuffix", &val)))
+ mRequireHTMLsuffix = val;
+ }
+}
+
+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, nsIStreamListener)
+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
+
+bool nsUnknownDecoder::AllowSniffing(nsIRequest* aRequest) {
+ if (!mRequireHTMLsuffix) {
+ return true;
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (!channel) {
+ NS_ERROR("QI failed");
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (NS_FAILED(channel->GetURI(getter_AddRefs(uri))) || !uri) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+ if (loadInfo->GetSkipContentSniffing()) {
+ return false;
+ }
+
+ return !uri->SchemeIs("file");
+}
+
+/**
+ * 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(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) {
+ /*
+ * To prevent a possible attack, we will not consider this to be
+ * html content if it comes from the local file system and our prefs
+ * are set right
+ */
+ if (!AllowSniffing(aRequest)) {
+ return false;
+ }
+
+ 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(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) && \
+ (PL_strncasecmp(str, _tagstr " ", sizeof(_tagstr)) == 0 || \
+ PL_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) {
+ // Just like HTML, this should be able to be shut off.
+ if (!AllowSniffing(aRequest)) {
+ return false;
+ }
+
+ // 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(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.
+ rv = NS_NewPipe(getter_AddRefs(in), getter_AddRefs(out), MAX_BUFFER_SIZE,
+ MAX_BUFFER_SIZE);
+
+ if (NS_SUCCEEDED(rv)) {
+ 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();
+}
+
+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..3c46d52414
--- /dev/null
+++ b/netwerk/streamconv/converters/nsUnknownDecoder.h
@@ -0,0 +1,166 @@
+/* -*- 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 nsIThreadRetargetableStreamListener {
+ 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
+
+ nsUnknownDecoder();
+
+ 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;
+
+ // Function to use to check whether sniffing some potentially
+ // dangerous types (eg HTML) is ok for this request. We can disable
+ // sniffing for local files if needed using this. Just a security
+ // precation thingy... who knows when we suddenly need to flip this
+ // pref?
+ bool AllowSniffing(nsIRequest* aRequest);
+
+ // 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 {
+ typedef bool (nsUnknownDecoder::*TypeSniffFunc)(nsIRequest* aRequest);
+
+ 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;
+ mozilla::Atomic<bool> mRequireHTMLsuffix;
+
+ nsCString mContentType;
+
+ // This mutex syncs: mContentType, mDecodedData and mNextListener.
+ mutable mozilla::Mutex mMutex;
+
+ 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;
+};
+
+#define NS_BINARYDETECTOR_CATEGORYENTRY \
+ { \
+ NS_CONTENT_SNIFFER_CATEGORY, "Binary Detector", \
+ NS_BINARYDETECTOR_CONTRACTID \
+ }
+
+#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..cbb2a483a9
--- /dev/null
+++ b/netwerk/streamconv/nsIDirIndex.idl
@@ -0,0 +1,72 @@
+/* -*- 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 content type - may be null if it is unknown.
+ * Unspecified for directories
+ */
+ attribute ACString contentType;
+
+ /**
+ * 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;
+
+ /**
+ * A description for the filename, which should be
+ * displayed by a viewer
+ */
+ attribute AString description;
+
+ /**
+ * 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..3b69a1c87d
--- /dev/null
+++ b/netwerk/streamconv/nsIDirIndexListener.idl
@@ -0,0 +1,63 @@
+/* -*- 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 ctxt - opaque parameter
+ * @param index - new index to add
+ */
+ void onIndexAvailable(in nsIRequest aRequest,
+ in nsISupports aCtxt,
+ in nsIDirIndex aIndex);
+
+ /**
+ * Called for each information line
+ *
+ * @param request - the request
+ * @param ctxt - opaque parameter
+ * @param info - new info to add
+ */
+ void onInformationAvailable(in nsIRequest aRequest,
+ in nsISupports aCtxt,
+ in AString aInfo);
+
+};
+
+/**
+ * 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;
+
+ /**
+ * The comment given, if any
+ * This result is only valid _after_ OnStopRequest has occurred,
+ * because it can occur anywhere in the datastream
+ */
+ readonly attribute string comment;
+
+ /**
+ * The encoding to use
+ */
+ attribute string encoding;
+};
+
diff --git a/netwerk/streamconv/nsIStreamConverter.idl b/netwerk/streamconv/nsIStreamConverter.idl
new file mode 100644
index 0000000000..22cbbe52b1
--- /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 "nsIStreamListener.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 : nsIStreamListener {
+
+ /**
+ * <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..495e10a94d
--- /dev/null
+++ b/netwerk/streamconv/nsStreamConverterService.cpp
@@ -0,0 +1,549 @@
+/* -*- 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 "nsCOMArray.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>>* fromEdges = mAdjacencyList.Get(fromStr);
+ if (!fromEdges) {
+ // There is no fromStr vertex, create one.
+ fromEdges = new nsTArray<RefPtr<nsAtom>>();
+ mAdjacencyList.Put(fromStr, fromEdges);
+ }
+
+ if (!mAdjacencyList.Get(toStr)) {
+ // There is no toStr vertex, create one.
+ mAdjacencyList.Put(toStr, new nsTArray<RefPtr<nsAtom>>());
+ }
+
+ // 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;
+}
+
+typedef nsClassHashtable<nsCStringHashKey, BFSTableData> BFSHashTable;
+
+// 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 (auto iter = mAdjacencyList.Iter(); !iter.Done(); iter.Next()) {
+ const nsACString& key = iter.Key();
+ MOZ_ASSERT(iter.UserData(), "no data in the table iteration");
+ lBFSTable.Put(key, new 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..15561a76b2
--- /dev/null
+++ b/netwerk/streamconv/nsStreamConverterService.h
@@ -0,0 +1,47 @@
+/* -*- 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 "nsCOMArray.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/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..4007a3239f
--- /dev/null
+++ b/netwerk/system/android/nsAndroidNetworkLinkService.cpp
@@ -0,0 +1,224 @@
+/* -*- 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"
+
+namespace java = mozilla::java;
+
+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 (!mozilla::AndroidBridge::Bridge()) {
+ NS_WARNING("GetDnsSuffixList is not supported without a bridge connection");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ auto suffixList = java::GeckoAppShell::GetDNSDomains();
+ if (!suffixList) {
+ return NS_OK;
+ }
+
+ nsAutoCString list(suffixList->ToCString());
+ for (const nsACString& suffix : list.Split(',')) {
+ aDnsSuffixList.AppendElement(suffix);
+ }
+ return NS_OK;
+}
+
+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..5f7b826cea
--- /dev/null
+++ b/netwerk/system/linux/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 += [
+ "nsNetworkLinkService.cpp",
+ ]
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/system/linux/nsNetworkLinkService.cpp b/netwerk/system/linux/nsNetworkLinkService.cpp
new file mode 100644
index 0000000000..7c6c791258
--- /dev/null
+++ b/netwerk/system/linux/nsNetworkLinkService.cpp
@@ -0,0 +1,188 @@
+/* -*- 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 "nsServiceManagerUtils.h"
+#include "nsNetworkLinkService.h"
+#include "nsString.h"
+#include "mozilla/Logging.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;
+ }
+
+ mNetlinkSvc->GetDnsSuffixList(aDnsSuffixList);
+ return NS_OK;
+}
+
+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..20e7d3d16f
--- /dev/null
+++ b/netwerk/system/mac/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 += [
+ "nsNetworkLinkService.mm",
+]
+
+FINAL_LIBRARY = "xul"
+
+if CONFIG["CC_TYPE"] == "clang":
+ CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/netwerk/system/mac/nsNetworkLinkService.h b/netwerk/system/mac/nsNetworkLinkService.h
new file mode 100644
index 0000000000..72522317d2
--- /dev/null
+++ b/netwerk/system/mac/nsNetworkLinkService.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 NSNETWORKLINKSERVICEMAC_H_
+#define NSNETWORKLINKSERVICEMAC_H_
+
+#include "nsINetworkLinkService.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/SHA1.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:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSINETWORKLINKSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+
+ 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();
+ void GetDnsSuffixListInternal();
+ bool RoutingFromKernel(nsTArray<nsCString>& aHash);
+ bool RoutingTable(nsTArray<nsCString>& aHash);
+
+ mozilla::Mutex mMutex;
+ 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;
+
+ // 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..f4d7205663
--- /dev/null
+++ b/netwerk/system/mac/nsNetworkLinkService.mm
@@ -0,0 +1,1065 @@
+/* -*- 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 "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 "../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;
+
+// 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)
+
+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;
+}
+
+// Note that this function is copied from xpcom/io/nsLocalFileUnix.cpp.
+static nsresult CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr) {
+ // first see if the conversion would succeed and find the length of the result
+ CFIndex usedBufLen, inStrLen = ::CFStringGetLength(aInStrRef);
+ CFIndex charsConverted =
+ ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8, 0, false,
+ nullptr, 0, &usedBufLen);
+ if (charsConverted == inStrLen) {
+ // all characters converted, do the actual conversion
+ aOutStr.SetLength(usedBufLen);
+ if (aOutStr.Length() != (unsigned int)usedBufLen) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ UInt8* buffer = (UInt8*)aOutStr.BeginWriting();
+ ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8, 0, false, buffer,
+ usedBufLen, &usedBufLen);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+static void GetDNSSearchDomains(const SCDynamicStoreRef aStoreRef, const CFStringRef aPattern,
+ nsTArray<nsCString>& aResult) {
+ CFDictionaryRef dnsDict = (CFDictionaryRef)SCDynamicStoreCopyValue(aStoreRef, aPattern);
+ if (dnsDict) {
+ CFTypeRef domains;
+ if (::CFDictionaryGetValueIfPresent(dnsDict, kSCPropNetDNSSearchDomains, &domains)) {
+ if (domains && ::CFGetTypeID(domains) == ::CFArrayGetTypeID()) {
+ int count = ::CFArrayGetCount(static_cast<CFArrayRef>(domains));
+ for (int i = 0; i < count; i++) {
+ CFTypeRef domain = ::CFArrayGetValueAtIndex(static_cast<CFArrayRef>(domains), i);
+ if (domain && ::CFGetTypeID(domain) == ::CFStringGetTypeID()) {
+ nsAutoCString domainStr;
+ if (NS_SUCCEEDED(CFStringReftoUTF8(static_cast<CFStringRef>(domain), domainStr))) {
+ LOG(("DNS search domain [%s]\n", domainStr.get()));
+ aResult.AppendElement(domainStr);
+ }
+ }
+ }
+ }
+ }
+ }
+ CFReleaseSafe(dnsDict);
+}
+
+static OSStatus GetPrimaryServiceInfo(const SCDynamicStoreRef aStoreRef, nsACString& aServiceName,
+ nsACString& aServiceId) {
+ OSStatus err = getErrorCodePtr(aStoreRef);
+ if (err != noErr) {
+ return err;
+ }
+
+ CFStringRef globalIPv4StateKey = ::SCDynamicStoreKeyCreateNetworkGlobalEntity(
+ nullptr, kSCDynamicStoreDomainState, kSCEntNetIPv4);
+ err = getErrorCodePtr(globalIPv4StateKey);
+
+ // Get the primary interface.
+ CFDictionaryRef primaryInterface = nullptr;
+ if (err == noErr) {
+ primaryInterface = (CFDictionaryRef)::SCDynamicStoreCopyValue(aStoreRef, globalIPv4StateKey);
+ err = getErrorCodePtr(primaryInterface);
+ }
+
+ CFTypeRef serviceId;
+ if (err == noErr && ::CFDictionaryGetValueIfPresent(
+ primaryInterface, kSCDynamicStorePropNetPrimaryService, &serviceId)) {
+ err = -1;
+ if (serviceId && ::CFGetTypeID(serviceId) == ::CFStringGetTypeID()) {
+ if (NS_SUCCEEDED(CFStringReftoUTF8(static_cast<CFStringRef>(serviceId), aServiceId))) {
+ err = noErr;
+ }
+ }
+ }
+
+ CFTypeRef serviceName;
+ if (err == noErr && ::CFDictionaryGetValueIfPresent(
+ primaryInterface, kSCDynamicStorePropNetPrimaryInterface, &serviceName)) {
+ err = -1;
+ if (serviceName && ::CFGetTypeID(serviceName) == ::CFStringGetTypeID()) {
+ if (NS_SUCCEEDED(CFStringReftoUTF8(static_cast<CFStringRef>(serviceName), aServiceName))) {
+ err = noErr;
+ }
+ }
+ }
+
+ CFReleaseSafe(globalIPv4StateKey);
+ CFReleaseSafe(primaryInterface);
+
+ if (err == noErr) {
+ return noErr;
+ }
+
+ return err;
+}
+
+static OSStatus IsInterfaceActive(const SCDynamicStoreRef aStoreRef, const char* aInterfaceName,
+ bool& aResult) {
+ aResult = false;
+
+ OSStatus err = getErrorCodePtr(aStoreRef);
+ if (err != noErr) {
+ return err;
+ }
+
+ CFStringRef serviceNameRef = nullptr;
+ if (err == noErr) {
+ serviceNameRef = CFStringCreateWithCString(nullptr, aInterfaceName, kCFStringEncodingUTF8);
+ err = getErrorCodePtr(serviceNameRef);
+ }
+
+ CFStringRef linkPattern = nullptr;
+ if (err == noErr) {
+ linkPattern = ::SCDynamicStoreKeyCreateNetworkInterfaceEntity(
+ nullptr, kSCDynamicStoreDomainState, serviceNameRef, kSCEntNetLink);
+ err = getErrorCodePtr(linkPattern);
+ }
+
+ CFDictionaryRef dict = nullptr;
+ if (err == noErr) {
+ dict = (CFDictionaryRef)SCDynamicStoreCopyValue(aStoreRef, linkPattern);
+ err = getErrorCodePtr(dict);
+ }
+
+ if (err == noErr) {
+ CFTypeRef activeRef;
+ err = -1;
+ if (::CFDictionaryGetValueIfPresent(dict, kSCPropNetLinkActive, &activeRef)) {
+ if (activeRef && ::CFGetTypeID(activeRef) == ::CFBooleanGetTypeID()) {
+ aResult = ::CFBooleanGetValue(static_cast<CFBooleanRef>(activeRef));
+ err = noErr;
+ }
+ }
+ }
+
+ CFReleaseSafe(dict);
+ CFReleaseSafe(linkPattern);
+ CFReleaseSafe(serviceNameRef);
+ return err;
+}
+
+void nsNetworkLinkService::GetDnsSuffixListInternal() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ RefPtr<nsNetworkLinkService> self = this;
+ auto sendNotification = mozilla::MakeScopeExit([self] {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "nsNetworkLinkService::GetDnsSuffixListInternal",
+ [self]() { self->NotifyObservers(NS_DNS_SUFFIX_LIST_UPDATED_TOPIC, nullptr); }));
+ });
+
+ nsAutoCString primaryServiceName;
+ nsAutoCString primaryServiceId;
+ OSStatus err = GetPrimaryServiceInfo(mStoreRef, primaryServiceName, primaryServiceId);
+
+ bool active = false;
+ if (err == noErr) {
+ err = IsInterfaceActive(mStoreRef, primaryServiceName.get(), active);
+ LOG(("primaryServiceName:[%s] active=%d", primaryServiceName.get(), active));
+ }
+
+ if (err != noErr || !active) {
+ // Primary interface is not active. Clear the DNS suffix list.
+ MutexAutoLock lock(mMutex);
+ mDNSSuffixList.Clear();
+ return;
+ }
+
+ nsTArray<nsCString> result;
+
+ CFStringRef serviceIdRef =
+ CFStringCreateWithCString(nullptr, primaryServiceId.get(), kCFStringEncodingUTF8);
+ err = getErrorCodePtr(serviceIdRef);
+ if (err == noErr) {
+ CFStringRef dnsSetupPattern = ::SCDynamicStoreKeyCreateNetworkServiceEntity(
+ nullptr, kSCDynamicStoreDomainSetup, serviceIdRef, kSCEntNetDNS);
+ CFStringRef dnsStatePattern = ::SCDynamicStoreKeyCreateNetworkServiceEntity(
+ nullptr, kSCDynamicStoreDomainState, serviceIdRef, kSCEntNetDNS);
+ err = getErrorCodePtr(dnsSetupPattern);
+ if (err == noErr) {
+ err = getErrorCodePtr(dnsStatePattern);
+ if (err == noErr) {
+ GetDNSSearchDomains(mStoreRef, dnsSetupPattern, result);
+ GetDNSSearchDomains(mStoreRef, dnsStatePattern, result);
+ }
+ }
+ CFReleaseSafe(dnsStatePattern);
+ CFReleaseSafe(dnsSetupPattern);
+ }
+ CFReleaseSafe(serviceIdRef);
+
+ if (err == noErr) {
+ 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;
+}
+
+#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;
+ if (sysctl(mib, 6, nullptr, &needed, nullptr, 0) < 0) {
+ return false;
+ }
+ if (needed == 0) {
+ LOG(("scanArp: empty table"));
+ return false;
+ }
+
+ UniquePtr<char[]> buf(new char[needed]);
+
+ for (;;) {
+ st = sysctl(mib, 6, &buf[0], &needed, nullptr, 0);
+ if (st == 0 || errno != ENOMEM) {
+ break;
+ }
+ needed += needed / 8;
+
+ auto tmp = MakeUnique<char[]>(needed);
+ memcpy(&tmp[0], &buf[0], needed);
+ buf = std::move(tmp);
+ }
+ 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;
+}
+
+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) {
+ // This 'addition' could potentially be a fixed number from the
+ // profile or something.
+ nsAutoCString addition("local-rubbish");
+ nsAutoCString output;
+ sha1.update(addition.get(), addition.Length());
+ 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();
+ }
+}
+
+void nsNetworkLinkService::DNSConfigChanged() {
+ LOG(("nsNetworkLinkService::DNSConfigChanged"));
+ nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ if (target) {
+ RefPtr<nsNetworkLinkService> self = this;
+ MOZ_ALWAYS_SUCCEEDS(
+ target->Dispatch(NS_NewRunnableFunction("nsNetworkLinkService::GetDnsSuffixListInternal",
+ [self]() { 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();
+
+ 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;
+ }
+
+ 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();
+}
diff --git a/netwerk/system/moz.build b/netwerk/system/moz.build
new file mode 100644
index 0000000000..4e9e29204a
--- /dev/null
+++ b/netwerk/system/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/.
+
+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"]
diff --git a/netwerk/system/netlink/NetlinkService.cpp b/netwerk/system/netlink/NetlinkService.cpp
new file mode 100644
index 0000000000..7c2c7d7e67
--- /dev/null
+++ b/netwerk/system/netlink/NetlinkService.cpp
@@ -0,0 +1,1838 @@
+/* -*- 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 <linux/rtnetlink.h>
+
+#include "nsThreadUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "NetlinkService.h"
+#include "nsIThread.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Logging.h"
+#include "../../base/IPv6Utils.h"
+#include "../NetworkLinkServiceDefines.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Services.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/DebugOnly.h"
+
+#if defined(HAVE_RES_NINIT)
+# include <netinet/in.h>
+# include <arpa/nameser.h>
+# include <resolv.h>
+#endif
+
+/* a shorter name that better explains what it does */
+#define EINTR_RETRY(x) MOZ_TEMP_FAILURE_RETRY(x)
+
+namespace mozilla::net {
+
+// 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)
+
+typedef union {
+ struct in_addr addr4;
+ struct in6_addr addr6;
+} in_common_addr;
+
+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() : mHasMAC(false) {}
+
+ 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;
+ 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))) {
+ 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))) {
+ return false;
+ }
+ if ((mHasDstAddr != aOther.mHasDstAddr) ||
+ (mHasDstAddr && memcmp(&mDstAddr, &(aOther.mDstAddr), addrSize))) {
+ return false;
+ }
+ if ((mHasPrefSrcAddr != aOther.mHasPrefSrcAddr) ||
+ (mHasPrefSrcAddr &&
+ memcmp(&mPrefSrcAddr, &(aOther.mPrefSrcAddr), addrSize))) {
+ 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() : mIsPending(false) {}
+ 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;
+};
+
+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()
+ : mMutex("NetlinkService::mMutex"),
+ mInitialScanFinished(false),
+ mMsgId(0),
+ mLinkUp(true),
+ mRecalculateNetworkId(false),
+ mSendNetworkChangeEvent(false) {
+ mPid = getpid();
+ mShutdownPipe[0] = -1;
+ mShutdownPipe[1] = -1;
+}
+
+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;
+ }
+
+ uint32_t linkIndex = link->GetIndex();
+ nsAutoCString linkName;
+ link->GetName(linkName);
+
+ LinkInfo* linkInfo = nullptr;
+ mLinks.Get(linkIndex, &linkInfo);
+
+ if (aNlh->nlmsg_type == RTM_NEWLINK) {
+ if (!linkInfo) {
+ LOG(("Creating new link [index=%u, name=%s, flags=%u, type=%u]",
+ linkIndex, linkName.get(), link->GetFlags(), link->GetType()));
+ linkInfo = new LinkInfo(std::move(link));
+ mLinks.Put(linkIndex, linkInfo);
+ } else {
+ LOG(("Updating link [index=%u, name=%s, flags=%u, type=%u]", linkIndex,
+ linkName.get(), link->GetFlags(), link->GetType()));
+
+ // 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 (!linkInfo) {
+ // 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()));
+ mLinks.Remove(linkIndex);
+ }
+ }
+}
+
+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.Put(key, neigh.release());
+ } 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(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 (auto iter = mLinks.ConstIter(); !iter.Done(); iter.Next()) {
+ LinkInfo* linkInfo = iter.UserData();
+ 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 (auto iter = mLinks.ConstIter(); !iter.Done(); iter.Next()) {
+ LinkInfo* linkInfo = iter.UserData();
+ 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::ComputeDNSSuffixList() {
+ MOZ_ASSERT(!NS_IsMainThread(), "Must not be called on the main thread");
+ nsTArray<nsCString> suffixList;
+#if defined(HAVE_RES_NINIT)
+ struct __res_state res;
+ if (res_ninit(&res) == 0) {
+ for (int i = 0; i < MAXDNSRCH; i++) {
+ if (!res.dnsrch[i]) {
+ break;
+ }
+ suffixList.AppendElement(nsCString(res.dnsrch[i]));
+ }
+ res_nclose(&res);
+ }
+#endif
+ RefPtr<NetlinkServiceListener> listener;
+ {
+ MutexAutoLock lock(mMutex);
+ listener = mListener;
+ mDNSSuffixList = std::move(suffixList);
+ }
+ 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();
+ ComputeDNSSuffixList();
+
+ bool idChanged = false;
+ bool found4 = CalculateIDForFamily(AF_INET, &sha1);
+ bool found6 = CalculateIDForFamily(AF_INET6, &sha1);
+
+ if (found4 || found6) {
+ // This 'addition' could potentially be a fixed number from the
+ // profile or something.
+ nsAutoCString addition("local-rubbish");
+ nsAutoCString output;
+ sha1.update(addition.get(), addition.Length());
+ 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
+}
+
+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..85796eb508
--- /dev/null
+++ b/netwerk/system/netlink/NetlinkService.h
@@ -0,0 +1,166 @@
+/* -*- 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 <linux/netlink.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"
+
+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);
+
+ private:
+ void EnqueueGenMsg(uint16_t aMsgType, uint8_t aFamily);
+ void EnqueueRtMsg(uint8_t aFamily, void* aAddress);
+ void RemovePendingMsg();
+
+ mozilla::Mutex 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 ComputeDNSSuffixList();
+
+ nsCOMPtr<nsIThread> mThread;
+
+ bool mInitialScanFinished;
+
+ // A pipe to signal shutdown with.
+ int mShutdownPipe[2];
+
+ // 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;
+
+ bool mLinkUp;
+
+ // Flag indicating that network ID could change and should be recalculated.
+ // Calculation is postponed until we receive responses to all enqueued
+ // messages.
+ bool mRecalculateNetworkId;
+
+ // Flag indicating that network change event needs to be sent even if
+ // network ID hasn't changed.
+ bool mSendNetworkChangeEvent;
+
+ // Time stamp of setting mRecalculateNetworkId to true
+ mozilla::TimeStamp mTriggerTime;
+
+ nsCString mNetworkId;
+ nsTArray<nsCString> mDNSSuffixList;
+
+ 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..0f4a2c2c96
--- /dev/null
+++ b/netwerk/system/win32/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"] == "WINNT":
+ SOURCES += [
+ "nsNotifyAddrListener.cpp",
+ ]
+
+FINAL_LIBRARY = "xul"
diff --git a/netwerk/system/win32/nsNotifyAddrListener.cpp b/netwerk/system/win32/nsNotifyAddrListener.cpp
new file mode 100644
index 0000000000..ce47ca99c1
--- /dev/null
+++ b/netwerk/system/win32/nsNotifyAddrListener.cpp
@@ -0,0 +1,670 @@
+/* -*- 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 "plstr.h"
+#include "mozilla/Logging.h"
+#include "nsComponentManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsIWindowsRegKey.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNotifyAddrListener.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Services.h"
+#include "nsCRT.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/Base64.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Telemetry.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::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%lX",
+ 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: %X", hr));
+ return;
+ }
+ RefPtr<IEnumNetworks> enumNetworks;
+ hr = nlm->GetNetworks(NLM_ENUM_NETWORK_CONNECTED,
+ getter_AddRefs(enumNetworks));
+ if (NS_WARN_IF(FAILED(hr))) {
+ LOG(("GetNetworks error: %X", 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);
+
+ 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", 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_DNS_SERVER | GAA_FLAG_SKIP_MULTICAST |
+ GAA_FLAG_SKIP_ANYCAST;
+
+ 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;
+ 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;
+ }
+
+ if (adapter->IfType == IF_TYPE_PPP) {
+ 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];
+ }
+ }
+
+ 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);
+ 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..8a39186bd3
--- /dev/null
+++ b/netwerk/system/win32/nsNotifyAddrListener.h
@@ -0,0 +1,93 @@
+/* -*- 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 "nsThreadUtils.h"
+#include "nsThreadPool.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/SHA1.h"
+
+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;
+ nsCString mNetworkId;
+ nsTArray<nsCString> mDnsSuffixList;
+
+ 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/browser.ini b/netwerk/test/browser/browser.ini
new file mode 100644
index 0000000000..dbe43345f2
--- /dev/null
+++ b/netwerk/test/browser/browser.ini
@@ -0,0 +1,31 @@
+[DEFAULT]
+support-files =
+ dummy.html
+ ioactivity.html
+ redirect.sjs
+
+[browser_about_cache.js]
+[browser_bug1535877.js]
+[browser_NetUtil.js]
+[browser_child_resource.js]
+skip-if = !crashreporter
+ (e10s && debug && os == "linux" && bits == 64)
+ debug # Bug 1370783
+ fission # Bug 1670867
+[browser_post_file.js]
+[browser_nsIFormPOSTActionChannel.js]
+skip-if = e10s # protocol handler and channel does not work in content process
+[browser_resource_navigation.js]
+[browser_test_io_activity.js]
+skip-if = socketprocess_networking
+[browser_cookie_sync_across_tabs.js]
+[browser_test_favicon.js]
+skip-if = (verify && (os == 'linux' || os == 'mac'))
+support-files =
+ damonbowling.jpg
+ damonbowling.jpg^headers^
+ file_favicon.html
+[browser_fetch_lnk.js]
+run-if = os == "win"
+support-files =
+ file_lnk.lnk
diff --git a/netwerk/test/browser/browser_NetUtil.js b/netwerk/test/browser/browser_NetUtil.js
new file mode 100644
index 0000000000..f8578fa4f8
--- /dev/null
+++ b/netwerk/test/browser/browser_NetUtil.js
@@ -0,0 +1,112 @@
+/*
+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.import("resource://testing-common/httpd.js", {});
+
+ 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..dba4204129
--- /dev/null
+++ b/netwerk/test/browser/browser_about_cache.js
@@ -0,0 +1,126 @@
+"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_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_child_resource.js b/netwerk/test/browser/browser_child_resource.js
new file mode 100644
index 0000000000..7535120f44
--- /dev/null
+++ b/netwerk/test/browser/browser_child_resource.js
@@ -0,0 +1,247 @@
+/*
+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 = "http://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 => {
+ const { Services } = ChromeUtils.import(
+ "resource://gre/modules/Services.jsm"
+ );
+ 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/Services.jsm");
+ let remote = await remoteResolveURI("resource://gre/modules/Services.jsm");
+ is(local, remote, "Services.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_sync_across_tabs.js b/netwerk/test/browser/browser_cookie_sync_across_tabs.js
new file mode 100644
index 0000000000..872e3cc4f5
--- /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..cbfc6a4c4b
--- /dev/null
+++ b/netwerk/test/browser/browser_fetch_lnk.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.file_unique_origin", false]],
+ });
+
+ 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_nsIFormPOSTActionChannel.js b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
new file mode 100644
index 0000000000..07387f8c09
--- /dev/null
+++ b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
@@ -0,0 +1,292 @@
+/*
+ * Tests for bug 1241377: A channel with nsIFormPOSTActionChannel interface
+ * should be able to accept form POST.
+ */
+
+/* eslint-env mozilla/frame-script */
+
+"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;
+ },
+ get defaultPort() {
+ return -1;
+ },
+ get protocolFlags() {
+ return (
+ Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE
+ );
+ },
+ newChannel(aURI, aLoadInfo) {
+ return new CustomChannel(aURI, aLoadInfo);
+ },
+ allowPort(port, scheme) {
+ return port != -1;
+ },
+
+ /** nsIFactory */
+ createInstance(aOuter, aIID) {
+ if (aOuter) {
+ throw Components.Exception("", Cr.NS_ERROR_NO_AGGREGATION);
+ }
+ return this.QueryInterface(aIID);
+ },
+ lockFactory() {},
+
+ /** nsISupports */
+ QueryInterface: ChromeUtils.generateQI(["nsIProtocolHandler", "nsIFactory"]),
+ classID: Components.ID("{16d594bc-d9d8-47ae-a139-ea714dc0c35c}"),
+};
+
+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() {
+ 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();
+ });
+}
+
+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();
+ var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(
+ handler.classID,
+ "",
+ "@mozilla.org/network/protocol;1?name=" + handler.scheme,
+ handler
+ );
+ registerCleanupFunction(function() {
+ registrar.unregisterFactory(handler.classID, handler);
+ });
+});
+
+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_file.js b/netwerk/test/browser/browser_post_file.js
new file mode 100644
index 0000000000..f8dc859cc0
--- /dev/null
+++ b/netwerk/test/browser/browser_post_file.js
@@ -0,0 +1,78 @@
+/*
+ * Tests for bug 1241100: Post to local file should not overwrite the file.
+ */
+"use strict";
+const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
+
+async function createTestFile(filename, content) {
+ let path = OS.Path.join(OS.Constants.Path.tmpDir, filename);
+ await OS.File.writeAtomic(path, content);
+ return path;
+}
+
+async function readFile(path) {
+ var array = await OS.File.read(path);
+ var decoder = new TextDecoder();
+ return decoder.decode(array);
+}
+
+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 = OS.Path.toFileURI(postPath);
+ var actionURI = OS.Path.toFileURI(actionPath);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ true,
+ actionURI
+ );
+ BrowserTestUtils.loadURI(tab.linkedBrowser, postURI);
+ await browserLoadedPromise;
+
+ var actionFileContentAfter = await readFile(actionPath);
+ is(actionFileContentAfter, actionFileContent, "action file is not modified");
+
+ await OS.File.remove(postPath);
+ await OS.File.remove(actionPath);
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/netwerk/test/browser/browser_resource_navigation.js b/netwerk/test/browser/browser_resource_navigation.js
new file mode 100644
index 0000000000..abcac2b554
--- /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_test_favicon.js b/netwerk/test/browser/browser_test_favicon.js
new file mode 100644
index 0000000000..2817385d20
--- /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..f847701888
--- /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/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/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_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/redirect.sjs b/netwerk/test/browser/redirect.sjs
new file mode 100644
index 0000000000..7599fe33fb
--- /dev/null
+++ b/netwerk/test/browser/redirect.sjs
@@ -0,0 +1,7 @@
+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/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/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..cfcedead02
--- /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; -moz-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; -moz-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..d47924ca79
--- /dev/null
+++ b/netwerk/test/crashtests/crashtests.list
@@ -0,0 +1,6 @@
+skip load 675518.html
+load 785753-1.html
+load 785753-2.html
+load 1274044-1.html
+skip-if(Android) pref(privacy.firstparty.isolate,true) load 1334468-1.html # Bug 1639080
+load 1399467-1.html
diff --git a/netwerk/test/fuzz/FuzzingStreamListener.cpp b/netwerk/test/fuzz/FuzzingStreamListener.cpp
new file mode 100644
index 0000000000..daf4f08e9d
--- /dev/null
+++ b/netwerk/test/fuzz/FuzzingStreamListener.cpp
@@ -0,0 +1,45 @@
+#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;
+ }
+ aOffset += toRead;
+ 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..97234bc7ba
--- /dev/null
+++ b/netwerk/test/fuzz/FuzzingStreamListener.h
@@ -0,0 +1,36 @@
+#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([&]() { return mChannelDone; });
+ }
+
+ bool isDone() { return mChannelDone; }
+
+ private:
+ ~FuzzingStreamListener() = default;
+ bool mChannelDone = false;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif
diff --git a/netwerk/test/fuzz/TestFtpFuzzing.cpp b/netwerk/test/fuzz/TestFtpFuzzing.cpp
new file mode 100644
index 0000000000..9fde504279
--- /dev/null
+++ b/netwerk/test/fuzz/TestFtpFuzzing.cpp
@@ -0,0 +1,141 @@
+#include "mozilla/Preferences.h"
+#include "mozilla/SpinEventLoopUntil.h"
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsString.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsScriptSecurityManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetUtil.h"
+#include "NullPrincipal.h"
+#include "nsCycleCollector.h"
+#include "nsIChannel.h"
+#include "nsSandboxFlags.h"
+
+#include "nsFtpProtocolHandler.h"
+
+#include "FuzzingInterface.h"
+#include "FuzzingStreamListener.h"
+#include "FuzzyLayer.h"
+
+namespace mozilla {
+namespace net {
+
+static nsAutoCString ftpSpec;
+
+static int FuzzingInitNetworkFtp(int* argc, char*** argv) {
+ Preferences::SetBool("network.dns.native-is-localhost", true);
+ Preferences::SetBool("fuzzing.necko.enabled", true);
+ Preferences::SetBool("network.connectivity-service.enabled", false);
+
+ if (ftpSpec.IsEmpty()) {
+ ftpSpec = "ftp://127.0.0.1/";
+ }
+
+ return 0;
+}
+
+static int FuzzingInitNetworkFtpDownload(int* argc, char*** argv) {
+ ftpSpec = "ftp://127.0.0.1/test.txt";
+ return FuzzingInitNetworkFtp(argc, argv);
+}
+
+static int FuzzingRunNetworkFtp(const uint8_t* data, size_t size) {
+ // Set the data to be processed
+ if (size > 1024) {
+ // If we have more than 1024 bytes, we use the excess for
+ // an optional data connection.
+ addNetworkFuzzingBuffer(data, 1024, true);
+ addNetworkFuzzingBuffer(data + 1024, size - 1024, true, true);
+ } else {
+ addNetworkFuzzingBuffer(data, size, true);
+ }
+
+ nsWeakPtr channelRef;
+
+ {
+ nsCOMPtr<nsIURI> url;
+ nsAutoCString spec;
+ nsresult rv;
+
+ if (NS_NewURI(getter_AddRefs(url), ftpSpec) != 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;
+ 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 (rv != NS_OK) {
+ MOZ_CRASH("Call to NS_NewChannel failed.");
+ }
+
+ RefPtr<FuzzingStreamListener> gStreamListener;
+
+ gStreamListener = new FuzzingStreamListener();
+
+ rv = channel->AsyncOpen(gStreamListener);
+
+ if (NS_FAILED(rv)) {
+ MOZ_CRASH("asyncOpen failed");
+ }
+
+ FUZZING_LOG(("Spinning for StopRequest"));
+
+ // Wait for StopRequest
+ gStreamListener->waitUntilDone();
+
+ // The FTP code can cache control connections which would cause
+ // a strong reference to be held to our channel. After performing
+ // all requests here, we need to prune all cached connections to
+ // let the channel die. In the future we can test the caching further
+ // with multiple requests using a cached control connection.
+ gFtpHandler->Observe(nullptr, "net:clear-active-logins", nullptr);
+
+ channelRef = do_GetWeakReference(channel);
+ }
+
+ FUZZING_LOG(("Spinning for channel == nullptr"));
+
+ // Wait for the channel to be destroyed
+ SpinEventLoopUntil([&]() -> bool {
+ nsCycleCollector_collect(nullptr);
+ nsCOMPtr<nsIChannel> channel = do_QueryReferent(channelRef);
+ return channel == nullptr;
+ });
+
+ if (!signalNetworkFuzzingDone()) {
+ // Wait for the connection to indicate closed
+ FUZZING_LOG(("Spinning for gFuzzingConnClosed"));
+ SpinEventLoopUntil([&]() -> bool { return gFuzzingConnClosed; });
+ }
+
+ FUZZING_LOG(("End of iteration"));
+ return 0;
+}
+
+MOZ_FUZZING_INTERFACE_RAW(FuzzingInitNetworkFtp, FuzzingRunNetworkFtp,
+ NetworkFtp);
+
+MOZ_FUZZING_INTERFACE_RAW(FuzzingInitNetworkFtpDownload, FuzzingRunNetworkFtp,
+ NetworkFtpDownload);
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/test/fuzz/TestHttpFuzzing.cpp b/netwerk/test/fuzz/TestHttpFuzzing.cpp
new file mode 100644
index 0000000000..5aabd903d8
--- /dev/null
+++ b/netwerk/test/fuzz/TestHttpFuzzing.cpp
@@ -0,0 +1,274 @@
+#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 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.spdy.default-concurrent", 1);
+
+ if (httpSpec.IsEmpty()) {
+ httpSpec = "http://127.0.0.1/";
+ }
+
+ return 0;
+}
+
+static int FuzzingInitNetworkHttp2(int* argc, char*** argv) {
+ httpSpec = "https://127.0.0.1/";
+ 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) {
+ // 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([&]() -> bool { return mainPingBack; });
+
+ channelRef = do_GetWeakReference(gHttpChannel);
+ }
+
+ // Wait for the channel to be destroyed
+ SpinEventLoopUntil([&]() -> bool {
+ nsCycleCollector_collect(nullptr);
+ nsCOMPtr<nsIHttpChannel> channel = do_QueryReferent(channelRef);
+ return channel == nullptr;
+ });
+
+ if (!signalNetworkFuzzingDone()) {
+ // Wait for the connection to indicate closed
+ SpinEventLoopUntil([&]() -> 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(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/TestURIFuzzing.cpp b/netwerk/test/fuzz/TestURIFuzzing.cpp
new file mode 100644
index 0000000000..bbd76706fc
--- /dev/null
+++ b/netwerk/test/fuzz/TestURIFuzzing.cpp
@@ -0,0 +1,240 @@
+/* -*- 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_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec(spec)
+ .Finalize(uri);
+
+ 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..517a24c28c
--- /dev/null
+++ b/netwerk/test/fuzz/TestWebsocketFuzzing.cpp
@@ -0,0 +1,222 @@
+#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([&]() { return mChannelDone || mChannelStarted; });
+ }
+
+ void waitUntilDone() {
+ SpinEventLoopUntil([&]() { return mChannelDone; });
+ }
+
+ void waitUntilDoneOrAck() {
+ SpinEventLoopUntil([&]() { 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);
+ 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();
+
+ rv =
+ gWebSocketChannel->AsyncOpen(url, spec, 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([&]() -> bool {
+ nsCycleCollector_collect(nullptr);
+ nsCOMPtr<nsIWebSocketChannel> channel = do_QueryReferent(channelRef);
+ return channel == nullptr;
+ });
+
+ if (!signalNetworkFuzzingDone()) {
+ // Wait for the connection to indicate closed
+ SpinEventLoopUntil([&]() -> 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..4a8787c7d9
--- /dev/null
+++ b/netwerk/test/fuzz/moz.build
@@ -0,0 +1,29 @@
+# -*- 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",
+ "TestFtpFuzzing.cpp",
+ "TestHttpFuzzing.cpp",
+ "TestURIFuzzing.cpp",
+ "TestWebsocketFuzzing.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/caps",
+ "/netwerk/base",
+ "/netwerk/protocol/ftp",
+ "/netwerk/protocol/http",
+ "/xpcom/tests/gtest",
+]
+
+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..37f5cb824e
--- /dev/null
+++ b/netwerk/test/gtest/TestBase64Stream.cpp
@@ -0,0 +1,95 @@
+/* -*- 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 "nsIInputStream.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 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_TRUE(NS_SUCCEEDED(rv));
+
+ EXPECT_TRUE(encodedData.EqualsLiteral("SGVsbG8gV29ybGQh"));
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/test/gtest/TestBind.cpp b/netwerk/test/gtest/TestBind.cpp
new file mode 100644
index 0000000000..e9ee9a7153
--- /dev/null
+++ b/netwerk/test/gtest/TestBind.cpp
@@ -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/. */
+
+#include "TestCommon.h"
+#include "gtest/gtest.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsIServerSocket.h"
+#include "nsIAsyncInputStream.h"
+#include "mozilla/net/DNS.h"
+#include "prerror.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_TRUE(NS_SUCCEEDED(rv));
+
+ int32_t serverPort;
+ rv = server->GetPort(&serverPort);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ RefPtr<WaitForCondition> waiter = new WaitForCondition();
+
+ // Listening.
+ RefPtr<ServerListener> serverListener = new ServerListener(waiter);
+ rv = server->AsyncListen(serverListener);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ //
+ // Client side
+ //
+ uint32_t bindingPort = 20000;
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1", &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ RefPtr<ClientInputCallback> clientCallback;
+
+ for (int32_t tried = 0; tried < 100; tried++) {
+ nsCOMPtr<nsISocketTransport> client;
+ rv = sts->CreateTransport(nsTArray<nsCString>(), "127.0.0.1"_ns, serverPort,
+ nullptr, getter_AddRefs(client));
+ ASSERT_TRUE(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_TRUE(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_TRUE(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..09527318c5
--- /dev/null
+++ b/netwerk/test/gtest/TestBufferedInputStream.cpp
@@ -0,0 +1,183 @@
+#include "gtest/gtest.h"
+
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsBufferedStreams.h"
+#include "nsStreamUtils.h"
+#include "nsIThread.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([&]() { 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([&]() { 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([&]() { 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([&]() { 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([&]() { 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([&]() { return cb->Called(); }));
+ ASSERT_TRUE(cb->Called());
+}
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..67ff55b1fb
--- /dev/null
+++ b/netwerk/test/gtest/TestCommon.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 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([&]() { 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..6e6cc9961d
--- /dev/null
+++ b/netwerk/test/gtest/TestCookie.cpp
@@ -0,0 +1,1062 @@
+/* -*- 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/Preferences.h"
+#include "mozilla/Unused.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "Cookie.h"
+#include "nsIURI.h"
+
+using mozilla::Unused;
+
+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_TRUE(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::GetBlockingAll();
+ MOZ_ASSERT(cookieJarSettings);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = dummyChannel->LoadInfo();
+ loadInfo->SetCookieJarSettings(cookieJarSettings);
+
+ nsresult rv = aCookieService->SetCookieStringFromHttp(
+ uri, nsDependentCString(aCookieString), dummyChannel);
+ EXPECT_TRUE(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 PL_strstr(aLhs, aRhs) != nullptr;
+
+ case MUST_NOT_CONTAIN:
+ return PL_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.whitelist_onions", false);
+ Preferences::SetBool("network.cookie.sameSite.schemeful", false);
+}
+
+TEST(TestCookie, TestCookieMain)
+{
+ nsresult rv0;
+
+ nsCOMPtr<nsICookieService> cookieService =
+ do_GetService(kCookieServiceCID, &rv0);
+ ASSERT_TRUE(NS_SUCCEEDED(rv0));
+
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(kPrefServiceCID, &rv0);
+ ASSERT_TRUE(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"));
+
+ Preferences::SetBool("network.cookie.sameSite.schemeful", true);
+ GetACookieNoHttp(cookieService, "http://www.security.test/", cookie);
+ EXPECT_FALSE(CheckResult(cookie.get(), MUST_CONTAIN, "test=security3"));
+ Preferences::SetBool("network.cookie.sameSite.schemeful", false);
+
+ // *** nsICookieManager interface tests
+ nsCOMPtr<nsICookieManager> cookieMgr =
+ do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv0);
+ ASSERT_TRUE(NS_SUCCEEDED(rv0));
+
+ nsCOMPtr<nsICookieManager> cookieMgr2 = cookieMgr;
+ ASSERT_TRUE(cookieMgr2);
+
+ mozilla::OriginAttributes attrs;
+
+ // first, ensure a clean slate
+ EXPECT_TRUE(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_TRUE(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_TRUE(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_TRUE(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_TRUE(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_TRUE(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_TRUE(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_TRUE(NS_SUCCEEDED(rv));
+
+ nsCOMPtr<nsICookieManager> cookieMgr =
+ do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ EXPECT_TRUE(NS_SUCCEEDED(cookieMgr->RemoveAll()));
+
+ SetACookie(cookieService, "http://samesite.test", "unset=yes");
+
+ nsTArray<RefPtr<nsICookie>> cookies;
+ EXPECT_TRUE(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_TRUE(NS_SUCCEEDED(cookieMgr->RemoveAll()));
+
+ cookies.SetLength(0);
+ EXPECT_TRUE(NS_SUCCEEDED(cookieMgr->GetCookies(cookies)));
+ EXPECT_EQ(cookies.Length(), (uint64_t)0);
+
+ SetACookie(cookieService, "http://samesite.test", "unset=yes");
+
+ cookies.SetLength(0);
+ EXPECT_TRUE(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_NONE);
+}
+
+TEST(TestCookie, OnionSite)
+{
+ Preferences::SetBool("dom.securecontext.whitelist_onions", true);
+ Preferences::SetBool("network.cookie.sameSite.laxByDefault", false);
+
+ nsresult rv;
+ nsCString cookie;
+
+ nsCOMPtr<nsICookieService> cookieService =
+ do_GetService(kCookieServiceCID, &rv);
+ ASSERT_TRUE(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"));
+}
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/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/TestHttpResponseHead.cpp b/netwerk/test/gtest/TestHttpResponseHead.cpp
new file mode 100644
index 0000000000..2a9a12e21a
--- /dev/null
+++ b/netwerk/test/gtest/TestHttpResponseHead.cpp
@@ -0,0 +1,109 @@
+#include "gtest/gtest.h"
+
+#include "chrome/common/ipc_message.h"
+#include "mozilla/net/PHttpChannelParams.h"
+#include "mozilla/Unused.h"
+#include "nsHttp.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::ParamTraits<nsHttpResponseHead>::Write(msg.get(), aHead);
+
+ nsHttpResponseHead deserializedHead;
+ PickleIterator iter(*msg);
+ bool res = IPC::ParamTraits<mozilla::net::nsHttpResponseHead>::Read(
+ msg.get(), &iter, &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;
+ ASSERT_EQ(aHead, copied);
+ }
+}
+
+TEST(TestHttpResponseHead, Bug1636930)
+{
+ // Only create atom table when it's not already created.
+ if (!nsHttp::ResolveAtom("content-type")) {
+ Unused << nsHttp::CreateAtomTable();
+ }
+
+ nsHttpResponseHead head;
+
+ 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)
+{
+ // Only create atom table when it's not already created.
+ if (!nsHttp::ResolveAtom("content-type")) {
+ Unused << nsHttp::CreateAtomTable();
+ }
+
+ nsHttpResponseHead head;
+
+ 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)
+{
+ // Only create atom table when it's not already created.
+ if (!nsHttp::ResolveAtom("content-type")) {
+ Unused << nsHttp::CreateAtomTable();
+ }
+
+ nsHttpResponseHead head;
+
+ 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);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/test/gtest/TestInputStreamTransport.cpp b/netwerk/test/gtest/TestInputStreamTransport.cpp
new file mode 100644
index 0000000000..ac133cab16
--- /dev/null
+++ b/netwerk/test/gtest/TestInputStreamTransport.cpp
@@ -0,0 +1,190 @@
+#include "gtest/gtest.h"
+
+#include "nsIStreamTransportService.h"
+#include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
+#include "Helpers.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
+ 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
+ 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 {
+ 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/TestMIMEInputStream.cpp b/netwerk/test/gtest/TestMIMEInputStream.cpp
new file mode 100644
index 0000000000..ce7f5bd47d
--- /dev/null
+++ b/netwerk/test/gtest/TestMIMEInputStream.cpp
@@ -0,0 +1,261 @@
+#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"
+
+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(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([&]() { return callback->Called(); }));
+ ASSERT_EQ(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([&]() { 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([&]() { 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..de0d69f28d
--- /dev/null
+++ b/netwerk/test/gtest/TestMozURL.cpp
@@ -0,0 +1,390 @@
+#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 "mozilla/net/MozURL.h"
+#include "nsCOMPtr.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsNetUtil.h"
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsStreamUtils.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 (uint32_t index = 0; index < root.size(); index++) {
+ const Json::Value& item = root[index];
+
+ 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);
+
+ const Json::Value& base = item["base"];
+ ASSERT_TRUE(base.isString());
+ const char* baseBegin;
+ const char* baseEnd;
+ base.getString(&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),
+ nsDependentCString(baseBegin, baseEnd),
+ nsDependentCString(originBegin, originEnd));
+ }
+}
diff --git a/netwerk/test/gtest/TestNamedPipeService.cpp b/netwerk/test/gtest/TestNamedPipeService.cpp
new file mode 100644
index 0000000000..c26424e46c
--- /dev/null
+++ b/netwerk/test/gtest/TestNamedPipeService.cpp
@@ -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/. */
+
+#include "TestCommon.h"
+#include "gtest/gtest.h"
+
+#include <windows.h>
+
+#include "mozilla/Atomics.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;
+ 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;
+
+ 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_TRUE(NS_SUCCEEDED(rv));
+
+ RefPtr<nsNamedPipeDataObserver> readObserver =
+ new nsNamedPipeDataObserver(readPipe);
+ RefPtr<nsNamedPipeDataObserver> writeObserver =
+ new nsNamedPipeDataObserver(writePipe);
+
+ ASSERT_TRUE(NS_SUCCEEDED(svc->AddDataObserver(readPipe, readObserver)));
+ ASSERT_TRUE(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_TRUE(NS_SUCCEEDED(svc->RemoveDataObserver(readPipe, readObserver)));
+ ASSERT_TRUE(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..52a34b4897
--- /dev/null
+++ b/netwerk/test/gtest/TestPACMan.cpp
@@ -0,0 +1,242 @@
+#include "gtest/gtest.h"
+#include "nsServiceManagerUtils.h"
+#include "../../../xpcom/threads/nsThreadManager.h"
+#include "nsIDHCPClient.h"
+#include "nsIPrefBranch.h"
+#include "nsComponentManager.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 = 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/TestPartiallySeekableInputStream.cpp b/netwerk/test/gtest/TestPartiallySeekableInputStream.cpp
new file mode 100644
index 0000000000..1dffd6fcaf
--- /dev/null
+++ b/netwerk/test/gtest/TestPartiallySeekableInputStream.cpp
@@ -0,0 +1,494 @@
+#include "gtest/gtest.h"
+
+#include "Helpers.h"
+#include "nsCOMPtr.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsStringStream.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/net/PartiallySeekableInputStream.h"
+
+using mozilla::GetCurrentSerialEventTarget;
+using mozilla::SpinEventLoopUntil;
+using mozilla::net::PartiallySeekableInputStream;
+
+class NonSeekableStream final : public nsIInputStream {
+ nsCOMPtr<nsIInputStream> mStream;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit NonSeekableStream(const nsACString& aBuffer) {
+ NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer);
+ }
+
+ NS_IMETHOD
+ Available(uint64_t* aLength) override { return mStream->Available(aLength); }
+
+ 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 {
+ return mStream->IsNonBlocking(aNonBlocking);
+ }
+
+ private:
+ ~NonSeekableStream() = default;
+};
+
+NS_IMPL_ISUPPORTS(NonSeekableStream, nsIInputStream)
+
+// Helper function for creating a non-seekable nsIInputStream + a
+// PartiallySeekableInputStream.
+PartiallySeekableInputStream* CreateStream(uint32_t aSize, uint64_t aStreamSize,
+ nsCString& aBuffer) {
+ aBuffer.SetLength(aSize);
+ for (uint32_t i = 0; i < aSize; ++i) {
+ aBuffer.BeginWriting()[i] = i % 10;
+ }
+
+ RefPtr<NonSeekableStream> stream = new NonSeekableStream(aBuffer);
+ return new PartiallySeekableInputStream(stream.forget(), aStreamSize);
+}
+
+// Simple reading.
+TEST(TestPartiallySeekableInputStream, SimpleRead)
+{
+ const size_t kBufSize = 10;
+
+ nsCString buf;
+ RefPtr<PartiallySeekableInputStream> psi = CreateStream(kBufSize, 5, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, psi->Available(&length));
+ ASSERT_EQ((uint64_t)kBufSize, length);
+
+ char buf2[kBufSize];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ(count, buf.Length());
+ ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count)));
+
+ // At this point, after reading more than the buffer size, seek is not
+ // allowed.
+ ASSERT_EQ(NS_ERROR_NOT_IMPLEMENTED,
+ psi->Seek(nsISeekableStream::NS_SEEK_SET, 0));
+
+ ASSERT_EQ(NS_ERROR_NOT_IMPLEMENTED,
+ psi->Seek(nsISeekableStream::NS_SEEK_END, 0));
+
+ ASSERT_EQ(NS_ERROR_NOT_IMPLEMENTED,
+ psi->Seek(nsISeekableStream::NS_SEEK_CUR, 0));
+
+ // Position is at the end of the stream.
+ int64_t pos;
+ ASSERT_EQ(NS_OK, psi->Tell(&pos));
+ ASSERT_EQ((int64_t)kBufSize, pos);
+}
+
+// Simple seek
+TEST(TestPartiallySeekableInputStream, SimpleSeek)
+{
+ const size_t kBufSize = 10;
+
+ nsCString buf;
+ RefPtr<PartiallySeekableInputStream> psi = CreateStream(kBufSize, 5, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, psi->Available(&length));
+ ASSERT_EQ((uint64_t)kBufSize, length);
+
+ uint32_t count;
+
+ {
+ char buf2[3];
+ ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ(count, sizeof(buf2));
+ ASSERT_TRUE(nsCString(buf.get(), sizeof(buf2))
+ .Equals(nsCString(buf2, sizeof(buf2))));
+
+ int64_t pos;
+ ASSERT_EQ(NS_OK, psi->Tell(&pos));
+ ASSERT_EQ((int64_t)sizeof(buf2), pos);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, psi->Available(&length));
+ ASSERT_EQ((uint64_t)kBufSize - sizeof(buf2), length);
+ }
+
+ // Let's seek back to the beginning using NS_SEEK_SET
+ ASSERT_EQ(NS_OK, psi->Seek(nsISeekableStream::NS_SEEK_SET, 0));
+
+ {
+ uint64_t length;
+ ASSERT_EQ(NS_OK, psi->Available(&length));
+ ASSERT_EQ((uint64_t)kBufSize, length);
+
+ char buf2[3];
+ ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ(count, sizeof(buf2));
+ ASSERT_TRUE(nsCString(buf.get(), sizeof(buf2))
+ .Equals(nsCString(buf2, sizeof(buf2))));
+
+ int64_t pos;
+ ASSERT_EQ(NS_OK, psi->Tell(&pos));
+ ASSERT_EQ((int64_t)sizeof(buf2), pos);
+
+ ASSERT_EQ(NS_OK, psi->Available(&length));
+ ASSERT_EQ((uint64_t)kBufSize - sizeof(buf2), length);
+ }
+
+ // Let's seek back of 2 bytes using NS_SEEK_CUR
+ ASSERT_EQ(NS_OK, psi->Seek(nsISeekableStream::NS_SEEK_CUR, -2));
+
+ {
+ uint64_t length;
+ ASSERT_EQ(NS_OK, psi->Available(&length));
+ ASSERT_EQ((uint64_t)kBufSize - 1, length);
+
+ char buf2[3];
+ ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ(count, sizeof(buf2));
+ ASSERT_TRUE(nsCString(buf.get() + 1, sizeof(buf2))
+ .Equals(nsCString(buf2, sizeof(buf2))));
+
+ int64_t pos;
+ ASSERT_EQ(NS_OK, psi->Tell(&pos));
+ ASSERT_EQ((int64_t)sizeof(buf2) + 1, pos);
+
+ ASSERT_EQ(NS_OK, psi->Available(&length));
+ ASSERT_EQ((uint64_t)kBufSize - sizeof(buf2) - 1, length);
+ }
+
+ // Let's seek back to the beginning using NS_SEEK_SET
+ ASSERT_EQ(NS_OK, psi->Seek(nsISeekableStream::NS_SEEK_SET, 0));
+
+ {
+ uint64_t length;
+ ASSERT_EQ(NS_OK, psi->Available(&length));
+ ASSERT_EQ((uint64_t)kBufSize, length);
+
+ char buf2[kBufSize];
+ ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ(count, buf.Length());
+ ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count)));
+ }
+}
+
+// Full in cache
+TEST(TestPartiallySeekableInputStream, FullCachedSeek)
+{
+ const size_t kBufSize = 10;
+
+ nsCString buf;
+ RefPtr<PartiallySeekableInputStream> psi = CreateStream(kBufSize, 4096, buf);
+
+ uint64_t length;
+ ASSERT_EQ(NS_OK, psi->Available(&length));
+ ASSERT_EQ((uint64_t)kBufSize, length);
+
+ char buf2[kBufSize];
+ uint32_t count;
+ ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ(count, buf.Length());
+ ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count)));
+
+ ASSERT_EQ(NS_OK, psi->Available(&length));
+ ASSERT_EQ((uint64_t)0, length);
+
+ ASSERT_EQ(NS_OK, psi->Seek(nsISeekableStream::NS_SEEK_SET, 0));
+
+ ASSERT_EQ(NS_OK, psi->Available(&length));
+ ASSERT_EQ((uint64_t)kBufSize, length);
+
+ ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count));
+ ASSERT_EQ(count, buf.Length());
+ ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count)));
+
+ ASSERT_EQ(NS_OK, psi->Available(&length));
+ ASSERT_EQ((uint64_t)0, length);
+}
+
+TEST(TestPartiallySeekableInputStream, QIInputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ for (int i = 0; i < 4; i++) {
+ nsCOMPtr<nsIInputStream> psis;
+ {
+ RefPtr<testing::LengthInputStream> stream =
+ new testing::LengthInputStream(buf, i % 2, i > 1);
+ psis = new PartiallySeekableInputStream(stream.forget());
+ }
+
+ {
+ nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(psis);
+ ASSERT_EQ(!!(i % 2), !!qi);
+ }
+
+ {
+ nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(psis);
+ ASSERT_EQ(i > 1, !!qi);
+ }
+ }
+}
+
+TEST(TestPartiallySeekableInputStream, InputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> psis;
+ {
+ RefPtr<testing::LengthInputStream> stream =
+ new testing::LengthInputStream(buf, true, false);
+ psis = new PartiallySeekableInputStream(stream.forget());
+ }
+
+ nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(psis);
+ ASSERT_TRUE(!!qi);
+
+ int64_t size;
+ nsresult rv = qi->Length(&size);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ(buf.Length(), size);
+}
+
+TEST(TestPartiallySeekableInputStream, NegativeInputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> psis;
+ {
+ RefPtr<testing::LengthInputStream> stream =
+ new testing::LengthInputStream(buf, true, false, NS_OK, true);
+ psis = new PartiallySeekableInputStream(stream.forget());
+ }
+
+ nsCOMPtr<nsIInputStreamLength> qi = do_QueryInterface(psis);
+ ASSERT_TRUE(!!qi);
+
+ int64_t size;
+ nsresult rv = qi->Length(&size);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ(-1, size);
+}
+
+TEST(TestPartiallySeekableInputStream, AsyncInputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> psis;
+ {
+ RefPtr<testing::LengthInputStream> stream =
+ new testing::LengthInputStream(buf, false, true);
+ psis = new PartiallySeekableInputStream(stream.forget());
+ }
+
+ nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(psis);
+ 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([&]() { return callback->Called(); }));
+ ASSERT_EQ(buf.Length(), callback->Size());
+}
+
+TEST(TestPartiallySeekableInputStream, NegativeAsyncInputStreamLength)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> psis;
+ {
+ RefPtr<testing::LengthInputStream> stream =
+ new testing::LengthInputStream(buf, false, true, NS_OK, true);
+ psis = new PartiallySeekableInputStream(stream.forget());
+ }
+
+ nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(psis);
+ 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([&]() { return callback->Called(); }));
+ ASSERT_EQ(-1, callback->Size());
+}
+
+TEST(TestPartiallySeekableInputStream, AbortLengthCallback)
+{
+ nsCString buf;
+ buf.AssignLiteral("Hello world");
+
+ nsCOMPtr<nsIInputStream> psis;
+ {
+ RefPtr<testing::LengthInputStream> stream =
+ new testing::LengthInputStream(buf, false, true, NS_OK, true);
+ psis = new PartiallySeekableInputStream(stream.forget());
+ }
+
+ nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(psis);
+ 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([&]() { return callback2->Called(); }));
+ ASSERT_TRUE(!callback1->Called());
+ ASSERT_EQ(-1, callback2->Size());
+}
+
+TEST(TestPartiallySeekableInputStream, AsyncWaitAfterConsumed)
+{
+ nsCString buf{"The Quick Brown Fox Jumps over the Lazy Dog"};
+ const size_t bufsize = 44;
+
+ auto stream = MakeRefPtr<testing::AsyncStringStream>(buf);
+ nsCOMPtr<nsIAsyncInputStream> psis =
+ new PartiallySeekableInputStream(stream.forget(), bufsize);
+ ASSERT_TRUE(psis);
+
+ auto callback = MakeRefPtr<testing::InputStreamCallback>();
+
+ nsresult rv = psis->AsyncWait(callback, 0, 0, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return callback->Called(); }));
+
+ char rdbuf[bufsize] = {'\0'};
+ uint32_t count;
+ ASSERT_EQ(NS_OK, psis->Read(rdbuf, sizeof(rdbuf), &count));
+ ASSERT_STREQ(rdbuf, buf.Data());
+
+ callback = MakeRefPtr<testing::InputStreamCallback>();
+
+ rv = psis->AsyncWait(callback, 0, 0, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return callback->Called(); }));
+
+ memset(rdbuf, 0x0, bufsize);
+ ASSERT_EQ(NS_OK, psis->Read(rdbuf, sizeof(rdbuf), &count));
+ ASSERT_EQ(0U, count);
+}
+
+TEST(TestPartiallySeekableInputStream, AsyncWaitAfterClosed)
+{
+ nsCString buf{"The Quick Brown Fox Jumps over the Lazy Dog"};
+ const size_t bufsize = 44;
+
+ auto stream = MakeRefPtr<testing::AsyncStringStream>(buf);
+ nsCOMPtr<nsIAsyncInputStream> psis =
+ new PartiallySeekableInputStream(stream.forget(), bufsize);
+ ASSERT_TRUE(psis);
+
+ auto callback = MakeRefPtr<testing::InputStreamCallback>();
+
+ nsresult rv = psis->AsyncWait(callback, 0, 0, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return callback->Called(); }));
+
+ ASSERT_EQ(NS_OK, psis->Close());
+
+ callback = MakeRefPtr<testing::InputStreamCallback>();
+
+ rv = psis->AsyncWait(callback, 0, 0, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return callback->Called(); }));
+}
+
+TEST(TestPartiallySeekableInputStream, AsyncLengthWaitAfterClosed)
+{
+ nsCString buf{"The Quick Brown Fox Jumps over the Lazy Dog"};
+
+ auto stream = MakeRefPtr<testing::LengthInputStream>(buf, false, true);
+ nsCOMPtr<nsIInputStream> psis =
+ new PartiallySeekableInputStream(stream.forget());
+
+ nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(psis);
+ ASSERT_TRUE(qi);
+
+ auto callback = MakeRefPtr<testing::LengthCallback>();
+
+ nsresult rv = qi->AsyncLengthWait(callback, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return callback->Called(); }));
+ ASSERT_EQ(buf.Length(), callback->Size());
+
+ ASSERT_EQ(NS_OK, psis->Close());
+
+ callback = MakeRefPtr<testing::LengthCallback>();
+
+ rv = qi->AsyncLengthWait(callback, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return callback->Called(); }));
+ ASSERT_EQ(-1, callback->Size());
+}
+
+TEST(TestPartiallySeekableInputStream, AsyncLengthWaitAfterConsumed)
+{
+ nsCString buf{"The Quick Brown Fox Jumps over the Lazy Dog"};
+ const size_t bufsize = 44;
+
+ auto stream = MakeRefPtr<testing::LengthInputStream>(buf, false, true);
+ nsCOMPtr<nsIInputStream> psis =
+ new PartiallySeekableInputStream(stream.forget());
+
+ nsCOMPtr<nsIAsyncInputStreamLength> qi = do_QueryInterface(psis);
+ ASSERT_TRUE(qi);
+
+ auto callback = MakeRefPtr<testing::LengthCallback>();
+
+ nsresult rv = qi->AsyncLengthWait(callback, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return callback->Called(); }));
+ ASSERT_EQ(buf.Length(), callback->Size());
+
+ char rdbuf[bufsize] = {'\0'};
+ uint32_t count;
+ ASSERT_EQ(NS_OK, psis->Read(rdbuf, sizeof(rdbuf), &count));
+ ASSERT_STREQ(rdbuf, buf.Data());
+
+ callback = MakeRefPtr<testing::LengthCallback>();
+
+ rv = qi->AsyncLengthWait(callback, GetCurrentSerialEventTarget());
+ ASSERT_EQ(NS_OK, rv);
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return callback->Called(); }));
+ ASSERT_EQ(0U, callback->Size());
+
+ memset(rdbuf, 0x0, bufsize);
+ ASSERT_EQ(NS_OK, psis->Read(rdbuf, sizeof(rdbuf), &count));
+ ASSERT_EQ(0U, count);
+}
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/TestServerTimingHeader.cpp b/netwerk/test/gtest/TestServerTimingHeader.cpp
new file mode 100644
index 0000000000..159e5bd7bc
--- /dev/null
+++ b/netwerk/test/gtest/TestServerTimingHeader.cpp
@@ -0,0 +1,239 @@
+#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("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("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("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("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", ""}});
+
+ // 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(
+ " 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..ce8c301c7d
--- /dev/null
+++ b/netwerk/test/gtest/TestSocketTransportService.cpp
@@ -0,0 +1,140 @@
+#include "gtest/gtest.h"
+
+#include "nsCOMPtr.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);
+
+ sts->Dispatch(
+ 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));
+ }
+ }),
+ NS_DISPATCH_SYNC);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/test/gtest/TestStandardURL.cpp b/netwerk/test/gtest/TestStandardURL.cpp
new file mode 100644
index 0000000000..b6a6e71764
--- /dev/null
+++ b/netwerk/test/gtest/TestStandardURL.cpp
@@ -0,0 +1,359 @@
+#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"
+
+// In nsStandardURL.cpp
+extern nsresult Test_NormalizeIPv4(const nsACString& host, nsCString& result);
+
+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", "0x.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 (uint32_t i = 0; i < sizeof(manual) / sizeof(manual[0]); i++) {
+ nsCString encHost(manual[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 (uint32_t i = 0; i < sizeof(localIPv4s) / sizeof(localIPv4s[0]); i++) {
+ nsCString encHost(localIPv4s[i]);
+ 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"};
+ for (uint32_t i = 0; i < sizeof(nonIPv4s) / sizeof(nonIPv4s[0]); i++) {
+ nsCString encHost(nonIPv4s[i]);
+ ASSERT_EQ(NS_ERROR_FAILURE, Test_NormalizeIPv4(encHost, result));
+ }
+}
+
+#define 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 = 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);
+}
diff --git a/netwerk/test/gtest/TestSupportAlpn.cpp b/netwerk/test/gtest/TestSupportAlpn.cpp
new file mode 100644
index 0000000000..ee67c854e6
--- /dev/null
+++ b/netwerk/test/gtest/TestSupportAlpn.cpp
@@ -0,0 +1,61 @@
+#include "gtest/gtest.h"
+
+#include "mozilla/Preferences.h"
+#include "nsIHttpProtocolHandler.h"
+#include "nsHttp.h"
+
+namespace mozilla {
+namespace net {
+
+TEST(TestSupportAlpn, testSvcParamKeyAlpn)
+{
+ Preferences::SetBool("network.http.http3.enabled", true);
+ // initialize gHttphandler.
+ nsCOMPtr<nsIHttpProtocolHandler> http =
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http");
+
+ // h2 and h3 are enabled, so first appearance h3 alpn-id is returned.
+ nsTArray<nsCString> alpn = {"h3-28"_ns, "h3-27"_ns, "h2"_ns, "http/1.1"_ns};
+ Tuple<nsCString, bool> result = SelectAlpnFromAlpnList(alpn, false, false);
+ ASSERT_EQ(Get<0>(result), "h3-28"_ns);
+ ASSERT_EQ(Get<1>(result), true);
+
+ // We don't support h3-26, so we should choose h2.
+ alpn = {"h3-26"_ns, "h2"_ns, "http/1.1"_ns};
+ result = SelectAlpnFromAlpnList(alpn, false, false);
+ ASSERT_EQ(Get<0>(result), "h2"_ns);
+ ASSERT_EQ(Get<1>(result), false);
+
+ // h3 is disabled, so we will use h2.
+ alpn = {"h3-28"_ns, "h3-27"_ns, "h2"_ns, "http/1.1"_ns};
+ result = SelectAlpnFromAlpnList(alpn, false, true);
+ ASSERT_EQ(Get<0>(result), "h2"_ns);
+ ASSERT_EQ(Get<1>(result), false);
+
+ // h3 is disabled and h2 is not found, so we will select http/1.1.
+ alpn = {"h3-28"_ns, "h3-27"_ns, "http/1.1"_ns};
+ result = SelectAlpnFromAlpnList(alpn, false, true);
+ ASSERT_EQ(Get<0>(result), "http/1.1"_ns);
+ ASSERT_EQ(Get<1>(result), false);
+
+ // h3 and h2 are disabled, so we will select http/1.1.
+ alpn = {"h3-28"_ns, "h3-27"_ns, "h2"_ns, "http/1.1"_ns};
+ result = SelectAlpnFromAlpnList(alpn, true, true);
+ ASSERT_EQ(Get<0>(result), "http/1.1"_ns);
+ ASSERT_EQ(Get<1>(result), false);
+
+ // h3 and h2 are disabled and http1.1 is not found, we return an empty string.
+ alpn = {"h3-28"_ns, "h3-27"_ns, "h2"_ns};
+ result = SelectAlpnFromAlpnList(alpn, true, true);
+ ASSERT_EQ(Get<0>(result), ""_ns);
+ ASSERT_EQ(Get<1>(result), false);
+
+ // No supported alpn.
+ alpn = {"ftp"_ns, "h2c"_ns};
+ result = SelectAlpnFromAlpnList(alpn, true, true);
+ ASSERT_EQ(Get<0>(result), ""_ns);
+ ASSERT_EQ(Get<1>(result), false);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/test/gtest/TestUDPSocket.cpp b/netwerk/test/gtest/TestUDPSocket.cpp
new file mode 100644
index 0000000000..84d26fbedf
--- /dev/null
+++ b/netwerk/test/gtest/TestUDPSocket.cpp
@@ -0,0 +1,395 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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/net/DNS.h"
+#include "prerror.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;
+ } else 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;
+ } else 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 {
+ protected:
+ virtual ~MulticastTimerCallback();
+
+ public:
+ explicit MulticastTimerCallback(WaitForCondition* waiter)
+ : mResult(NS_ERROR_NOT_INITIALIZED), mWaiter(waiter) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ nsresult mResult;
+ RefPtr<WaitForCondition> mWaiter;
+};
+
+NS_IMPL_ISUPPORTS(MulticastTimerCallback, nsITimerCallback)
+
+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;
+}
+
+/**** Main ****/
+
+TEST(TestUDPSocket, TestUDPSocketMain)
+{
+ nsresult rv;
+
+ // Create UDPSocket
+ nsCOMPtr<nsIUDPSocket> server, client;
+ server = do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ client = do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
+ ASSERT_TRUE(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_TRUE(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_TRUE(NS_SUCCEEDED(rv));
+ EXPECT_EQ(count, sizeof(uint32_t));
+
+ // Wait for server
+ waiter->Wait(1);
+ ASSERT_TRUE(NS_SUCCEEDED(serverListener->mResult));
+
+ // Read response from server
+ ASSERT_TRUE(NS_SUCCEEDED(clientListener->mResult));
+
+ mozilla::net::NetAddr clientAddr;
+ rv = client->GetAddress(&clientAddr);
+ ASSERT_TRUE(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, &count);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ EXPECT_EQ(count, sizeof(uint32_t));
+
+ // Wait for server
+ waiter->Wait(1);
+ ASSERT_TRUE(NS_SUCCEEDED(serverListener->mResult));
+
+ // Read response from server
+ ASSERT_TRUE(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_TRUE(NS_SUCCEEDED(rv));
+
+ // Send multicast ping
+ timerCb->mResult = NS_OK;
+ timer->InitWithCallback(timerCb, MULTICAST_TIMEOUT, nsITimer::TYPE_ONE_SHOT);
+ rv = client->SendWithAddress(&multicastAddr, data, &count);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ EXPECT_EQ(count, sizeof(uint32_t));
+
+ // Wait for server to receive successfully
+ waiter->Wait(1);
+ ASSERT_TRUE(NS_SUCCEEDED(serverListener->mResult));
+ ASSERT_TRUE(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, &count);
+ ASSERT_TRUE(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, &count);
+ ASSERT_TRUE(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_TRUE(NS_SUCCEEDED(rv));
+
+ // Send multicast ping
+ timerCb->mResult = NS_OK;
+ timer->InitWithCallback(timerCb, MULTICAST_TIMEOUT, nsITimer::TYPE_ONE_SHOT);
+ rv = client->SendWithAddress(&multicastAddr, data, &count);
+ ASSERT_TRUE(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..cd45457a3c
--- /dev/null
+++ b/netwerk/test/gtest/TestURIMutator.cpp
@@ -0,0 +1,128 @@
+#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);
+}
+
+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..5f912c17fe
--- /dev/null
+++ b/netwerk/test/gtest/moz.build
@@ -0,0 +1,84 @@
+# -*- 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",
+ "TestHeaders.cpp",
+ "TestHttpAuthUtils.cpp",
+ "TestHttpResponseHead.cpp",
+ "TestInputStreamTransport.cpp",
+ "TestIsValidIp.cpp",
+ "TestMIMEInputStream.cpp",
+ "TestMozURL.cpp",
+ "TestProtocolProxyService.cpp",
+ "TestReadStreamToString.cpp",
+ "TestServerTimingHeader.cpp",
+ "TestSocketTransportService.cpp",
+ "TestStandardURL.cpp",
+ "TestSupportAlpn.cpp",
+ "TestUDPSocket.cpp",
+]
+
+if CONFIG["OS_TARGET"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "TestNamedPipeService.cpp",
+ ]
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-shadow"]
+
+# skip the test on windows10-aarch64
+if not (CONFIG["OS_TARGET"] == "WINNT" and CONFIG["CPU_ARCH"] == "aarch64"):
+ UNIFIED_SOURCES += [
+ "TestPACMan.cpp",
+ "TestPartiallySeekableInputStream.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",
+]
+
+TEST_DIRS += [
+ "parse-ftp",
+]
+
+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/parse-ftp/3-guess.in b/netwerk/test/gtest/parse-ftp/3-guess.in
new file mode 100644
index 0000000000..b5e8596c1c
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/3-guess.in
@@ -0,0 +1,30 @@
+I couldn't find any SuperTCP or Chameleon/Newt FTP servers, so the
+following is based on guesswork from the mirror.pl and lynx projects.
+The first 8 are Chameleon/Newt, the remaining 16 entries are SuperTCP
+with two different date styles. The precise amount of whitespace
+between columns might not be correct, but the parser shouldn't care.
+
+. <DIR> Nov 16 1994 17:16
+.. <DIR> Nov 16 1994 17:16
+INSTALL <DIR> Nov 16 1994 17:17
+CMT <DIR> Nov 21 1994 10:17
+DESIGN1.DOC 11264 May 11 1995 14:20 A
+README.TXT 1045 May 10 1995 11:01
+WPKIT1.EXE 960338 Jun 21 1995 17:01 R
+CMT.CSV 0 Jul 06 1995 14:56 RHA
+. <DIR> 11-16-94 17:16
+.. <DIR> 11-16-94 17:16
+INSTALL <DIR> 11-16-94 17:17
+CMT <DIR> 11-21-94 10:17
+DESIGN1.DOC 11264 05-11-95 14:20
+README.TXT 1045 05-10-95 11:01
+WPKIT1.EXE 960338 06-21-95 17:01
+CMT.CSV 0 07-06-95 14:56
+. <DIR> 11/16/94 17:16
+.. <DIR> 11/16/94 17:16
+INSTALL <DIR> 11/16/94 17:17
+CMT <DIR> 11/21/94 10:17
+DESIGN1.DOC 11264 05/11/95 14:20
+README.TXT 1045 05/10/95 11:01
+WPKIT1.EXE 960338 06/21/95 17:01
+CMT.CSV 0 07/06/95 14:56
diff --git a/netwerk/test/gtest/parse-ftp/3-guess.out b/netwerk/test/gtest/parse-ftp/3-guess.out
new file mode 100644
index 0000000000..30a1d0fb98
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/3-guess.out
@@ -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/. -->
+
+11-16-1994 17:16:00 <DIR> .
+11-16-1994 17:16:00 <DIR> ..
+11-16-1994 17:17:00 <DIR> INSTALL
+11-21-1994 10:17:00 <DIR> CMT
+05-11-1995 14:20:00 11264 DESIGN1.DOC
+05-10-1995 11:01:00 1045 README.TXT
+06-21-1995 17:01:00 960338 WPKIT1.EXE
+07-06-1995 14:56:00 0 CMT.CSV
+11-16-1994 17:16:00 <DIR> .
+11-16-1994 17:16:00 <DIR> ..
+11-16-1994 17:17:00 <DIR> INSTALL
+11-21-1994 10:17:00 <DIR> CMT
+05-11-1995 14:20:00 11264 DESIGN1.DOC
+05-10-1995 11:01:00 1045 README.TXT
+06-21-1995 17:01:00 960338 WPKIT1.EXE
+07-06-1995 14:56:00 0 CMT.CSV
+11-16-1994 17:16:00 <DIR> .
+11-16-1994 17:16:00 <DIR> ..
+11-16-1994 17:17:00 <DIR> INSTALL
+11-21-1994 10:17:00 <DIR> CMT
+05-11-1995 14:20:00 11264 DESIGN1.DOC
+05-10-1995 11:01:00 1045 README.TXT
+06-21-1995 17:01:00 960338 WPKIT1.EXE
+07-06-1995 14:56:00 0 CMT.CSV
diff --git a/netwerk/test/gtest/parse-ftp/C-VMold.in b/netwerk/test/gtest/parse-ftp/C-VMold.in
new file mode 100644
index 0000000000..5453b496e7
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/C-VMold.in
@@ -0,0 +1,16 @@
+This is a synthetic LISTing based on comments in mirror.pl and a
+screenshot I saw somewhere. Either way, its 'synthetic' because I
+couldn't find an FTP server that LISTed like this. Note that (as
+with other VM/CMS LISTings) filesize cannot be determined from
+the listing and (AFAIK) files/dirs not on the 'A' minidisk (see
+'Fm' field) are not RETRievable/CHDIR'able without magic.
+
+Filename FileType Fm Format Lrecl Records Blocks Date Time
+LASTING GLOBALV A1 V 41 21 1 9/16/91 15:10:32
+J43401 NETLOG A0 V 77 1 1 9/12/91 12:36:04
+PROFILE EXEC A1 V 17 3 1 9/12/91 12:39:07
+DIRUNIX SCRIPT A1 V 77 1216 17 1/04/93 20:30:47
+MAIL PROFILE A2 F 80 1 1 10/14/92 16:12:27
+ABADY2K DATE A0 V 1 1 1 1/03/100 10:11:12
+BBADY2K DATE A0 V 1 1 1 11/03/100 10:11:12
+AUTHORS A1 DIR - - - 9/20/99 10:31:11
diff --git a/netwerk/test/gtest/parse-ftp/C-VMold.out b/netwerk/test/gtest/parse-ftp/C-VMold.out
new file mode 100644
index 0000000000..0c9c016f61
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/C-VMold.out
@@ -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/. -->
+
+09-16-1991 15:10:32 LASTING.GLOBALV
+09-12-1991 12:36:04 J43401.NETLOG
+09-12-1991 12:39:07 PROFILE.EXEC
+01-04-1993 20:30:47 DIRUNIX.SCRIPT
+10-14-1992 16:12:27 MAIL.PROFILE
+01-03-2000 10:11:12 ABADY2K.DATE
+11-03-2000 10:11:12 BBADY2K.DATE
+09-20-1999 10:31:11 <DIR> AUTHORS
diff --git a/netwerk/test/gtest/parse-ftp/C-zVM.in b/netwerk/test/gtest/parse-ftp/C-zVM.in
new file mode 100644
index 0000000000..bd63f631ff
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/C-zVM.in
@@ -0,0 +1,61 @@
+ftp> open vm.marist.edu
+220-Welcome to FTP.Marist.edu
+220-
+220-All usage logged. Usage restricted.
+220-FTPSERVE IBM VM Level 420 at VM.MARIST.EDU, 05:06:00 EDT WEDNESDAY 2002-07-10
+220 Connection will close if idle for more than 5 minutes.
+Name (vm.marist.edu:cyp):
+230-
+230-
+230-Listserv logs for certain lists are available via CD ACADEM:listname.
+230-
+230-Lists that they are available for are:
+230-
+230-ADSM-L ftp://vm.marist.edu/academ:adsm-l./
+230-CLINTON ftp://vm.marist.edu/academ:clinton./
+230-CMSPIP-L ftp://vm.marist.edu/academ:cmspip-l./
+230-CONCORD ftp://vm.marist.edu/academ:concord./
+230-REPUB-L ftp://vm.marist.edu/academ:repub-l./
+230-SAS-L ftp://vm.marist.edu/academ:sas-l./
+230-VM-UTIL ftp://vm.marist.edu/academ:vm-util./
+230-LINUX-VM ftp://vm.marist.edu/academ:linux-vm./
+230-
+230-For example: (Note the trailing .)
+230-
+230-CD ACADEM:ADSM-L.
+230-
+230-Due to limitations of Netscape, you may need to append ";type=a"
+230-to the end of a URL when obtaining a file.
+230-
+230-CMS Pipelines RLD available as
+230-
+230- ftp://vm.marist.edu/academ:pipeline/eweb./
+230-
+230 ANONYMOU logged in; working directory = ACADEM:ANONYMOU.
+Remote system type is z/VM.
+ftp> ls
+200 Port request OK.
+125 List started OK
+README ANONYMOU V 71 26 1 1997-04-02 12:33:20 TCP291
+README ANONYOLD V 71 15 1 1995-08-25 16:04:27 TCP291
+AUTHORS DIR - - - 1999-09-20 10:31:11 -
+HARRINGTON DIR - - - 1997-02-12 15:33:28 -
+PICS DIR - - - 2000-10-12 15:43:23 -
+SYSFILE DIR - - - 2000-07-20 17:48:01 -
+WELCNVT EXEC V 72 9 1 1999-09-20 17:16:18 -
+WELCOME EREADME F 80 21 1 1999-12-27 16:19:00 -
+WELCOME README V 82 21 1 1999-12-27 16:19:04 -
+PROJECT PDF V 984 10 3 1997-06-15 11:15:02 -
+CAMPUS TIF V 8192 3172 3606 2000-10-06 17:20:37 -
+ROTUNDA TIF V 8192 5745 8750 2000-10-06 17:28:25 -
+STUDENT TIF V 8192 5017 8557 2000-10-06 17:31:26 -
+CHILDREN TXT V 280 310 20 1993-08-06 14:20:00 -
+SYSFILE VMARC F 80 2001 40 2000-07-20 17:47:35 -
+250 List completed successfully.
+ftp> syst
+215-z/VM Version 4 Release 2.0, service level 0101 (32-bit)
+ VM/CMS Level 18, Service Level 101
+215 VM is the operating system of this server.
+ftp> close
+221 Quit command received. Goodbye.
+ftp>
diff --git a/netwerk/test/gtest/parse-ftp/C-zVM.out b/netwerk/test/gtest/parse-ftp/C-zVM.out
new file mode 100644
index 0000000000..04511ebc7e
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/C-zVM.out
@@ -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/. -->
+
+04-02-1997 12:33:20 README.ANONYMOU
+08-25-1995 16:04:27 README.ANONYOLD
+09-20-1999 10:31:11 <DIR> AUTHORS
+02-12-1997 15:33:28 <DIR> HARRINGTON
+10-12-2000 15:43:23 <DIR> PICS
+07-20-2000 17:48:01 <DIR> SYSFILE
+09-20-1999 17:16:18 WELCNVT.EXEC
+12-27-1999 16:19:00 WELCOME.EREADME
+12-27-1999 16:19:04 WELCOME.README
+06-15-1997 11:15:02 PROJECT.PDF
+10-06-2000 17:20:37 CAMPUS.TIF
+10-06-2000 17:28:25 ROTUNDA.TIF
+10-06-2000 17:31:26 STUDENT.TIF
+08-06-1993 14:20:00 CHILDREN.TXT
+07-20-2000 17:47:35 SYSFILE.VMARC
diff --git a/netwerk/test/gtest/parse-ftp/D-WinNT.in b/netwerk/test/gtest/parse-ftp/D-WinNT.in
new file mode 100644
index 0000000000..c27f4d8bff
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/D-WinNT.in
@@ -0,0 +1,62 @@
+ftp> open ftp.microsoft.com
+220 Microsoft FTP Service
+Name (ftp.microsoft.com:cyp):
+331 Anonymous access allowed, send identity (e-mail name) as password.
+230-This is FTP.Microsoft.Com
+230 Anonymous user logged in.
+Remote system type is Windows_NT.
+ftp> quote dirstyle
+200 MSDOS-like directory output is on
+ftp> ls
+200 PORT command successful.
+150 Opening ASCII mode data connection for /bin/ls.
+02-05-01 05:52PM <DIR> 20Year
+09-26-00 03:20PM <DIR> AccessFoxPro
+12-21-00 02:39PM <DIR> AlbGrp
+05-31-01 11:16AM <DIR> Alexandra
+01-19-01 09:36AM <DIR> anna
+04-06-00 12:53PM <DIR> anz
+05-10-00 01:37PM <DIR> Chase Bobko2
+03-29-00 02:43PM <DIR> cnn
+11-21-00 11:21AM <DIR> Darin
+03-09-00 05:55PM <DIR> digitalchicago
+09-06-00 11:38AM <DIR> Dublin
+01-30-01 07:15PM <DIR> eleanorf
+04-26-01 11:04AM <DIR> girvin
+04-26-00 09:28PM <DIR> Hires
+08-15-00 05:03PM <DIR> HR
+10-24-99 01:29AM 4368384 IMG00022.PCD
+05-18-01 07:52AM <DIR> jacubowsky
+10-12-00 10:55AM <DIR> JFalvey
+03-28-01 02:38PM <DIR> johnci
+07-14-00 08:59AM <DIR> Karin
+09-07-00 05:48PM <DIR> Kjung
+09-28-00 08:59AM <DIR> LarryE
+08-17-00 06:06PM <DIR> Larson
+09-12-00 05:22PM <DIR> marion
+08-09-00 12:30PM <DIR> ms25
+11-16-00 03:58PM <DIR> MS25Brochure
+03-29-00 12:07PM <DIR> MShistory
+09-05-00 10:35AM <DIR> Neils
+08-02-00 06:25PM <DIR> NLM
+09-06-00 03:04PM <DIR> PageOne
+06-27-00 06:00PM <DIR> pccomputing
+05-09-01 01:27PM <DIR> pictures
+07-21-00 04:23PM <DIR> pranks
+08-22-00 10:36AM <DIR> Sean
+08-10-00 01:54PM <DIR> SLeong
+09-07-00 05:46PM <DIR> svr
+10-23-00 01:27PM <JUNCTION> b_junction_sample => foo
+06-15-00 07:37AM <JUNCTION> c_junction_sample -> bar too
+07-14-00 01:35PM 2094926 canprankdesk.tif
+07-21-00 01:19PM 95077 Jon Kauffman Enjoys the Good Life.jpg
+07-21-00 01:19PM 52275 Name Plate.jpg
+07-14-00 01:38PM 2250540 Valentineoffprank-HiRes.jpg
+01-11-14 12:25AM 18864566 Bug961346
+10-10-2014 10:10AM <DIR> Bug1061898
+01-18-12 19:00 30724571 Bug1125816
+226 Transfer complete.
+ftp> close
+221 Thank-You For Using Microsoft Products!
+ftp>
+
diff --git a/netwerk/test/gtest/parse-ftp/D-WinNT.out b/netwerk/test/gtest/parse-ftp/D-WinNT.out
new file mode 100644
index 0000000000..0a42d4d668
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/D-WinNT.out
@@ -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/. -->
+
+02-05-2001 17:52:00 <DIR> 20Year
+09-26-2000 15:20:00 <DIR> AccessFoxPro
+12-21-2000 14:39:00 <DIR> AlbGrp
+05-31-2001 11:16:00 <DIR> Alexandra
+01-19-2001 09:36:00 <DIR> anna
+04-06-2000 12:53:00 <DIR> anz
+05-10-2000 13:37:00 <DIR> Chase Bobko2
+03-29-2000 14:43:00 <DIR> cnn
+11-21-2000 11:21:00 <DIR> Darin
+03-09-2000 17:55:00 <DIR> digitalchicago
+09-06-2000 11:38:00 <DIR> Dublin
+01-30-2001 19:15:00 <DIR> eleanorf
+04-26-2001 11:04:00 <DIR> girvin
+04-26-2000 21:28:00 <DIR> Hires
+08-15-2000 17:03:00 <DIR> HR
+10-24-1999 01:29:00 4368384 IMG00022.PCD
+05-18-2001 07:52:00 <DIR> jacubowsky
+10-12-2000 10:55:00 <DIR> JFalvey
+03-28-2001 14:38:00 <DIR> johnci
+07-14-2000 08:59:00 <DIR> Karin
+09-07-2000 17:48:00 <DIR> Kjung
+09-28-2000 08:59:00 <DIR> LarryE
+08-17-2000 18:06:00 <DIR> Larson
+09-12-2000 17:22:00 <DIR> marion
+08-09-2000 12:30:00 <DIR> ms25
+11-16-2000 15:58:00 <DIR> MS25Brochure
+03-29-2000 12:07:00 <DIR> MShistory
+09-05-2000 10:35:00 <DIR> Neils
+08-02-2000 18:25:00 <DIR> NLM
+09-06-2000 15:04:00 <DIR> PageOne
+06-27-2000 18:00:00 <DIR> pccomputing
+05-09-2001 13:27:00 <DIR> pictures
+07-21-2000 16:23:00 <DIR> pranks
+08-22-2000 10:36:00 <DIR> Sean
+08-10-2000 13:54:00 <DIR> SLeong
+09-07-2000 17:46:00 <DIR> svr
+10-23-2000 13:27:00 <JUNCTION> b_junction_sample -> foo
+06-15-2000 07:37:00 <JUNCTION> c_junction_sample -> bar too
+07-14-2000 13:35:00 2094926 canprankdesk.tif
+07-21-2000 13:19:00 95077 Jon Kauffman Enjoys the Good Life.jpg
+07-21-2000 13:19:00 52275 Name Plate.jpg
+07-14-2000 13:38:00 2250540 Valentineoffprank-HiRes.jpg
+01-11-2014 00:25:00 18864566 Bug961346
+10-10-2014 10:10:00 <DIR> Bug1061898
+01-18-2012 19:00:00 30724571 Bug1125816
diff --git a/netwerk/test/gtest/parse-ftp/E-EPLF.in b/netwerk/test/gtest/parse-ftp/E-EPLF.in
new file mode 100644
index 0000000000..3798ea0a9d
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/E-EPLF.in
@@ -0,0 +1,189 @@
+ftp> open cr.yp.to
+Connected to cr.yp.to.
+220 Features: a p .
+Name (cr.yp.to:cyp):
+230 Hi. No need to log in; I'm an anonymous ftp server.
+Remote system type is UNIX.
+Using binary mode to transfer files.
+ftp> ls
+200 Okay.
+150 Making transfer connection...
++i0.307427,m979205905,/, tmp
++i0.631874,m951789705,/, 1998-100
++i0.236492,m977690646,r,s1172, mirrors.html
++i0.718174,m951789707,/, 1999-275
++i0.236496,m957763516,r,s933, 1999-275.html
++i0.236247,m957763626,r,s322, 1998-401.html
++i0.689416,m951789707,/, 1998-401
++i0.714380,m951789705,/, 1997-275
++i0.236426,m957763705,r,s277, 1998-515.html
++i0.236324,m957764203,r,s325, 2000-436.html
++i0.553153,m977554031,/, checkpwd
++i0.73117,m1025804866,/, bib
++i0.2012,m951789707,/, 1999-541
++i0.134494,m988426955,/, 2001-260
++i0.457327,m1019068075,/, 2002-501
++i0.11567,m991428977,/, 1995-514
++i0.716165,m951789705,/, 1999-180
++i0.236327,m991363463,r,s475, 2000-515.html
++i0.687522,m951789707,/, 1998-515
++i0.412995,m951789707,/, 2000-515
++i0.213334,m958117573,/, zpfft
++i0.295901,m1004187614,/, psibound
++i0.236429,m951789711,r,s398, anonftpd.html
++i0.326793,m1014690862,/, nistp224
++i0.587605,m997428440,/, cdb
++i0.516884,m1020211107,/, talks
++i0.255382,m977607275,/, ftpparse
++i0.103809,m947569810,/, clockspeed
++i0.236433,m951789712,r,s1350, clockspeed.html
++i0.530322,m1013028754,/, slashdoc
++i0.10002,m1023465640,/, export
++i0.105740,m995334516,/, daemontools
++i0.236425,m994972628,r,s2187, daemontools.html
++i0.236437,m999204676,r,s175, data.html
++i0.236435,m1009503157,r,s1901, crypto.html
++i0.236430,m1005435923,r,s739, arith.html
++i0.236297,m1007092089,r,s2733, cdb.html
++i0.236475,m1005436262,r,s344, floatasm.html
++i0.126821,m977433956,/, djbfft
++i0.236440,m951789712,r,s1319, djbfft.html
++i0.149876,m963163007,/, dnscache
++i0.236441,m962665064,r,s4016, dnscache.html
++i0.674064,m994734982,/, docs
++i0.236442,m951789712,r,s293, docs.html
++i0.236443,m951789712,r,s952, dot-forward.html
++i0.76895,m947569811,/, etc-mta
++i0.236444,m951789712,r,s3174, etc-mta.html
++i0.236445,m951789712,r,s2990, ezmlm.html
++i0.142082,m977433956,/, ftp
++i0.78826,m951789709,/, im
++i0.236446,m951789712,r,s1823, fastforward.html
++i0.236447,m951789712,r,s1707, ftp.html
++i0.21212,m981944230,/, freebugtraq
++i0.117314,m1022981965,/, hardware
++i0.236326,m991364255,r,s610, 1995-514.html
++i0.63463,m977433957,/, hash127
++i0.236450,m958703342,r,s4835, hash127.html
++i0.236451,m951789712,r,s1515, im.html
++i0.82651,m1013546406,/, immhf
++i0.236299,m988235329,r,s2902, immhf.html
++i0.147915,m980402316,/, lib
++i0.591514,m991784750,/, libtai
++i0.236454,m963255256,r,s2120, libtai.html
++i0.236427,m1006883560,r,s14460, qmail.html
++i0.236452,m999204683,r,s2153, mail.html
++i0.236457,m951789712,r,s108, maildir.html
++i0.71192,m951789710,/, maildisasters
++i0.236458,m951789712,r,s873, maildisasters.html
++i0.236459,m951789712,r,s1688, mess822.html
++i0.13517,m1014605247,/, mirror
++i0.236431,m1009400552,r,s581, ntheory.html
++i0.629891,m1023118587,/, papers
++i0.176720,m1021999815,/, postpropter
++i0.67332,m947569812,/, postings
++i0.101902,m947569812,/, primegen
++i0.236453,m999204693,r,s415, precompiled.html
++i0.236463,m951789712,r,s1121, primegen.html
++i0.574160,m1020562675,/, proto
++i0.236464,m951789712,r,s452, proto.html
++i0.113471,m994717073,/, publicfile
++i0.236295,m1007092757,r,s5651, publicfile.html
++i0.236455,m999204697,r,s409, qlist.html
++i0.15552,m1017725145,/, qmail
++i0.236292,m1012629148,r,s7549, lists.html
++i0.236468,m951789712,r,s1166, qmailanalog.html
++i0.236456,m999204699,r,s1434, qmsmac.html
++i0.236470,m952925886,r,s465, rblsmtpd.html
++i0.236471,m951789712,r,s1225, rights.html
++i0.691333,m947569813,/, sarcasm
++i0.236465,m1000434854,r,s1058, unix.html
++i0.236472,m951789713,r,s1568, serialmail.html
++i0.236473,m970514024,r,s840, sigs.html
++i0.80743,m1021999808,/, smtp
++i0.236300,m988300727,r,s1723, smtp.html
++i0.693264,m977433963,/, software
++i0.236476,m951789713,r,s3671, softwarelaw.html
++i0.710487,m947569813,/, sortedsums
++i0.236477,m951789713,r,s2735, sortedsums.html
++i0.23149,m1014689198,/, speed
++i0.236478,m962664017,r,s572, surveydns.html
++i0.589555,m1002157171,/, surveys
++i0.236278,m1002156396,r,s3503, surveys.html
++i0.96135,m973835888,/, syncookies
++i0.236479,m1012529017,r,s7918, syncookies.html
++i0.236460,m999204703,r,s1115, tcpcontrol.html
++i0.236461,m1016453307,r,s589, tcpip.html
++i0.236483,m951789713,r,s906, thoughts.html
++i0.136514,m996502689,/, threecubes
++i0.236462,m1000430758,r,s525, time.html
++i0.121107,m1022041265,/, ucspi-tcp
++i0.236484,m1011915691,r,s4086, ucspi-tcp.html
++i0.236285,m999204710,r,s732, web.html
++i0.236323,m1025804868,/, www
++i0.236467,m1000430768,r,s2717, y2k.html
++i0.94216,m981624489,/, dnsroot
++i0.236280,m988005697,r,s2745, 2001-260.html
++i0.236489,m951789713,r,s291, zeroseek.html
++i0.90373,m951789713,/, zmodexp
++i0.236490,m958703347,r,s1513, zmodexp.html
++i0.65452,m965092134,/, zmult
++i0.236491,m965093093,r,s669, zmult.html
++i0.236432,m977552387,r,s1097, checkpwd.html
++i0.236480,m1009824436,r,s2077, focus.html
++i0.236493,m964048291,r,s3842, im2000.html
++i0.5981,m1025804863,/, slashcommand
++i0.192219,m1025804864,/, slashpackage
++i0.236277,m995235879,r,s602, slashpackage.html
++i0.236302,m1020384933,r,s23874, talks.html
++i0.236495,m957763499,r,s566, 1997-275.html
++i0.236494,m957763864,r,s313, 1998-100.html
++i0.543361,m951789707,/, 2000-436
++i0.236424,m957764246,r,s660, 1997-494.html
++i0.236428,m957764441,r,s730, 1999-541.html
++i0.236497,m968828938,r,s2367, psibound.html
++i0.236500,m961639740,r,s1866, smallfactors.html
++i0.413016,m960514519,/, smallfactors
++i0.75096,m962492119,/, conferences
++i0.557013,m1022046749,/, djbdns
++i0.236282,m995342458,r,s2712, djbdns.html
++i0.236502,m963796539,r,s216, zpfft.html
++i0.537820,m966797797,/, fastnewton
++i0.236503,m966797851,r,s333, fastnewton.html
++i0.510806,m967833231,/, patents
++i0.236448,m977607617,r,s1748, ftpparse.html
++i0.236281,m1005436670,r,s3261, slash.html
++i0.236279,m979753810,r,s2663, slashdoc.html
++i0.236283,m1016442275,r,s2344, dnsroot.html
++i0.236284,m1009400580,r,s3238, software.html
++i0.236287,m1000434873,r,s3481, compatibility.html
++i0.236291,m1020223834,r,s5525, distributors.html
++i0.236293,m983072943,r,s212, freebugtraq.html
++i0.236294,m1017547190,r,s806, hardware.html
++i0.236296,m986022918,r,s121, securesoftware.html
++i0.236301,m1022035196,r,s10950, conferences.html
++i0.378623,m1007142710,/, 2001-275
++i0.236303,m991095307,r,s459, rwb100.html
++i0.236325,m1002087642,r,s863, nistp224.html
++i0.236436,m996502546,r,s778, threecubes.html
++i0.236298,m1023118336,r,s27125, papers.html
++i0.236439,m1006311069,r,s379, bib.html
++i0.236438,m1007142093,r,s3791, 2001-275.html
++i0.236474,m1002046139,r,s169, dh224.html
++i0.236434,m1016258736,r,s1126, courses.html
++i0.236466,m999469348,r,s355, slashcommand.html
++i0.236449,m1017264022,r,s1199, patents.html
++i0.236469,m1005247635,r,s640, mailcopyright.html
++i0.236286,m1022974070,r,s1257, djb.html
++i0.236481,m1010383954,r,s301, 2002-501.html
++i0.236482,m1020220905,r,s1655, export.html
++i0.236276,m1016966459,r,s6446, index.html
++i0.236485,m1023740653, donations.html
++i0.236486,m1016258948,r,s570, 2005-590.html
++i0.236487,m1016258749,r,s800, 2004-494.html
++i0.236488,m1023740407, thanks.html
++i0.236498,m1022974139,r,s4459, positions.html
++i0.236499,m1022972131,r,s466, contact.html
+226 Success.
+ftp> close
+ftp> \ No newline at end of file
diff --git a/netwerk/test/gtest/parse-ftp/E-EPLF.out b/netwerk/test/gtest/parse-ftp/E-EPLF.out
new file mode 100644
index 0000000000..3cb6ae85b1
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/E-EPLF.out
@@ -0,0 +1,178 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+01-11-2001 09:38:25 <DIR> tmp
+02-29-2000 02:01:45 <DIR> 1998-100
+12-24-2000 20:44:06 1172 mirrors.html
+02-29-2000 02:01:47 <DIR> 1999-275
+05-08-2000 05:25:16 933 1999-275.html
+05-08-2000 05:27:06 322 1998-401.html
+02-29-2000 02:01:47 <DIR> 1998-401
+02-29-2000 02:01:45 <DIR> 1997-275
+05-08-2000 05:28:25 277 1998-515.html
+05-08-2000 05:36:43 325 2000-436.html
+12-23-2000 06:47:11 <DIR> checkpwd
+07-04-2002 17:47:46 <DIR> bib
+02-29-2000 02:01:47 <DIR> 1999-541
+04-28-2001 03:02:35 <DIR> 2001-260
+04-17-2002 18:27:55 <DIR> 2002-501
+06-01-2001 20:56:17 <DIR> 1995-514
+02-29-2000 02:01:45 <DIR> 1999-180
+06-01-2001 02:44:23 475 2000-515.html
+02-29-2000 02:01:47 <DIR> 1998-515
+02-29-2000 02:01:47 <DIR> 2000-515
+05-12-2000 07:46:13 <DIR> zpfft
+10-27-2001 13:00:14 <DIR> psibound
+02-29-2000 02:01:51 398 anonftpd.html
+02-26-2002 02:34:22 <DIR> nistp224
+08-10-2001 07:27:20 <DIR> cdb
+04-30-2002 23:58:27 <DIR> talks
+12-23-2000 21:34:35 <DIR> ftpparse
+01-11-2000 05:50:10 <DIR> clockspeed
+02-29-2000 02:01:52 1350 clockspeed.html
+02-06-2002 20:52:34 <DIR> slashdoc
+06-07-2002 16:00:40 <DIR> export
+07-17-2001 01:48:36 <DIR> daemontools
+07-12-2001 21:17:08 2187 daemontools.html
+08-30-2001 20:51:16 175 data.html
+12-28-2001 01:32:37 1901 crypto.html
+11-10-2001 23:45:23 739 arith.html
+11-30-2001 03:48:09 2733 cdb.html
+11-10-2001 23:51:02 344 floatasm.html
+12-21-2000 21:25:56 <DIR> djbfft
+02-29-2000 02:01:52 1319 djbfft.html
+07-09-2000 17:16:47 <DIR> dnscache
+07-03-2000 22:57:44 4016 dnscache.html
+07-10-2001 03:16:22 <DIR> docs
+02-29-2000 02:01:52 293 docs.html
+02-29-2000 02:01:52 952 dot-forward.html
+01-11-2000 05:50:11 <DIR> etc-mta
+02-29-2000 02:01:52 3174 etc-mta.html
+02-29-2000 02:01:52 2990 ezmlm.html
+12-21-2000 21:25:56 <DIR> ftp
+02-29-2000 02:01:49 <DIR> im
+02-29-2000 02:01:52 1823 fastforward.html
+02-29-2000 02:01:52 1707 ftp.html
+02-12-2001 02:17:10 <DIR> freebugtraq
+06-02-2002 01:39:25 <DIR> hardware
+06-01-2001 02:57:35 610 1995-514.html
+12-21-2000 21:25:57 <DIR> hash127
+05-19-2000 02:29:02 4835 hash127.html
+02-29-2000 02:01:52 1515 im.html
+02-12-2002 20:40:06 <DIR> immhf
+04-25-2001 21:48:49 2902 immhf.html
+01-25-2001 05:58:36 <DIR> lib
+06-05-2001 23:45:50 <DIR> libtai
+07-10-2000 18:54:16 2120 libtai.html
+11-27-2001 17:52:40 14460 qmail.html
+08-30-2001 20:51:23 2153 mail.html
+02-29-2000 02:01:52 108 maildir.html
+02-29-2000 02:01:50 <DIR> maildisasters
+02-29-2000 02:01:52 873 maildisasters.html
+02-29-2000 02:01:52 1688 mess822.html
+02-25-2002 02:47:27 <DIR> mirror
+12-26-2001 21:02:32 581 ntheory.html
+06-03-2002 15:36:27 <DIR> papers
+05-21-2002 16:50:15 <DIR> postpropter
+01-11-2000 05:50:12 <DIR> postings
+01-11-2000 05:50:12 <DIR> primegen
+08-30-2001 20:51:33 415 precompiled.html
+02-29-2000 02:01:52 1121 primegen.html
+05-05-2002 01:37:55 <DIR> proto
+02-29-2000 02:01:52 452 proto.html
+07-09-2001 22:17:53 <DIR> publicfile
+11-30-2001 03:59:17 5651 publicfile.html
+08-30-2001 20:51:37 409 qlist.html
+04-02-2002 05:25:45 <DIR> qmail
+02-02-2002 05:52:28 7549 lists.html
+02-29-2000 02:01:52 1166 qmailanalog.html
+08-30-2001 20:51:39 1434 qmsmac.html
+03-13-2000 05:38:06 465 rblsmtpd.html
+02-29-2000 02:01:52 1225 rights.html
+01-11-2000 05:50:13 <DIR> sarcasm
+09-14-2001 02:34:14 1058 unix.html
+02-29-2000 02:01:53 1568 serialmail.html
+10-02-2000 19:13:44 840 sigs.html
+05-21-2002 16:50:08 <DIR> smtp
+04-26-2001 15:58:47 1723 smtp.html
+12-21-2000 21:26:03 <DIR> software
+02-29-2000 02:01:53 3671 softwarelaw.html
+01-11-2000 05:50:13 <DIR> sortedsums
+02-29-2000 02:01:53 2735 sortedsums.html
+02-26-2002 02:06:38 <DIR> speed
+07-03-2000 22:40:17 572 surveydns.html
+10-04-2001 00:59:31 <DIR> surveys
+10-04-2001 00:46:36 3503 surveys.html
+11-10-2000 05:58:08 <DIR> syncookies
+02-01-2002 02:03:37 7918 syncookies.html
+08-30-2001 20:51:43 1115 tcpcontrol.html
+03-18-2002 12:08:27 589 tcpip.html
+02-29-2000 02:01:53 906 thoughts.html
+07-30-2001 14:18:09 <DIR> threecubes
+09-14-2001 01:25:58 525 time.html
+05-22-2002 04:21:05 <DIR> ucspi-tcp
+01-24-2002 23:41:31 4086 ucspi-tcp.html
+08-30-2001 20:51:50 732 web.html
+07-04-2002 17:47:48 <DIR> www
+09-14-2001 01:26:08 2717 y2k.html
+02-08-2001 09:28:09 <DIR> dnsroot
+04-23-2001 06:01:37 2745 2001-260.html
+02-29-2000 02:01:53 291 zeroseek.html
+02-29-2000 02:01:53 <DIR> zmodexp
+05-19-2000 02:29:07 1513 zmodexp.html
+08-01-2000 01:08:54 <DIR> zmult
+08-01-2000 01:24:53 669 zmult.html
+12-23-2000 06:19:47 1097 checkpwd.html
+12-31-2001 18:47:16 2077 focus.html
+07-19-2000 23:11:31 3842 im2000.html
+07-04-2002 17:47:43 <DIR> slashcommand
+07-04-2002 17:47:44 <DIR> slashpackage
+07-15-2001 22:24:39 602 slashpackage.html
+05-03-2002 00:15:33 23874 talks.html
+05-08-2000 05:24:59 566 1997-275.html
+05-08-2000 05:31:04 313 1998-100.html
+02-29-2000 02:01:47 <DIR> 2000-436
+05-08-2000 05:37:26 660 1997-494.html
+05-08-2000 05:40:41 730 1999-541.html
+09-13-2000 07:08:58 2367 psibound.html
+06-22-2000 02:09:00 1866 smallfactors.html
+06-09-2000 01:35:19 <DIR> smallfactors
+07-01-2000 22:55:19 <DIR> conferences
+05-22-2002 05:52:29 <DIR> djbdns
+07-17-2001 04:00:58 2712 djbdns.html
+07-17-2000 01:15:39 216 zpfft.html
+08-20-2000 18:56:37 <DIR> fastnewton
+08-20-2000 18:57:31 333 fastnewton.html
+09-01-2000 18:33:51 <DIR> patents
+12-23-2000 21:40:17 1748 ftpparse.html
+11-10-2001 23:57:50 3261 slash.html
+01-17-2001 17:50:10 2663 slashdoc.html
+03-18-2002 09:04:35 2344 dnsroot.html
+12-26-2001 21:03:00 3238 software.html
+09-14-2001 02:34:33 3481 compatibility.html
+05-01-2002 03:30:34 5525 distributors.html
+02-25-2001 03:49:03 212 freebugtraq.html
+03-31-2002 03:59:50 806 hardware.html
+03-31-2001 07:15:18 121 securesoftware.html
+05-22-2002 02:39:56 10950 conferences.html
+11-30-2001 17:51:50 <DIR> 2001-275
+05-29-2001 00:15:07 459 rwb100.html
+10-03-2001 05:40:42 863 nistp224.html
+07-30-2001 14:15:46 778 threecubes.html
+06-03-2002 15:32:16 27125 papers.html
+11-21-2001 02:51:09 379 bib.html
+11-30-2001 17:41:33 3791 2001-275.html
+10-02-2001 18:08:59 169 dh224.html
+03-16-2002 06:05:36 1126 courses.html
+09-02-2001 22:22:28 355 slashcommand.html
+03-27-2002 21:20:22 1199 patents.html
+11-08-2001 19:27:15 640 mailcopyright.html
+06-01-2002 23:27:50 1257 djb.html
+01-07-2002 06:12:34 301 2002-501.html
+05-01-2002 02:41:45 1655 export.html
+03-24-2002 10:40:59 6446 index.html
+03-16-2002 06:09:08 570 2005-590.html
+03-16-2002 06:05:49 800 2004-494.html
+06-01-2002 23:28:59 4459 positions.html
+06-01-2002 22:55:31 466 contact.html
diff --git a/netwerk/test/gtest/parse-ftp/O-guess.in b/netwerk/test/gtest/parse-ftp/O-guess.in
new file mode 100644
index 0000000000..4477a9d763
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/O-guess.in
@@ -0,0 +1,19 @@
+220 server IBM TCP/IP for OS/2 - FTP Server ver 23:04:36 on Jan 15 1997
+
+This listing is just a guess because I couldn't find an FTP server that
+LISTs like this. This guess is based on comments in Perl mirror project.
+I'm particularly unsure of the contents of cols 19-34.
+
+ 1 2 3 4 5 6
+0123456789012345678901234567890123456789012345678901234567890123456789
+----- size -------| ?????????????? MM-DD-YY| HH:MM| nnnnnnnnn....
+
+ 0 DIR 04-11-95 16:26 .
+ 0 DIR 04-11-95 16:26 ..
+ 0 DIR 04-11-95 16:26 ADDRESS
+ 612 A 07-28-95 16:45 air_tra1.bag
+ 195 A 08-09-95 10:23 Alfa1.bag
+ 0 DIR 04-11-95 16:26 ATTACH
+ 372 A 08-09-95 10:26 Aussie_1.bag
+ 310992 06-28-94 09:56 INSTALL.EXE
+ 12345 H 10-19-00 15:10 ADOE ADIR MOZILLA.FAKEOUT
diff --git a/netwerk/test/gtest/parse-ftp/O-guess.out b/netwerk/test/gtest/parse-ftp/O-guess.out
new file mode 100644
index 0000000000..402f77b5bf
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/O-guess.out
@@ -0,0 +1,13 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+04-11-1995 16:26:00 <DIR> .
+04-11-1995 16:26:00 <DIR> ..
+04-11-1995 16:26:00 <DIR> ADDRESS
+07-28-1995 16:45:00 612 air_tra1.bag
+08-09-1995 10:23:00 195 Alfa1.bag
+04-11-1995 16:26:00 <DIR> ATTACH
+08-09-1995 10:26:00 372 Aussie_1.bag
+06-28-1994 09:56:00 310992 INSTALL.EXE
+10-19-2000 15:10:00 12345 ADOE ADIR MOZILLA.FAKEOUT
diff --git a/netwerk/test/gtest/parse-ftp/R-dls.in b/netwerk/test/gtest/parse-ftp/R-dls.in
new file mode 100644
index 0000000000..b472a00bce
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/R-dls.in
@@ -0,0 +1,23 @@
+README 763 Information about this server
+bin/ -
+etc/ =
+ls-lR 0
+ls-lR.Z 3
+pub/ = Public area
+usr/ -
+morgan 14 -> ../real/morgan
+TIMIT.mostlikely.Z
+ 79215
+README 763 Feb 3 18:21 Information about this server
+bin/ - Apr 28 1994
+etc/ = 11 Jul 21:04
+ls-lR 0 6 Aug 17:14
+ls-lR.Z 3 05 Sep 1994
+pub/ = Jul 11 21:04 Public area
+usr/ - Sep 7 09:39
+morgan 14 Apr 18 9:39 -> ../real/morgan
+TIMIT.mostlikely.Z
+ 79215 Jan 4 6:45
+
+*** CAUTION while editing this sample LISTing -
+*** Lines contain trailing whitespace and/or '\r's
diff --git a/netwerk/test/gtest/parse-ftp/R-dls.out b/netwerk/test/gtest/parse-ftp/R-dls.out
new file mode 100644
index 0000000000..414f0320c5
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/R-dls.out
@@ -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/. -->
+
+00-00-0000 00:00:00 763 README
+00-00-0000 00:00:00 <DIR> bin
+00-00-0000 00:00:00 <DIR> etc
+00-00-0000 00:00:00 0 ls-lR
+00-00-0000 00:00:00 3 ls-lR.Z
+00-00-0000 00:00:00 <DIR> pub
+00-00-0000 00:00:00 <DIR> usr
+00-00-0000 00:00:00 <JUNCTION> morgan -> ../real/morgan
+00-00-0000 00:00:00 79215 TIMIT.mostlikely.Z
+02-03-2002 18:21:00 763 README
+04-28-1994 00:00:00 <DIR> bin
+07-11-2002 21:04:00 <DIR> etc
+08-06-2001 17:14:00 0 ls-lR
+09-05-1994 00:00:00 3 ls-lR.Z
+07-11-2002 21:04:00 <DIR> pub
+09-07-2001 09:39:00 <DIR> usr
+04-18-2002 09:39:00 <JUNCTION> morgan -> ../real/morgan
+01-04-2002 06:45:00 79215 TIMIT.mostlikely.Z
diff --git a/netwerk/test/gtest/parse-ftp/README b/netwerk/test/gtest/parse-ftp/README
new file mode 100644
index 0000000000..c49e28c700
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/README
@@ -0,0 +1,28 @@
+Testcases for the following:
+
+- VMS (MultiNet, UCX, and CMU) LIST format (including multi-line format)
+- IBM VM/CMS, VM/ESA LIST format (two known variants)
+- Windows NT's default "DOS-dirstyle"
+- OS/2 basic server format LIST format
+- SuperTCP FTP Server
+- NetManage Chameleon (NEWT)
+- EPLF (Easily Parsable List Format)
+- '/bin/dls' (two known variants, plus multi-line) LIST format
+- '/bin/ls -l' and all variants (even if they are not SYST UNIX)
+ including
+ - Hellsoft FTP for NetWare (non-unix perm-bits)
+ - Hethmon Brothers FTP for OS/2 (all '-' perm bits)
+ - NetPresenz (SYST is "MACOS")
+ - "NETWARE" (Hellsoft-style perms, no linkcount, no UID/GID)
+ - OpenBSD FTPD (numeric UID/GID)
+ - Open Group's FTP servers (no GID)
+ - Novonyx [Netscape/Novell] (fields not in columns)
+ - wuFTPd and other BSD-based ftpd that exec "ls -l"
+ - Windows NT server (internal "ls -l" compatible)
+ - Netmanage ProFTPD for Win32 (internal "ls -l" compatible)
+ - SurgeFTPd for Win32 (internal "ls -l" compatible)
+ - WarFTPd for Win32 (internal "ls -l" compatible)
+ - WebStarFTP for MacOS (internal "ls -l" compatible)
+ - MurkWorks FTP for NetWare (internal "ls -l" compatible)
+ - NcFTPd for Unix (internal "ls -l" compatible).
+
diff --git a/netwerk/test/gtest/parse-ftp/TestParseFTPList.cpp b/netwerk/test/gtest/parse-ftp/TestParseFTPList.cpp
new file mode 100644
index 0000000000..1f8f529b90
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/TestParseFTPList.cpp
@@ -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/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/ArrayUtils.h"
+#include "nsPrintfCString.h"
+#include <stdio.h>
+
+#include "ParseFTPList.h"
+
+PRTime gTestTime = 0;
+
+// Pretend this is the current time for the purpose of running the
+// test. The day and year matter because they are used to figure out a
+// default value when no year is specified. Tests will fail if this is
+// changed too much.
+const char* kDefaultTestTime = "01-Aug-2002 00:00:00 GMT";
+
+static PRTime TestTime() { return gTestTime; }
+
+static void ParseFTPLine(char* inputLine, FILE* resultFile, list_state* state) {
+ struct list_result result;
+ int rc = ParseFTPList(inputLine, state, &result, PR_GMTParameters, TestTime);
+
+ if (rc == '?' || rc == '"') {
+ // Ignore junk and comments.
+ return;
+ }
+
+ if (!resultFile) {
+ // No result file was passed in, so there's nothing to check.
+ return;
+ }
+
+ char resultLine[512];
+ ASSERT_NE(fgets(resultLine, sizeof(resultLine), resultFile), nullptr);
+
+ nsPrintfCString parsed(
+ "%02u-%02u-%04u %02u:%02u:%02u %20s %.*s%s%.*s\n",
+ (result.fe_time.tm_mday ? (result.fe_time.tm_month + 1) : 0),
+ result.fe_time.tm_mday,
+ (result.fe_time.tm_mday ? result.fe_time.tm_year : 0),
+ result.fe_time.tm_hour, result.fe_time.tm_min, result.fe_time.tm_sec,
+ (rc == 'd' ? "<DIR> "
+ : (rc == 'l' ? "<JUNCTION> " : result.fe_size)),
+ (int)result.fe_fnlen, result.fe_fname,
+ ((rc == 'l' && result.fe_lnlen) ? " -> " : ""),
+ (int)((rc == 'l' && result.fe_lnlen) ? result.fe_lnlen : 0),
+ ((rc == 'l' && result.fe_lnlen) ? result.fe_lname : ""));
+
+ ASSERT_STREQ(parsed.get(), resultLine);
+}
+
+FILE* OpenResultFile(const char* resultFileName) {
+ if (!resultFileName) {
+ return nullptr;
+ }
+
+ FILE* resultFile = fopen(resultFileName, "r");
+ EXPECT_NE(resultFile, nullptr);
+
+ // Ignore anything in the expected result file before and including the first
+ // blank line.
+ char resultLine[512];
+ while (fgets(resultLine, sizeof(resultLine), resultFile)) {
+ size_t lineLen = strlen(resultLine);
+ EXPECT_LT(lineLen, sizeof(resultLine) - 1);
+ if (lineLen > 0 && resultLine[lineLen - 1] == '\n') {
+ lineLen--;
+ }
+ if (lineLen == 0) {
+ break;
+ }
+ }
+
+ // There must be a blank line somewhere in the result file.
+ EXPECT_EQ(strcmp(resultLine, "\n"), 0);
+
+ return resultFile;
+}
+
+void ParseFTPFile(const char* inputFileName, const char* resultFileName) {
+ printf("Checking %s\n", inputFileName);
+ FILE* inFile = fopen(inputFileName, "r");
+ ASSERT_NE(inFile, nullptr);
+
+ FILE* resultFile = OpenResultFile(resultFileName);
+
+ char inputLine[512];
+ struct list_state state;
+ memset(&state, 0, sizeof(state));
+ while (fgets(inputLine, sizeof(inputLine), inFile)) {
+ size_t lineLen = strlen(inputLine);
+ EXPECT_LT(lineLen, sizeof(inputLine) - 1);
+ if (lineLen > 0 && inputLine[lineLen - 1] == '\n') {
+ lineLen--;
+ }
+
+ ParseFTPLine(inputLine, resultFile, &state);
+ }
+
+ // Make sure there are no extra lines in the result file.
+ if (resultFile) {
+ char resultLine[512];
+ EXPECT_EQ(fgets(resultLine, sizeof(resultLine), resultFile), nullptr)
+ << "There should not be more lines in the expected results file than "
+ "in the parser output.";
+ fclose(resultFile);
+ }
+
+ fclose(inFile);
+}
+
+static const char* testFiles[] = {
+ "3-guess", "C-VMold", "C-zVM", "D-WinNT", "E-EPLF",
+ "O-guess", "R-dls", "U-HellSoft", "U-hethmon", "U-murksw",
+ "U-ncFTPd", "U-NetPresenz", "U-NetWare", "U-nogid", "U-no_ug",
+ "U-Novonyx", "U-proftpd", "U-Surge", "U-WarFTPd", "U-WebStar",
+ "U-WinNT", "U-wu", "V-MultiNet", "V-VMS-mix",
+};
+
+TEST(ParseFTPTest, Check)
+{
+ PRStatus result = PR_ParseTimeString(kDefaultTestTime, true, &gTestTime);
+ ASSERT_EQ(PR_SUCCESS, result);
+
+ char inputFileName[200];
+ char resultFileName[200];
+ for (size_t test = 0; test < mozilla::ArrayLength(testFiles); ++test) {
+ snprintf(inputFileName, mozilla::ArrayLength(inputFileName), "%s.in",
+ testFiles[test]);
+ snprintf(resultFileName, mozilla::ArrayLength(inputFileName), "%s.out",
+ testFiles[test]);
+ ParseFTPFile(inputFileName, resultFileName);
+ }
+}
diff --git a/netwerk/test/gtest/parse-ftp/U-HellSoft.in b/netwerk/test/gtest/parse-ftp/U-HellSoft.in
new file mode 100644
index 0000000000..50f03d664a
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-HellSoft.in
@@ -0,0 +1,25 @@
+ftp> open pandora.anglistik.uni-mainz.de
+220 pandora FTP Server for NW 3.1x, 4.xx (v1.10), (c) 1994 HellSoft.
+Name (pandora.anglistik.uni-mainz.de:cyp):
+331 Password required.
+230-User Logged.
+230 Home Directory: /SYS/USER/FTP
+ftp> ls
+200 Command Accepted.
+150 Opening ASCII data connection.
+d[RWCEMFA] 1 cyp 512 Jul 01 11:49 htdocs
+d[RWCEMFA] 1 cyp 512 Jul 02 08:56 config
+d[RWCEMFA] 1 cyp 512 Jun 20 11:23 other
+d[RWCEMFA] 1 cyp 512 Jun 20 11:23 mail
+d[RWCEMFA] 1 cyp 512 Jun 20 14:35 news
+d[RWCEMFA] 1 cyp 512 Jun 30 08:48 download
+-[RWCEMFA] 1 *unknown 13312 May 27 1997 19calit.doc
+-[RWCEMFA] 1 *unknown 237 Feb 21 1997 forfile.bat
+-[RWCEMFA] 1 *unknown 1401 Mar 03 1997 intertex.txt
+d[RWCEMFA] 1 cyp 512 May 15 08:55 d1
+d[RWCEMFA] 1 cyp 512 May 06 10:57 drivers
+d[RWCEMFA] 1 cyp 512 Mar 28 12:05 --moved-
+226 Transfer complete.
+ftp> close
+221 Goodbye.
+ftp> \ No newline at end of file
diff --git a/netwerk/test/gtest/parse-ftp/U-HellSoft.out b/netwerk/test/gtest/parse-ftp/U-HellSoft.out
new file mode 100644
index 0000000000..a42b16315c
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-HellSoft.out
@@ -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/. -->
+
+07-01-2002 11:49:00 <DIR> htdocs
+07-02-2002 08:56:00 <DIR> config
+06-20-2002 11:23:00 <DIR> other
+06-20-2002 11:23:00 <DIR> mail
+06-20-2002 14:35:00 <DIR> news
+06-30-2002 08:48:00 <DIR> download
+05-27-1997 00:00:00 13312 19calit.doc
+02-21-1997 00:00:00 237 forfile.bat
+03-03-1997 00:00:00 1401 intertex.txt
+05-15-2002 08:55:00 <DIR> d1
+05-06-2002 10:57:00 <DIR> drivers
+03-28-2002 12:05:00 <DIR> --moved-
diff --git a/netwerk/test/gtest/parse-ftp/U-NetPresenz.in b/netwerk/test/gtest/parse-ftp/U-NetPresenz.in
new file mode 100644
index 0000000000..1114ffe4ba
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-NetPresenz.in
@@ -0,0 +1,31 @@
+ftp> open gean5.pfmb.uni-mb.si
+Connected to gean5.pfmb.uni-mb.si.
+220 NetPresenz v4.1 (Unregistered) awaits your command.
+Name (gean5.pfmb.uni-mb.si:cyp):
+331 Guest log in, send Email address (user@host) as password.
+230-This file must be in the Startup Messages folder in either the same
+ folder as NetPresenz or in the NetPresenz Preferences folder in the
+ Preferences folder, and will be displayed to guests user when they log
+ in. Guest users log in by using the username "ftp" or "anonymous".
+230 Anonymous login to 1 volumes. Access restrictions apply. "/Eng Dept web pages/MariborEnglishClub".
+Remote system type is MACOS.
+ftp> ls
+200 PORT command successful.
+150 ASCII transfer started.
+-------r-- 0 2166 2166 Mar 22 09:21 default.htm
+-------r-- 0 16504 16504 Mar 21 14:32 FAQs.htm
+drwxr-xr-x folder 5 Mar 22 10:06 FAQs_files
+-------r-- 0 250 250 Mar 22 09:22 filelist.xml
+-------r-- 0 1575 1575 Mar 21 14:22 image001.gif
+-------r-- 0 631827 631827 Mar 21 14:22 image002.jpg
+-------r-- 0 34318 34318 Mar 21 14:22 image003.jpg
+-------r-- 0 9121 9121 Mar 22 09:30 MariborEnglishClubHomepage.htm
+-------r-- 0 6710 6710 Mar 21 14:30 Photos.htm
+drwxr-xr-x folder 5 Mar 22 10:06 Photos_files
+-------r-- 0 5995 5995 Mar 22 09:23 TOCFrame.htm
+-------r-- 0 7226 7226 Mar 21 14:27 Upcoming Events.htm
+drwxr-xr-x folder 3 Mar 22 10:06 Upcoming Events_files
+226 Transfer complete.
+ftp>ftp> close
+221 Nice chatting with you.
+ftp> \ No newline at end of file
diff --git a/netwerk/test/gtest/parse-ftp/U-NetPresenz.out b/netwerk/test/gtest/parse-ftp/U-NetPresenz.out
new file mode 100644
index 0000000000..754e0e3ca5
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-NetPresenz.out
@@ -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/. -->
+
+03-22-2002 09:21:00 2166 default.htm
+03-21-2002 14:32:00 16504 FAQs.htm
+03-22-2002 10:06:00 <DIR> FAQs_files
+03-22-2002 09:22:00 250 filelist.xml
+03-21-2002 14:22:00 1575 image001.gif
+03-21-2002 14:22:00 631827 image002.jpg
+03-21-2002 14:22:00 34318 image003.jpg
+03-22-2002 09:30:00 9121 MariborEnglishClubHomepage.htm
+03-21-2002 14:30:00 6710 Photos.htm
+03-22-2002 10:06:00 <DIR> Photos_files
+03-22-2002 09:23:00 5995 TOCFrame.htm
+03-21-2002 14:27:00 7226 Upcoming Events.htm
+03-22-2002 10:06:00 <DIR> Upcoming Events_files
diff --git a/netwerk/test/gtest/parse-ftp/U-NetWare.in b/netwerk/test/gtest/parse-ftp/U-NetWare.in
new file mode 100644
index 0000000000..c7252569e8
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-NetWare.in
@@ -0,0 +1,51 @@
+ftp> open netlab2.usu.edu
+Connected to netlab2.usu.edu.
+220 Service Ready for new User
+Name (netlab2.usu.edu:cyp):
+331 Enter E-Mail id as Password
+230 User anonymous Logged in Successfully
+Remote system type is NETWARE.
+ftp> syst
+215 NETWARE Type : L8
+ftp> ls
+200 PORT Command OK
+150 Opening data connection
+total 0
+- [RWCEAFMS] NFAUUser 192 Apr 27 15:21 HEADER.html
+d [RWCEAFMS] jrd 512 Jul 11 03:01 allupdates
+d [RWCEAFMS] jrd 512 Dec 05 2001 apps
+d [RWCEAFMS] jrd 512 Oct 21 2001 bsuk00
+d [RWCEAFMS] jrd 512 Oct 21 2001 bsuk2001
+d [RWCEAFMS] jrd 512 Oct 21 2001 bsuk99
+d [RWCEAFMS] jrd 512 Oct 21 2001 bsus2000
+d [RWCEAFMS] jrd 512 Oct 21 2001 bsus2001
+d [RWCEAFMS] jrd 512 Mar 27 20:23 bsus2002
+d [RWCEAFMS] jrd 512 Oct 21 2001 bsus99
+d [RWCEAFMS] jrd 512 Oct 21 2001 drivers
+d [RWCEAFMS] jrd 512 Oct 24 2001 freebird
+d [RWCEAFMS] jrd 512 Apr 30 13:56 kermit
+d [RWCEAFMS] jrd 512 Oct 21 2001 macipx
+d [RWCEAFMS] jrd 512 Jun 13 01:45 misc
+d [RWCEAFMS] jrd 512 Oct 21 2001 netwatch
+d [RWCEAFMS] jrd 512 Oct 21 2001 netwire
+d [RWCEAFMS] jrd 512 Oct 21 2001 odi
+d [RWCEAFMS] jrd 512 Oct 21 2001 pktdrvr
+d [RWCEAFMS] jrd 512 Mar 02 20:23 rfc
+d [RWCEAFMS] jrd 512 Oct 30 2001 UnixWare
+d [RWCEAFMS] jrd 512 Oct 21 2001 updates
+- [RWCEAFMS] jrd 666258 Oct 30 1992 ENCAPS10.PS
+- [RWCEAFMS] jrd 25085 Oct 28 1992 ENCAPS10.TXT
+- [RWCEAFMS] jrd 611989 Jan 13 1993 ENCAPS11.PS
+- [RWCEAFMS] jrd 27134 Jan 12 1993 ENCAPS11.TXT
+- [RWCEAFMS] jrd 372 Jan 13 1993 README.TXT
+226 Transfer Complete
+ftp> site help
+214-Only DOS and LONG site commands are supported for Anonymous Users
+ DOS :
+ The DOS command shows the file/directory permissions in DOS format.
+ LONG :
+ The LONG command shows the file/directory permissions in LONG format.
+214 End of Help
+ftp> close
+221 Closing Session
+ftp> \ No newline at end of file
diff --git a/netwerk/test/gtest/parse-ftp/U-NetWare.out b/netwerk/test/gtest/parse-ftp/U-NetWare.out
new file mode 100644
index 0000000000..6cf23ffb33
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-NetWare.out
@@ -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/. -->
+
+04-27-2002 15:21:00 192 HEADER.html
+07-11-2002 03:01:00 <DIR> allupdates
+12-05-2001 00:00:00 <DIR> apps
+10-21-2001 00:00:00 <DIR> bsuk00
+10-21-2001 00:00:00 <DIR> bsuk2001
+10-21-2001 00:00:00 <DIR> bsuk99
+10-21-2001 00:00:00 <DIR> bsus2000
+10-21-2001 00:00:00 <DIR> bsus2001
+03-27-2002 20:23:00 <DIR> bsus2002
+10-21-2001 00:00:00 <DIR> bsus99
+10-21-2001 00:00:00 <DIR> drivers
+10-24-2001 00:00:00 <DIR> freebird
+04-30-2002 13:56:00 <DIR> kermit
+10-21-2001 00:00:00 <DIR> macipx
+06-13-2002 01:45:00 <DIR> misc
+10-21-2001 00:00:00 <DIR> netwatch
+10-21-2001 00:00:00 <DIR> netwire
+10-21-2001 00:00:00 <DIR> odi
+10-21-2001 00:00:00 <DIR> pktdrvr
+03-02-2002 20:23:00 <DIR> rfc
+10-30-2001 00:00:00 <DIR> UnixWare
+10-21-2001 00:00:00 <DIR> updates
+10-30-1992 00:00:00 666258 ENCAPS10.PS
+10-28-1992 00:00:00 25085 ENCAPS10.TXT
+01-13-1993 00:00:00 611989 ENCAPS11.PS
+01-12-1993 00:00:00 27134 ENCAPS11.TXT
+01-13-1993 00:00:00 372 README.TXT
diff --git a/netwerk/test/gtest/parse-ftp/U-Novonyx.in b/netwerk/test/gtest/parse-ftp/U-Novonyx.in
new file mode 100644
index 0000000000..e3de9474d9
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-Novonyx.in
@@ -0,0 +1,92 @@
+ftp> open pandora.anglistik.uni-mainz.de
+220- Novonyx FTP Server for NetWare, v0.51 (9-16-98)
+220 Service Ready
+Name (pandora.anglistik.uni-mainz.de:cyp):
+331 User name okay, need password
+230 User logged in, proceed
+Remote system type is UNIX.
+Using binary mode to transfer files.
+ftp> cd login
+250 Directory successfully changed
+ftp> ls
+200 PORT Command OK
+150 File status OK, about to open data connection
+total 123
+-rw-rw-rw- 1 root sysadmin 25775 Sep 29 19:45 NOD$UPD.BAT
+-r--r--r-- 1 root sysadmin 2860 Feb 6 22:03 AX_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 B5DV_AIO.OVL
+-r--r--r-- 1 root sysadmin 7843 Feb 6 22:03 B5DV_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 B5ET_AIO.OVL
+-r--r--r-- 1 root sysadmin 5589 Feb 6 22:03 B5ET_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 B5KC_AIO.OVL
+-r--r--r-- 1 root sysadmin 5042 Feb 6 22:03 B5KC_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 BIG5_AIO.OVL
+-r--r--r-- 1 root sysadmin 3710 Feb 6 22:03 BIG5_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 CMPQ_AIO.OVL
+-r--r--r-- 1 root sysadmin 2815 Feb 6 22:03 CMPQ_RUN.OVL
+-r-xr-xr-x 1 root sysadmin 210009 Feb 6 22:03 CX.EXE
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 DOSV_AIO.OVL
+-r--r--r-- 1 root sysadmin 3825 Feb 6 22:03 DOSV_RUN.OVL
+-r--r--r-- 1 root sysadmin 16264 Feb 6 22:03 ETHER.RPL
+-r--r--r-- 1 root sysadmin 12133 Feb 6 22:03 F1ETH.RPL
+-r--r--r-- 1 root sysadmin 5141 Feb 6 22:03 FMR_AIO.OVL
+-r--r--r-- 1 root sysadmin 4747 Feb 6 22:03 FMR_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 GBCD_AIO.OVL
+-r--r--r-- 1 root sysadmin 3857 Feb 6 22:03 GBCD_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 GBET_AIO.OVL
+-r--r--r-- 1 root sysadmin 5545 Feb 6 22:03 GBET_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 GBLX_AIO.OVL
+-r--r--r-- 1 root sysadmin 3913 Feb 6 22:03 GBLX_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 GBPD_AIO.OVL
+-r--r--r-- 1 root sysadmin 8019 Feb 6 22:03 GBPD_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 GBSP_AIO.OVL
+-r--r--r-- 1 root sysadmin 8141 Feb 6 22:03 GBSP_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 GBTW_AIO.OVL
+-r--r--r-- 1 root sysadmin 4152 Feb 6 22:03 GBTW_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 GBUC_AIO.OVL
+-r--r--r-- 1 root sysadmin 4033 Feb 6 22:03 GBUC_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 GBWM_AIO.OVL
+-r--r--r-- 1 root sysadmin 3710 Feb 6 22:03 GBWM_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 GBXJ_AIO.OVL
+-r--r--r-- 1 root sysadmin 3848 Feb 6 22:03 GBXJ_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 GB_AIO.OVL
+-r--r--r-- 1 root sysadmin 3710 Feb 6 22:03 GB_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 IBM_AIO.OVL
+-r--r--r-- 1 root sysadmin 2815 Feb 6 22:03 IBM_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 J31_AIO.OVL
+-r--r--r-- 1 root sysadmin 5240 Feb 6 22:03 J31_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 KOR_AIO.OVL
+-r--r--r-- 1 root sysadmin 3710 Feb 6 22:03 KOR_RUN.OVL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 KSC_AIO.OVL
+-r--r--r-- 1 root sysadmin 3710 Feb 6 22:03 KSC_RUN.OVL
+-r-xr-xr-x 1 root sysadmin 311171 Apr 4 14:19 LOGIN.EXE
+-r--r--r-- 1 root sysadmin 5167 Feb 6 22:03 PC98_AIO.OVL
+-r--r--r-- 1 root sysadmin 7152 Feb 6 22:03 PC98_RUN.OVL
+-r--r--r-- 1 root sysadmin 10991 Feb 6 22:03 PCN2L.RPL
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 PS55_AIO.OVL
+-r--r--r-- 1 root sysadmin 3616 Feb 6 22:03 PS55_RUN.OVL
+-r--r--r-- 1 root sysadmin 8074 Feb 6 22:03 RBOOT.RPL
+-r--r--r-- 1 root sysadmin 9170 Feb 6 22:03 TEXTUTIL.IDX
+-r--r--r-- 1 root sysadmin 18433 Feb 6 22:03 TOKEN.RPL
+-r-xr-xr-x 1 root sysadmin 43549 Feb 6 22:03 TYPEMSG.EXE
+-r--r--r-- 1 root sysadmin 27 Feb 6 22:03 ATTACH.BAT
+-rwxrwxrwx 1 root sysadmin 439913 Feb 6 23:33 NLIST.EXE
+-r--r--r-- 1 root sysadmin 307 Feb 6 22:03 MENU_X.BAT
+-rwxrwxrwx 1 root sysadmin 269247 Feb 6 23:33 MAP.EXE
+-r--r--r-- 1 root sysadmin 4965 Feb 6 22:03 AX_AIO.OVL
+-r-xr-xr-x 1 root sysadmin 111663 Apr 16 12:20 LOGIN3X.EXE
+-rw-rw-rw- 1 root sysadmin 38 Apr 18 15:19 login.nb
+-r-xr-xr-x 1 root sysadmin 392528 Apr 20 04:02 NPRINTER.EXE
+-r-xr-xr-x 1 root sysadmin 311171 Feb 9 16:29 LOGIN4X.EXE
+-rw-rw-rw- 1 root sysadmin 2102 Feb 8 13:18 lpt$cap.bat
+-rw-rw-rw- 1 root sysadmin 20614 Mar 13 13:42 nod$log.bat
+-rw-rw-rw- 1 root sysadmin 20450 Mar 12 10:36 nod$log.~ba
+drwxrwxrwx 1 root sysadmin 0 Mar 8 13:30 NLS
+drwxrwxrwx 1 root sysadmin 0 Mar 8 13:30 OS2
+drwxrwxrwx 1 root sysadmin 0 Apr 9 16:37 NOD$LOG.DRV
+drwxrwxrwx 1 root sysadmin 0 Apr 9 16:36 LOCAL
+drwxrwxrwx 1 root sysadmin 0 Mar 16 14:25 local.nt4
+226 Requested file action okay, completed
+ftp> close
+221 Service closing connection, user logged out
+ftp>
diff --git a/netwerk/test/gtest/parse-ftp/U-Novonyx.out b/netwerk/test/gtest/parse-ftp/U-Novonyx.out
new file mode 100644
index 0000000000..8755d5fa7b
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-Novonyx.out
@@ -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/. -->
+
+09-29-2001 19:45:00 25775 NOD$UPD.BAT
+02-06-2002 22:03:00 2860 AX_RUN.OVL
+02-06-2002 22:03:00 4965 B5DV_AIO.OVL
+02-06-2002 22:03:00 7843 B5DV_RUN.OVL
+02-06-2002 22:03:00 4965 B5ET_AIO.OVL
+02-06-2002 22:03:00 5589 B5ET_RUN.OVL
+02-06-2002 22:03:00 4965 B5KC_AIO.OVL
+02-06-2002 22:03:00 5042 B5KC_RUN.OVL
+02-06-2002 22:03:00 4965 BIG5_AIO.OVL
+02-06-2002 22:03:00 3710 BIG5_RUN.OVL
+02-06-2002 22:03:00 4965 CMPQ_AIO.OVL
+02-06-2002 22:03:00 2815 CMPQ_RUN.OVL
+02-06-2002 22:03:00 210009 CX.EXE
+02-06-2002 22:03:00 4965 DOSV_AIO.OVL
+02-06-2002 22:03:00 3825 DOSV_RUN.OVL
+02-06-2002 22:03:00 16264 ETHER.RPL
+02-06-2002 22:03:00 12133 F1ETH.RPL
+02-06-2002 22:03:00 5141 FMR_AIO.OVL
+02-06-2002 22:03:00 4747 FMR_RUN.OVL
+02-06-2002 22:03:00 4965 GBCD_AIO.OVL
+02-06-2002 22:03:00 3857 GBCD_RUN.OVL
+02-06-2002 22:03:00 4965 GBET_AIO.OVL
+02-06-2002 22:03:00 5545 GBET_RUN.OVL
+02-06-2002 22:03:00 4965 GBLX_AIO.OVL
+02-06-2002 22:03:00 3913 GBLX_RUN.OVL
+02-06-2002 22:03:00 4965 GBPD_AIO.OVL
+02-06-2002 22:03:00 8019 GBPD_RUN.OVL
+02-06-2002 22:03:00 4965 GBSP_AIO.OVL
+02-06-2002 22:03:00 8141 GBSP_RUN.OVL
+02-06-2002 22:03:00 4965 GBTW_AIO.OVL
+02-06-2002 22:03:00 4152 GBTW_RUN.OVL
+02-06-2002 22:03:00 4965 GBUC_AIO.OVL
+02-06-2002 22:03:00 4033 GBUC_RUN.OVL
+02-06-2002 22:03:00 4965 GBWM_AIO.OVL
+02-06-2002 22:03:00 3710 GBWM_RUN.OVL
+02-06-2002 22:03:00 4965 GBXJ_AIO.OVL
+02-06-2002 22:03:00 3848 GBXJ_RUN.OVL
+02-06-2002 22:03:00 4965 GB_AIO.OVL
+02-06-2002 22:03:00 3710 GB_RUN.OVL
+02-06-2002 22:03:00 4965 IBM_AIO.OVL
+02-06-2002 22:03:00 2815 IBM_RUN.OVL
+02-06-2002 22:03:00 4965 J31_AIO.OVL
+02-06-2002 22:03:00 5240 J31_RUN.OVL
+02-06-2002 22:03:00 4965 KOR_AIO.OVL
+02-06-2002 22:03:00 3710 KOR_RUN.OVL
+02-06-2002 22:03:00 4965 KSC_AIO.OVL
+02-06-2002 22:03:00 3710 KSC_RUN.OVL
+04-04-2002 14:19:00 311171 LOGIN.EXE
+02-06-2002 22:03:00 5167 PC98_AIO.OVL
+02-06-2002 22:03:00 7152 PC98_RUN.OVL
+02-06-2002 22:03:00 10991 PCN2L.RPL
+02-06-2002 22:03:00 4965 PS55_AIO.OVL
+02-06-2002 22:03:00 3616 PS55_RUN.OVL
+02-06-2002 22:03:00 8074 RBOOT.RPL
+02-06-2002 22:03:00 9170 TEXTUTIL.IDX
+02-06-2002 22:03:00 18433 TOKEN.RPL
+02-06-2002 22:03:00 43549 TYPEMSG.EXE
+02-06-2002 22:03:00 27 ATTACH.BAT
+02-06-2002 23:33:00 439913 NLIST.EXE
+02-06-2002 22:03:00 307 MENU_X.BAT
+02-06-2002 23:33:00 269247 MAP.EXE
+02-06-2002 22:03:00 4965 AX_AIO.OVL
+04-16-2002 12:20:00 111663 LOGIN3X.EXE
+04-18-2002 15:19:00 38 login.nb
+04-20-2002 04:02:00 392528 NPRINTER.EXE
+02-09-2002 16:29:00 311171 LOGIN4X.EXE
+02-08-2002 13:18:00 2102 lpt$cap.bat
+03-13-2002 13:42:00 20614 nod$log.bat
+03-12-2002 10:36:00 20450 nod$log.~ba
+03-08-2002 13:30:00 <DIR> NLS
+03-08-2002 13:30:00 <DIR> OS2
+04-09-2002 16:37:00 <DIR> NOD$LOG.DRV
+04-09-2002 16:36:00 <DIR> LOCAL
+03-16-2002 14:25:00 <DIR> local.nt4
diff --git a/netwerk/test/gtest/parse-ftp/U-Surge.in b/netwerk/test/gtest/parse-ftp/U-Surge.in
new file mode 100644
index 0000000000..f1e0963688
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-Surge.in
@@ -0,0 +1,66 @@
+ftp> open netwinsite.com
+Connected to netwinsite.com.
+220 SurgeFTP netwin1 (Version 2.1e)
+Name (netwinsite.com:cyp): 331 Guest login ok, send your complete e-mail address as password.
+230- Alias Real path Access
+230- / /disk2/ftp read
+230 User anonymous logged in.
+Remote system type is UNIX.
+Using binary mode to transfer files.
+ftp> cd pub
+250 CWD command successful now (/pub)
+ftp> ls
+200 PORT command successful.
+150 Opening ASCII mode data connection for file list. (/pub)
+drwxr-xr-x 2 jesseftp netwin 4096 Jul 7 21:37 cgimail
+drwxr-xr-x 4 dnewsftp netwin 4096 May 2 17:38 dbabble
+drwxr-xr-x 2 netwin netwin 4096 Jul 1 2001 dek_temp
+drwxr-xr-x 2 netwin netwin 4096 Jul 1 2001 deye
+drwxr-xr-x 2 jesseftp root 4096 Feb 18 17:50 dfree
+drwxr-xr-x 2 netwin netwin 4096 Jul 1 2001 dgate
+drwxr-xr-x 4 root root 4096 Jan 31 15:18 discontinued
+drwxr-xr-x 4 dmailftp netwin 8192 Jul 7 21:28 dmail
+drwxr-xr-x 4 pnftp netwin 4096 Jun 30 17:56 dmailweb
+drwxr-xr-x 8 dnewsftp netwin 8192 Jul 10 1:40 dnews
+drwxr-xr-x 3 netwin netwin 4096 Jul 1 2001 dnewsweb
+drwxr-xr-x 3 pnftp root 4096 Sep 4 2001 dnotice
+drwxr-xr-x 2 root root 4096 May 6 16:58 fengate
+drwxr-xr-x 2 pnftp root 4096 May 28 19:01 ferngate
+drwxr-xr-x 3 netwin netwin 4096 Jul 1 2001 jeeves
+drwxr-xr-x 2 netwin netwin 4096 Jun 27 20:10 misc
+drwxr-xr-x 3 pnftp bin 4096 Jul 4 21:13 netauth
+drwxr-xr-x 2 pnftp bin 4096 Feb 8 1999 poppassd
+drwxr-xr-x 2 jesseftp netwin 4096 Jul 9 1:39 surgeftp
+drwxr-xr-x 2 surgemail root 4096 Jul 3 0:00 surgemail
+drwxr-xr-x 2 surgemail root 4096 Jul 3 0:01 surgemail-enterprise
+drwxr-xr-x 2 surgemail root 4096 Jul 3 0:01 surgemail-home
+drwxr-xr-x 2 surgemail root 4096 Jul 3 0:01 surgemail-isp
+drwxr-xr-x 2 surgemail root 4096 Jul 3 0:01 surgemail-workplace
+drwxr-xr-x 3 netwin bin 4096 Jul 4 2001 watchdog
+drwxr-xr-x 3 pnftp bin 4096 Mar 21 2001 webimap
+drwxr-xr-x 3 pnftp root 4096 Apr 28 21:30 webmail
+drwxr-xr-x 3 pnftp bin 4096 Jun 30 18:23 webnews
+drwxr-xr-x 2 pnftp root 4096 Jan 30 20:31 webshareit
+drwxr-xr-x 2 netwin bin 4096 Sep 20 1999 webtwin
+drwxr-xr-x 4 pnftp root 4096 Jul 8 17:07 beta
+lrwxrwxrwx 1 pnftp pnftp 12 Mar 18 15:48 wmail.exe -> wmail30h.exe
+-rw-r--r-- 1 pnftp pnftp 2312192 Feb 19 15:33 wmail30h.exe
+-rw-r--r-- 1 pnftp pnftp 3360893 Feb 21 19:24 wmail30h_aix4.tar.Z
+-rw-r--r-- 1 pnftp pnftp 3508885 Mar 18 15:47 wmail30h_bsdi4.tar.Z
+-rw-r--r-- 1 pnftp pnftp 2693234 Feb 19 15:47 wmail30h_freebsd.tar.Z
+-rw-r--r-- 1 pnftp pnftp 2640047 Feb 19 15:38 wmail30h_freebsd3.tar.Z
+-rw-r--r-- 1 pnftp pnftp 2654381 Feb 19 15:43 wmail30h_freebsd4.tar.Z
+-rw-r--r-- 1 pnftp pnftp 3005333 Mar 18 18:21 wmail30h_hpux.tar.Z
+-rw-r--r-- 1 pnftp pnftp 2779942 Feb 19 15:52 wmail30h_linux.tar.Z
+-rw-r--r-- 1 pnftp pnftp 2782353 Feb 19 15:58 wmail30h_linuxlibc6.tar.Z
+-rw-r--r-- 1 pnftp pnftp 1783391 Feb 19 16:01 wmail30h_linuxppc.tar.gz
+-rw-r--r-- 1 pnftp pnftp 2758233 Mar 18 15:47 wmail30h_macosx_aqua.tar.Z
+-rw-r--r-- 1 pnftp pnftp 3113365 Mar 18 15:47 wmail30h_osf.tar.Z
+-rw-r--r-- 1 pnftp pnftp 1779720 Apr 28 21:30 wmail30h_raq3.tar.gz
+-rw-r--r-- 1 pnftp pnftp 1789173 Mar 11 16:38 wmail30h_raq4.tar.gz
+-rw-r--r-- 1 pnftp pnftp 2846045 Feb 19 16:06 wmail30h_solaris.tar.Z
+-rw-r--r-- 1 pnftp pnftp 2858946 Feb 19 16:11 wmail30h_solarisx86.tar.Z
+226 Transfer complete.
+ftp> close
+221 Closing connection - goodbye!
+ftp> \ No newline at end of file
diff --git a/netwerk/test/gtest/parse-ftp/U-Surge.out b/netwerk/test/gtest/parse-ftp/U-Surge.out
new file mode 100644
index 0000000000..e6b6527106
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-Surge.out
@@ -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/. -->
+
+07-07-2002 21:37:00 <DIR> cgimail
+05-02-2002 17:38:00 <DIR> dbabble
+07-01-2001 00:00:00 <DIR> dek_temp
+07-01-2001 00:00:00 <DIR> deye
+02-18-2002 17:50:00 <DIR> dfree
+07-01-2001 00:00:00 <DIR> dgate
+01-31-2002 15:18:00 <DIR> discontinued
+07-07-2002 21:28:00 <DIR> dmail
+06-30-2002 17:56:00 <DIR> dmailweb
+07-10-2002 01:40:00 <DIR> dnews
+07-01-2001 00:00:00 <DIR> dnewsweb
+09-04-2001 00:00:00 <DIR> dnotice
+05-06-2002 16:58:00 <DIR> fengate
+05-28-2002 19:01:00 <DIR> ferngate
+07-01-2001 00:00:00 <DIR> jeeves
+06-27-2002 20:10:00 <DIR> misc
+07-04-2002 21:13:00 <DIR> netauth
+02-08-1999 00:00:00 <DIR> poppassd
+07-09-2002 01:39:00 <DIR> surgeftp
+07-03-2002 00:00:00 <DIR> surgemail
+07-03-2002 00:01:00 <DIR> surgemail-enterprise
+07-03-2002 00:01:00 <DIR> surgemail-home
+07-03-2002 00:01:00 <DIR> surgemail-isp
+07-03-2002 00:01:00 <DIR> surgemail-workplace
+07-04-2001 00:00:00 <DIR> watchdog
+03-21-2001 00:00:00 <DIR> webimap
+04-28-2002 21:30:00 <DIR> webmail
+06-30-2002 18:23:00 <DIR> webnews
+01-30-2002 20:31:00 <DIR> webshareit
+09-20-1999 00:00:00 <DIR> webtwin
+07-08-2002 17:07:00 <DIR> beta
+03-18-2002 15:48:00 <JUNCTION> wmail.exe -> wmail30h.exe
+02-19-2002 15:33:00 2312192 wmail30h.exe
+02-21-2002 19:24:00 3360893 wmail30h_aix4.tar.Z
+03-18-2002 15:47:00 3508885 wmail30h_bsdi4.tar.Z
+02-19-2002 15:47:00 2693234 wmail30h_freebsd.tar.Z
+02-19-2002 15:38:00 2640047 wmail30h_freebsd3.tar.Z
+02-19-2002 15:43:00 2654381 wmail30h_freebsd4.tar.Z
+03-18-2002 18:21:00 3005333 wmail30h_hpux.tar.Z
+02-19-2002 15:52:00 2779942 wmail30h_linux.tar.Z
+02-19-2002 15:58:00 2782353 wmail30h_linuxlibc6.tar.Z
+02-19-2002 16:01:00 1783391 wmail30h_linuxppc.tar.gz
+03-18-2002 15:47:00 2758233 wmail30h_macosx_aqua.tar.Z
+03-18-2002 15:47:00 3113365 wmail30h_osf.tar.Z
+04-28-2002 21:30:00 1779720 wmail30h_raq3.tar.gz
+03-11-2002 16:38:00 1789173 wmail30h_raq4.tar.gz
+02-19-2002 16:06:00 2846045 wmail30h_solaris.tar.Z
+02-19-2002 16:11:00 2858946 wmail30h_solarisx86.tar.Z
diff --git a/netwerk/test/gtest/parse-ftp/U-WarFTPd.in b/netwerk/test/gtest/parse-ftp/U-WarFTPd.in
new file mode 100644
index 0000000000..244c087097
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-WarFTPd.in
@@ -0,0 +1,38 @@
+ftp> open ftp.jgaa.com
+Connected to ftp.jgaa.com.
+220-Jgaa's official FTP server
+ WarFTPd 1.81.00 (Mar 3 2002) Ready
+ (C)opyright 1996 - 2002 by Jarle (jgaa) Aase - all rights reserved.
+220 Please enter your user name.
+Name (ftp.jgaa.com:cyp):
+230 User logged in.
+Remote system type is UNIX.
+Using binary mode to transfer files.
+ftp> passive
+Passive mode on.
+ftp> ls
+227 Entering Passive Mode (80,239,13,229,14,199)
+125 Using existing ASCII mode data connection for /bin/ls (1220 bytes).
+total 5127
+dr-xr--r-- 1 root root 0 Apr 22 2009 .
+dr-xr--r-- 1 root root 0 Apr 22 2009 ..
+-r-xr--r-- 1 root root 359807 Mar 13 2000 adns-0.6-win32-01.zip
+-r-xr--r-- 1 root root 77 Oct 17 2000 adns-0.6-win32-01.zip.md5
+-r-xr--r-- 1 root root 218767 Nov 11 2000 cfs-1.3.3-8bit-fix.r2.tgz
+-r-xr--r-- 1 root root 1606 Jun 13 2000 cfs-1.3.3-eight-bit-fix.diff.tar.gz
+-r-xr--r-- 1 root root 3327 Oct 19 2000 chkmailspool-1.0.tgz
+-r-xr--r-- 1 root root 9356 Oct 21 2000 chkmailspool-1.1.tgz
+-r-xr--r-- 1 root root 32742 Feb 1 2000 iisstat-1.2.zip
+-r-xr--r-- 1 root root 71 Oct 17 2000 iisstat-1.2.zip.md5
+-r-xr--r-- 1 root root 27531 Aug 14 1999 inshtml.zip
+-r-xr--r-- 1 root root 67 Oct 17 2000 inshtml.zip.md5
+-r-xr--r-- 1 root root 23780 Aug 14 1999 netcps.zip
+-r-xr--r-- 1 root root 66 Oct 17 2000 netcps.zip.md5
+-r-xr--r-- 1 root root 1057919 Nov 3 2001 openssl-0.9.6b-win32.zip
+-r-xr--r-- 1 root root 56823 Jan 28 2001 passgen-2.0.zip
+-r-xr--r-- 1 root root 826880 May 25 1997 warftpd-version2-public-source.zip
+-r-xr--r-- 1 root root 90 Oct 17 2000 warftpd-version2-public-source.zip.md5
+226 Transfer complete. 1220 bytes in 0.01 sec. (119.141 Kb/s)
+ftp> close
+221 Goodbye. Control connection closed.
+ftp> \ No newline at end of file
diff --git a/netwerk/test/gtest/parse-ftp/U-WarFTPd.out b/netwerk/test/gtest/parse-ftp/U-WarFTPd.out
new file mode 100644
index 0000000000..b0a0bab43c
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-WarFTPd.out
@@ -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/. -->
+
+04-22-2009 00:00:00 <DIR> .
+04-22-2009 00:00:00 <DIR> ..
+03-13-2000 00:00:00 359807 adns-0.6-win32-01.zip
+10-17-2000 00:00:00 77 adns-0.6-win32-01.zip.md5
+11-11-2000 00:00:00 218767 cfs-1.3.3-8bit-fix.r2.tgz
+06-13-2000 00:00:00 1606 cfs-1.3.3-eight-bit-fix.diff.tar.gz
+10-19-2000 00:00:00 3327 chkmailspool-1.0.tgz
+10-21-2000 00:00:00 9356 chkmailspool-1.1.tgz
+02-01-2000 00:00:00 32742 iisstat-1.2.zip
+10-17-2000 00:00:00 71 iisstat-1.2.zip.md5
+08-14-1999 00:00:00 27531 inshtml.zip
+10-17-2000 00:00:00 67 inshtml.zip.md5
+08-14-1999 00:00:00 23780 netcps.zip
+10-17-2000 00:00:00 66 netcps.zip.md5
+11-03-2001 00:00:00 1057919 openssl-0.9.6b-win32.zip
+01-28-2001 00:00:00 56823 passgen-2.0.zip
+05-25-1997 00:00:00 826880 warftpd-version2-public-source.zip
+10-17-2000 00:00:00 90 warftpd-version2-public-source.zip.md5
diff --git a/netwerk/test/gtest/parse-ftp/U-WebStar.in b/netwerk/test/gtest/parse-ftp/U-WebStar.in
new file mode 100644
index 0000000000..4010d462e1
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-WebStar.in
@@ -0,0 +1,41 @@
+ftp> open ftp.webstar.com
+Connected to ftp.webstar.com.
+220 FTP server ready.
+Name (ftp.webstar.com:cyp):
+331 Anonymous login OK, send login as password.
+230 User logged in.
+Remote system type is UNIX.
+ftp> pwd
+257 "/" is the current directory.
+ftp> syst
+215 UNIX WebSTAR FTP.
+ftp> cd products
+250 CWD OK, "/products".
+ftp> cd WebStar
+250 CWD OK, "/products/WebStar".
+ftp> cd Updates
+250 CWD OK, "/products/WebStar/Updates".
+ftp> ls
+200 PORT OK, IP address 134.93.247.34 port 49247
+150 Opening data connection.
+drwx------ 0 owner group 1 Jan 30 17:16 bundleup
+drwx------ 0 owner group 2 Nov 08 2001 docs
+drwx------ 0 owner group 12 Nov 08 2001 extending_webstar
+drwx------ 0 owner group 2 Apr 25 09:40 Products
+drwx------ 0 owner group 1 Jan 30 16:20 updates
+drwx------ 0 owner group 11 Jan 30 17:16 webstar_dev
+drwx------ 0 owner group 2 May 01 15:46 WebSTAR
+drwx------ 0 owner group 15 May 13 09:54 Installers
+drwx------ 0 owner group 8 Apr 25 09:59 Updates
+drwx------ 0 owner group 7 Apr 25 10:00 Plug-ins
+drwx------ 0 owner group 2 Apr 06 2001 SSL
+-rwx------ 0 owner group 1605421 Mar 30 2001 webstar1.3.2_updater.sea.hqx
+-rwx------ 0 owner group 6883859 Mar 30 2001 WebSTAR211installer.sea.hqx
+-rwx------ 0 owner group 7366223 Mar 30 2001 WebSTAR211updater.sea.hqx
+-rwx------ 0 owner group 13415184 Mar 30 2001 WebSTAR302updater.sea.hqx
+drwx------ 0 owner group 4 Apr 05 2001 WebSTAR_44
+drwx------ 0 owner group 2 Apr 25 10:00 WebSTAR_V
+226 Transfer complete.
+ftp> close
+221 Goodbye.
+ftp> \ No newline at end of file
diff --git a/netwerk/test/gtest/parse-ftp/U-WebStar.out b/netwerk/test/gtest/parse-ftp/U-WebStar.out
new file mode 100644
index 0000000000..3a6720727c
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-WebStar.out
@@ -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/. -->
+
+01-30-2002 17:16:00 <DIR> bundleup
+11-08-2001 00:00:00 <DIR> docs
+11-08-2001 00:00:00 <DIR> extending_webstar
+04-25-2002 09:40:00 <DIR> Products
+01-30-2002 16:20:00 <DIR> updates
+01-30-2002 17:16:00 <DIR> webstar_dev
+05-01-2002 15:46:00 <DIR> WebSTAR
+05-13-2002 09:54:00 <DIR> Installers
+04-25-2002 09:59:00 <DIR> Updates
+04-25-2002 10:00:00 <DIR> Plug-ins
+04-06-2001 00:00:00 <DIR> SSL
+03-30-2001 00:00:00 1605421 webstar1.3.2_updater.sea.hqx
+03-30-2001 00:00:00 6883859 WebSTAR211installer.sea.hqx
+03-30-2001 00:00:00 7366223 WebSTAR211updater.sea.hqx
+03-30-2001 00:00:00 13415184 WebSTAR302updater.sea.hqx
+04-05-2001 00:00:00 <DIR> WebSTAR_44
+04-25-2002 10:00:00 <DIR> WebSTAR_V
diff --git a/netwerk/test/gtest/parse-ftp/U-WinNT.in b/netwerk/test/gtest/parse-ftp/U-WinNT.in
new file mode 100644
index 0000000000..a883fe0aae
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-WinNT.in
@@ -0,0 +1,68 @@
+ftp> open ftp.microsoft.com
+220 Microsoft FTP Service
+Name (ftp.microsoft.com:cyp):
+331 Anonymous access allowed, send identity (e-mail name) as password.
+230-This is FTP.Microsoft.Com
+230 Anonymous user logged in.
+Remote system type is Windows_NT.
+ftp> quote dirstyle
+200 MSDOS-like directory output is on
+ftp> quote dirstyle
+200 MSDOS-like directory output is off
+ftp> ls
+200 PORT command successful.
+150 Opening ASCII mode data connection for /bin/ls.
+dr-xr-xr-x 1 owner group 0 Feb 25 2000 channel
+dr-xr-xr-x 1 owner group 0 Feb 25 2000 Education
+dr-xr-xr-x 1 owner group 0 Feb 25 2000 enterprise
+dr-xr-xr-x 1 owner group 0 Jun 20 2001 ISN
+dr-xr-xr-x 1 owner group 0 May 31 2001 Museum
+dr-xr-xr-x 1 owner group 0 Feb 14 2001 TechNet
+dr-xr-xr-x 1 owner group 0 Oct 24 2001 whql
+dr-xr-xr-x 1 owner group 0 Feb 5 2001 20Year
+dr-xr-xr-x 1 owner group 0 Sep 26 2000 AccessFoxPro
+dr-xr-xr-x 1 owner group 0 Dec 21 2000 AlbGrp
+dr-xr-xr-x 1 owner group 0 May 31 2001 Alexandra
+dr-xr-xr-x 1 owner group 0 Jan 19 2001 anna
+dr-xr-xr-x 1 owner group 0 Apr 6 2000 anz
+dr-xr-xr-x 1 owner group 0 May 10 2000 Chase Bobko2
+dr-xr-xr-x 1 owner group 0 Mar 29 2000 cnn
+dr-xr-xr-x 1 owner group 0 Nov 21 2000 Darin
+dr-xr-xr-x 1 owner group 0 Mar 9 2000 digitalchicago
+dr-xr-xr-x 1 owner group 0 Sep 6 2000 Dublin
+dr-xr-xr-x 1 owner group 0 Jan 30 2001 eleanorf
+dr-xr-xr-x 1 owner group 0 Apr 26 2001 girvin
+dr-xr-xr-x 1 owner group 0 Apr 26 2000 Hires
+dr-xr-xr-x 1 owner group 0 Aug 15 2000 HR
+-r-xr-xr-x 1 owner group 4368384 Oct 24 1999 IMG00022.PCD
+dr-xr-xr-x 1 owner group 0 May 18 2001 jacubowsky
+dr-xr-xr-x 1 owner group 0 Oct 12 2000 JFalvey
+dr-xr-xr-x 1 owner group 0 Mar 28 2001 johnci
+dr-xr-xr-x 1 owner group 0 Jul 14 2000 Karin
+dr-xr-xr-x 1 owner group 0 Sep 7 2000 Kjung
+dr-xr-xr-x 1 owner group 0 Sep 28 2000 LarryE
+dr-xr-xr-x 1 owner group 0 Aug 17 2000 Larson
+dr-xr-xr-x 1 owner group 0 Sep 12 2000 marion
+dr-xr-xr-x 1 owner group 0 Aug 9 2000 ms25
+dr-xr-xr-x 1 owner group 0 Nov 16 2000 MS25Brochure
+dr-xr-xr-x 1 owner group 0 Mar 29 2000 MShistory
+dr-xr-xr-x 1 owner group 0 Sep 5 2000 Neils
+dr-xr-xr-x 1 owner group 0 Aug 2 2000 NLM
+dr-xr-xr-x 1 owner group 0 Sep 6 2000 PageOne
+dr-xr-xr-x 1 owner group 0 Jun 27 2000 pccomputing
+dr-xr-xr-x 1 owner group 0 May 9 2001 pictures
+dr-xr-xr-x 1 owner group 0 Jul 21 2000 pranks
+dr-xr-xr-x 1 owner group 0 Aug 22 2000 Sean
+dr-xr-xr-x 1 owner group 0 Aug 10 2000 SLeong
+dr-xr-xr-x 1 owner group 0 Sep 7 2000 svr
+dr-xr-xr-x 1 owner group 0 Jul 21 2000 Transcontinental
+dr-xr-xr-x 1 owner group 0 Oct 23 2000 veronist
+dr-xr-xr-x 1 owner group 0 Jun 15 2000 zoe
+-r-xr-xr-x 1 owner group 2094926 Jul 14 2000 canprankdesk.tif
+-r-xr-xr-x 1 owner group 95077 Jul 21 2000 Jon Kauffman Enjoys the Good Life.jpg
+-r-xr-xr-x 1 owner group 52275 Jul 21 2000 Name Plate.jpg
+-r-xr-xr-x 1 owner group 2250540 Jul 14 2000 Valentineoffprank-HiRes.jpg
+226 Transfer complete.
+ftp> close
+221 Thank-You For Using Microsoft Products!
+ftp>
diff --git a/netwerk/test/gtest/parse-ftp/U-WinNT.out b/netwerk/test/gtest/parse-ftp/U-WinNT.out
new file mode 100644
index 0000000000..5c4106b9be
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-WinNT.out
@@ -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/. -->
+
+02-25-2000 00:00:00 <DIR> channel
+02-25-2000 00:00:00 <DIR> Education
+02-25-2000 00:00:00 <DIR> enterprise
+06-20-2001 00:00:00 <DIR> ISN
+05-31-2001 00:00:00 <DIR> Museum
+02-14-2001 00:00:00 <DIR> TechNet
+10-24-2001 00:00:00 <DIR> whql
+02-05-2001 00:00:00 <DIR> 20Year
+09-26-2000 00:00:00 <DIR> AccessFoxPro
+12-21-2000 00:00:00 <DIR> AlbGrp
+05-31-2001 00:00:00 <DIR> Alexandra
+01-19-2001 00:00:00 <DIR> anna
+04-06-2000 00:00:00 <DIR> anz
+05-10-2000 00:00:00 <DIR> Chase Bobko2
+03-29-2000 00:00:00 <DIR> cnn
+11-21-2000 00:00:00 <DIR> Darin
+03-09-2000 00:00:00 <DIR> digitalchicago
+09-06-2000 00:00:00 <DIR> Dublin
+01-30-2001 00:00:00 <DIR> eleanorf
+04-26-2001 00:00:00 <DIR> girvin
+04-26-2000 00:00:00 <DIR> Hires
+08-15-2000 00:00:00 <DIR> HR
+10-24-1999 00:00:00 4368384 IMG00022.PCD
+05-18-2001 00:00:00 <DIR> jacubowsky
+10-12-2000 00:00:00 <DIR> JFalvey
+03-28-2001 00:00:00 <DIR> johnci
+07-14-2000 00:00:00 <DIR> Karin
+09-07-2000 00:00:00 <DIR> Kjung
+09-28-2000 00:00:00 <DIR> LarryE
+08-17-2000 00:00:00 <DIR> Larson
+09-12-2000 00:00:00 <DIR> marion
+08-09-2000 00:00:00 <DIR> ms25
+11-16-2000 00:00:00 <DIR> MS25Brochure
+03-29-2000 00:00:00 <DIR> MShistory
+09-05-2000 00:00:00 <DIR> Neils
+08-02-2000 00:00:00 <DIR> NLM
+09-06-2000 00:00:00 <DIR> PageOne
+06-27-2000 00:00:00 <DIR> pccomputing
+05-09-2001 00:00:00 <DIR> pictures
+07-21-2000 00:00:00 <DIR> pranks
+08-22-2000 00:00:00 <DIR> Sean
+08-10-2000 00:00:00 <DIR> SLeong
+09-07-2000 00:00:00 <DIR> svr
+07-21-2000 00:00:00 <DIR> Transcontinental
+10-23-2000 00:00:00 <DIR> veronist
+06-15-2000 00:00:00 <DIR> zoe
+07-14-2000 00:00:00 2094926 canprankdesk.tif
+07-21-2000 00:00:00 95077 Jon Kauffman Enjoys the Good Life.jpg
+07-21-2000 00:00:00 52275 Name Plate.jpg
+07-14-2000 00:00:00 2250540 Valentineoffprank-HiRes.jpg
diff --git a/netwerk/test/gtest/parse-ftp/U-hethmon.in b/netwerk/test/gtest/parse-ftp/U-hethmon.in
new file mode 100644
index 0000000000..2c9887cdbf
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-hethmon.in
@@ -0,0 +1,42 @@
+ftp> open ftp.hethmon.com
+Connected to ftp.hethmon.com.
+220 Hethmon Brothers FTP Server for OS/2 ** BETA 2.0 **. Ready for new user.
+Name (ftp.hethmon.com:cyp):
+331 Guest logins allowed. Email address required for anonymous.
+230 User foo@bar.com logged in.
+Remote system type is UNIX.
+ftp> cd pub
+250 Directory changed to "/pub"
+ftp> ls
+200 Port command ok
+150 Opening ASCII mode data connection.
+d--x------ 1 ftp ftp 5 Mar 21 1999 ..
+d--x------ 1 ftp ftp 0 Feb 16 2002 beta
+---------- 1 ftp ftp 35573 May 5 2000 CheckQ2.zip
+---------- 1 ftp ftp 1550521 May 5 2000 Ftpd106.zip
+---------- 1 ftp ftp 35459 Feb 7 2001 hmailbox.zip
+---------- 1 ftp ftp 55463 May 5 2000 HRxMail.zip
+---------- 1 ftp ftp 34950 May 5 2000 HRxPass.zip
+---------- 1 ftp ftp 1320184 May 5 2000 inetmail-1.2.1.pro.zip
+---------- 1 ftp ftp 1218483 May 5 2000 inetmail-1.2.1.zip
+---------- 1 ftp ftp 1615428 May 5 2000 inetmail-1.3.0.pro.zip
+---------- 1 ftp ftp 1425567 May 5 2000 inetmail-1.3.0.zip
+---------- 1 ftp ftp 1629145 Jan 9 2001 inetmail-1.3.18.zip
+---------- 1 ftp ftp 1625314 Feb 7 2001 inetmail-1.3.18a.zip
+---------- 1 ftp ftp 1407069 May 5 2000 inetmail-1.5.0.pro.zip
+---------- 1 ftp ftp 1470338 Sep 27 2000 inetmail-1.5.31.pro.zip
+---------- 1 ftp ftp 1583435 Jan 4 2001 inetmail-1.5.32.pro.zip
+---------- 1 ftp ftp 1429364 May 5 2000 inetmail-1.5.5.pro.zip
+---------- 1 ftp ftp 1471373 May 5 2000 inetmail-1.5.6.pro.zip
+d--x------ 1 ftp ftp 0 Feb 8 2001 misc
+---------- 1 ftp ftp 30913 May 5 2000 MXLookup.zip
+d--x------ 1 ftp ftp 0 May 2 2002 perseus
+d--x------ 1 ftp ftp 0 Feb 8 2001 rfcs
+d--x------ 1 ftp ftp 0 Feb 8 2001 scripts
+d--x------ 1 ftp ftp 0 Feb 8 2001 tcpip
+d--x------ 1 ftp ftp 0 May 5 2000 testcase
+d--x------ 1 ftp ftp 0 Feb 7 2001 tools
+226 Data transfer complete
+ftp> close
+221 Service closing control connection.
+ftp> \ No newline at end of file
diff --git a/netwerk/test/gtest/parse-ftp/U-hethmon.out b/netwerk/test/gtest/parse-ftp/U-hethmon.out
new file mode 100644
index 0000000000..dada168791
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-hethmon.out
@@ -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/. -->
+
+03-21-1999 00:00:00 <DIR> ..
+02-16-2002 00:00:00 <DIR> beta
+05-05-2000 00:00:00 35573 CheckQ2.zip
+05-05-2000 00:00:00 1550521 Ftpd106.zip
+02-07-2001 00:00:00 35459 hmailbox.zip
+05-05-2000 00:00:00 55463 HRxMail.zip
+05-05-2000 00:00:00 34950 HRxPass.zip
+05-05-2000 00:00:00 1320184 inetmail-1.2.1.pro.zip
+05-05-2000 00:00:00 1218483 inetmail-1.2.1.zip
+05-05-2000 00:00:00 1615428 inetmail-1.3.0.pro.zip
+05-05-2000 00:00:00 1425567 inetmail-1.3.0.zip
+01-09-2001 00:00:00 1629145 inetmail-1.3.18.zip
+02-07-2001 00:00:00 1625314 inetmail-1.3.18a.zip
+05-05-2000 00:00:00 1407069 inetmail-1.5.0.pro.zip
+09-27-2000 00:00:00 1470338 inetmail-1.5.31.pro.zip
+01-04-2001 00:00:00 1583435 inetmail-1.5.32.pro.zip
+05-05-2000 00:00:00 1429364 inetmail-1.5.5.pro.zip
+05-05-2000 00:00:00 1471373 inetmail-1.5.6.pro.zip
+02-08-2001 00:00:00 <DIR> misc
+05-05-2000 00:00:00 30913 MXLookup.zip
+05-02-2002 00:00:00 <DIR> perseus
+02-08-2001 00:00:00 <DIR> rfcs
+02-08-2001 00:00:00 <DIR> scripts
+02-08-2001 00:00:00 <DIR> tcpip
+05-05-2000 00:00:00 <DIR> testcase
+02-07-2001 00:00:00 <DIR> tools
diff --git a/netwerk/test/gtest/parse-ftp/U-murksw.in b/netwerk/test/gtest/parse-ftp/U-murksw.in
new file mode 100644
index 0000000000..0d5786cf2e
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-murksw.in
@@ -0,0 +1,29 @@
+ftp> open tui.lincoln.ac.nz
+Connected to tui.lincoln.ac.nz.
+220 tui.lincoln.ac.nz Novell NetWare FTP Server (V1.80), Copyright (C) 1992-2000 MurkWorks Inc.
+Name (tui.lincoln.ac.nz:cyp): 331 Anonymous Login OK, send id as password.
+230-User logged in, Max Connect time 600 minutes (READONLY)
+230 Current Directory : /
+Remote system type is UNIX.
+Using binary mode to transfer files.
+ftp> ls
+200 Command Accepted
+150 Opening connection for ASCII transfer
+-rwx---r-- 1 anonymou other 1445 Apr 28 1994 ANON.DAT
+-rwx---r-- 1 mcnaughp other 83 Apr 18 09:39 Copy of LABSPACE.SUM
+drwx---r-x 2 anonymou other 512 Jul 11 21:04 JRBUTILS
+-rwx---r-- 1 labspace other 91 Jul 13 02:04 LABSPACE.SUM
+drwx---r-x 2 anonymou other 512 Jul 11 21:04 MAINT
+drwx---r-x 2 anonymou other 512 Jul 11 21:04 MISC
+drwx---r-x 2 anonymou other 512 Jul 11 21:04 PCOUNTER
+drwx---r-x 2 helleupp other 512 Jul 11 21:05 PEGASUS
+-rwx---r-- 1 anonymou other 13662 Aug 6 17:14 VOL$LOG.ERR
+-rwx---r-- 1 anonymou other 27945 Sep 5 1994 WHATIS.MSC
+226 Transfer complete
+ftp> pwd
+257 "/" is the current directory
+ftp> syst
+215 UNIX Type: L8 Version: NetWare MurkWorks, Inc. 1.80
+ftp> close
+221 Goodbye.
+ftp> \ No newline at end of file
diff --git a/netwerk/test/gtest/parse-ftp/U-murksw.out b/netwerk/test/gtest/parse-ftp/U-murksw.out
new file mode 100644
index 0000000000..68ff9448e9
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-murksw.out
@@ -0,0 +1,14 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+04-28-1994 00:00:00 1445 ANON.DAT
+04-18-2002 09:39:00 83 Copy of LABSPACE.SUM
+07-11-2002 21:04:00 <DIR> JRBUTILS
+07-13-2002 02:04:00 91 LABSPACE.SUM
+07-11-2002 21:04:00 <DIR> MAINT
+07-11-2002 21:04:00 <DIR> MISC
+07-11-2002 21:04:00 <DIR> PCOUNTER
+07-11-2002 21:05:00 <DIR> PEGASUS
+08-06-2001 17:14:00 13662 VOL$LOG.ERR
+09-05-1994 00:00:00 27945 WHATIS.MSC
diff --git a/netwerk/test/gtest/parse-ftp/U-ncFTPd.in b/netwerk/test/gtest/parse-ftp/U-ncFTPd.in
new file mode 100644
index 0000000000..635f556423
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-ncFTPd.in
@@ -0,0 +1,1411 @@
+ftp> ftp ftp.novell.com
+Connected to kmpec.provo.novell.com.
+220 ftp.novell.com NcFTPd Server (licensed copy) ready.
+Name (ftp.novell.com:cyp):
+331 Guest login ok, send your complete e-mail address as password.
+230-You are user #18 of 400 simultaneous users allowed.
+230-
+230-This content is also available via HTTP at http://ftp.novell.com
+230-
+230-Other FTP mirror sites of ftp.novell.com are:
+230-ftp2.novell.com (United States)
+230-ftp3.novell.com (United States)
+230-ftp.novell.com.au (Australia)
+230-ftp.novell.de (Germany)
+230-ftp.novell.co.jp (Japan)
+230-ftp.novell.nl (Netherlands)
+230-
+230-World Wide Web Novell Support sites:
+230-http://support.novell.com (United States)
+230-http://support.novell.com.au (Australia)
+230-http://support.novell.de (Germany)
+230-http://support.novell.co.jp (Japan)
+230-
+230-webmaster@novell.com
+230-
+230 Logged in anonymously.
+Remote system type is UNIX.
+Using binary mode to transfer files.
+ftp> syst
+215 UNIX Type: L8
+ftp> ls
+200 PORT command successful.
+150 Opening ASCII mode data connection for /bin/ls.
+-rw-r--r-- 1 ftpuser ftpusers 2419 Jan 1 1997 Readme
+drwxrwxrwx 2 ftpuser ftpusers 8192 Jul 10 11:03 incoming
+drwxr-x--x 2 ftpuser ftpusers 4096 Jul 10 10:18 outgoing
+drwxr-xr-x 2 ftpuser ftpusers 4096 May 10 10:16 priv
+drwxr-xr-x 5 ftpuser ftpusers 4096 Jul 10 08:12 pub
+drwxr-xr-x 2 ftpuser ftpusers 28672 Jul 8 19:20 allupdates
+drwxr-xr-x 4 ftpuser ftpusers 4096 Jan 1 1997 netwire
+drwxr-xr-x 2 ftpuser ftpusers 4096 Jul 9 19:20 support
+lrwxrwxrwx 1 ftpuser ftpusers 10 Jan 10 2002 updates -> allupdates
+-rw-r--r-- 1 ftpuser ftpusers 19448 Jul 27 1999 0dsb.exe
+-rw-r--r-- 1 ftpuser ftpusers 49351 Sep 17 1993 215mir.exe
+-rw-r--r-- 1 ftpuser ftpusers 32586 Sep 17 1993 215sec.exe
+-rw-r--r-- 1 ftpuser ftpusers 136344 Sep 17 1993 21fix.exe
+-rw-r--r-- 1 ftpuser ftpusers 57276 Sep 17 1993 223200.exe
+-rw-r--r-- 1 ftpuser ftpusers 119616 Nov 1 1995 22bkup.exe
+-rw-r--r-- 1 ftpuser ftpusers 19820 Oct 13 1995 22dos5.exe
+-rw-r--r-- 1 ftpuser ftpusers 32980 Nov 1 1995 22nd2f.exe
+-rw-r--r-- 1 ftpuser ftpusers 92151 May 9 11:30 236779.exe
+-rw-r--r-- 1 ftpuser ftpusers 273958 Jan 17 2001 242234.exe
+-rw-r--r-- 1 ftpuser ftpusers 283462 Jul 10 2001 243798.exe
+-rw-r--r-- 1 ftpuser ftpusers 160893 Dec 18 2001 251000.exe
+-rw-r--r-- 1 ftpuser ftpusers 205531 Dec 18 2001 252143.exe
+-rw-r--r-- 1 ftpuser ftpusers 183262 Dec 18 2001 253720.exe
+-rw-r--r-- 1 ftpuser ftpusers 231638 Nov 6 2001 260624.exe
+-rw-r--r-- 1 ftpuser ftpusers 250545 Jun 21 2001 264837.exe
+-rw-r--r-- 1 ftpuser ftpusers 589722 Feb 21 16:33 265058a.exe
+-rw-r--r-- 1 ftpuser ftpusers 336586 Nov 7 2001 269308.exe
+-rw-r--r-- 1 ftpuser ftpusers 345275 Nov 6 2001 270050.exe
+-rw-r--r-- 1 ftpuser ftpusers 103027 Nov 6 2001 270410.exe
+-rw-r--r-- 1 ftpuser ftpusers 341460 May 9 07:13 275382.exe
+-rw-r--r-- 1 ftpuser ftpusers 365835 May 9 11:23 275436.exe
+-rw-r--r-- 1 ftpuser ftpusers 230063 Oct 9 2001 275520.exe
+-rw-r--r-- 1 ftpuser ftpusers 285953 Oct 2 2001 275820.exe
+-rw-r--r-- 1 ftpuser ftpusers 156517 Nov 16 2001 277412.exe
+-rw-r--r-- 1 ftpuser ftpusers 839272 Dec 19 2001 278415.exe
+-rw-r--r-- 1 ftpuser ftpusers 130983 Dec 19 2001 282848.exe
+-rw-r--r-- 1 ftpuser ftpusers 61891 Sep 17 1993 286dwn.exe
+-rw-r--r-- 1 ftpuser ftpusers 136529 May 9 07:18 290254.exe
+-rw-r--r-- 1 ftpuser ftpusers 96694 Apr 12 17:07 290261.exe
+-rw-r--r-- 1 ftpuser ftpusers 230377 May 9 07:22 291158.exe
+-rw-r--r-- 1 ftpuser ftpusers 114329 Jun 27 12:20 295137.exe
+-rw-r--r-- 1 ftpuser ftpusers 84885 Sep 17 1993 2xto3x.exe
+-rw-r--r-- 1 ftpuser ftpusers 60861 Sep 17 1993 300pt1.exe
+-rw-r--r-- 1 ftpuser ftpusers 180279 Sep 17 1993 310pt1.exe
+-rw-r--r-- 1 ftpuser ftpusers 178076 Nov 11 1996 311ptg.exe
+-rw-r--r-- 1 ftpuser ftpusers 188572 Apr 29 1996 312du1.exe
+-rw-r--r-- 1 ftpuser ftpusers 168258 Mar 18 1998 312ptd.exe
+-rw-r--r-- 1 ftpuser ftpusers 436629 Aug 25 1999 312y2kp2.exe
+-rw-r--r-- 1 ftpuser ftpusers 2365264 Jun 29 2001 33sp3.exe
+-rw-r--r-- 1 ftpuser ftpusers 28881 Sep 17 1993 386dcb.exe
+-rw-r--r-- 1 ftpuser ftpusers 33111 Sep 17 1993 3cboot.exe
+-rw-r--r-- 1 ftpuser ftpusers 27583 Nov 10 1995 400pt1.exe
+-rw-r--r-- 1 ftpuser ftpusers 79295 Oct 23 1995 401pt6.exe
+-rw-r--r-- 1 ftpuser ftpusers 42873 Aug 26 1994 402pa1.exe
+-rw-r--r-- 1 ftpuser ftpusers 50737 Mar 28 1995 402pt1.exe
+-rw-r--r-- 1 ftpuser ftpusers 548398 Jun 8 1998 410pt8b.exe
+-rw-r--r-- 1 ftpuser ftpusers 285471 Aug 25 1999 410y2kp2.exe
+-rw-r--r-- 1 ftpuser ftpusers 538544 Jul 23 1999 411y2kp2.exe
+-rw-r--r-- 1 ftpuser ftpusers 370469 Apr 29 1996 41filr.exe
+-rw-r--r-- 1 ftpuser ftpusers 58965 Jan 2 1997 41migutl.exe
+-rw-r--r-- 1 ftpuser ftpusers 173978 Aug 29 1995 41ndir.exe
+-rw-r--r-- 1 ftpuser ftpusers 42101 Nov 24 1997 41rem1.exe
+-rw-r--r-- 1 ftpuser ftpusers 855292 Jul 9 1996 41rtr2.exe
+-rw-r--r-- 1 ftpuser ftpusers 212785 Jul 23 1999 42y2kp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 3043701 Jul 3 2001 48sp3.exe
+-rw-r--r-- 1 ftpuser ftpusers 308856 May 22 2001 4pent.exe
+-rw-r--r-- 1 ftpuser ftpusers 1072023 Apr 6 1998 4xmigr2.exe
+-rw-r--r-- 1 ftpuser ftpusers 125457 Nov 17 1995 4xrep1.exe
+-rw-r--r-- 1 ftpuser ftpusers 29001 Jul 6 1994 50z70.exe
+-rw-r--r-- 1 ftpuser ftpusers 23248 Nov 1 1995 55sx60.exe
+-rw-r--r-- 1 ftpuser ftpusers 647818 Dec 4 1998 95220p1.exe
+-rw-r--r-- 1 ftpuser ftpusers 905243 Mar 25 1999 95250p2.exe
+-rw-r--r-- 1 ftpuser ftpusers 694344 Jun 21 1999 9530p1.exe
+-rw-r--r-- 1 ftpuser ftpusers 794454 Jun 13 2000 9531pt2.exe
+-rw-r--r-- 1 ftpuser ftpusers 4380134 Oct 3 1999 9531sp2.exe
+-rw-r--r-- 1 ftpuser ftpusers 1306298 Nov 16 2000 95321pt5.exe
+-rw-r--r-- 1 ftpuser ftpusers 494394 Jun 13 2000 9532pt3.exe
+-rw-r--r-- 1 ftpuser ftpusers 483405 Nov 6 2001 95331pt1.exe
+-rw-r--r-- 1 ftpuser ftpusers 19588899 Jun 2 2000 a526aix.z
+-rw-r--r-- 1 ftpuser ftpusers 17196628 Jun 2 2000 a526hp.z
+-rw-r--r-- 1 ftpuser ftpusers 14813432 Jun 2 2000 a526sol.z
+-rw-r--r-- 1 ftpuser ftpusers 238005 Jul 9 1999 accupg1b.exe
+-rw-r--r-- 1 ftpuser ftpusers 19472 Sep 17 1993 aceclp.exe
+-rw-r--r-- 1 ftpuser ftpusers 2061142 Jun 19 2001 acmgtcl.exe
+-rw-r--r-- 1 ftpuser ftpusers 52792 Dec 17 1996 actkey.exe
+-rw-r--r-- 1 ftpuser ftpusers 1026928 Nov 8 1996 adde1.exe
+-rw-r--r-- 1 ftpuser ftpusers 476094 Sep 10 1997 adm32_22.exe
+-rw-r--r-- 1 ftpuser ftpusers 4204451 Jan 10 1997 adm4113x.exe
+-rw-r--r-- 1 ftpuser ftpusers 3813257 Jan 10 1997 adm41195.exe
+-rw-r--r-- 1 ftpuser ftpusers 5377426 Jan 10 1997 adm411nt.exe
+-rw-r--r-- 1 ftpuser ftpusers 130826 May 23 2001 admattrs.exe
+-rw-r--r-- 1 ftpuser ftpusers 3291925 May 23 2001 admn519f.exe
+-rw-r--r-- 1 ftpuser ftpusers 273404 Jul 15 1997 adtus3.exe
+-rw-r--r-- 1 ftpuser ftpusers 25475 Sep 17 1993 afix1.exe
+-rw-r--r-- 1 ftpuser ftpusers 25929 Sep 17 1993 afix2.exe
+-rw-r--r-- 1 ftpuser ftpusers 26184 Sep 17 1993 afix4.exe
+-rw-r--r-- 1 ftpuser ftpusers 116034 Apr 10 2001 afnwcgi1.exe
+-rw-r--r-- 1 ftpuser ftpusers 175949 Jan 28 13:43 afp100j.exe
+-rw-r--r-- 1 ftpuser ftpusers 90415 Jul 12 1999 afp11.exe
+-rw-r--r-- 1 ftpuser ftpusers 513677 Nov 1 1995 aiodrv.exe
+-rw-r--r-- 1 ftpuser ftpusers 26752 Aug 12 1995 aiotrm.exe
+-rw-r--r-- 1 ftpuser ftpusers 113328 Feb 8 2000 alx311cm.exe
+-rw-r--r-- 1 ftpuser ftpusers 202594 Dec 11 2001 am210pt2.exe
+-rw-r--r-- 1 ftpuser ftpusers 1126900 Dec 10 2001 am210snp.exe
+-rw-r--r-- 1 ftpuser ftpusers 267218 Dec 11 2001 amsammg.exe
+-rw-r--r-- 1 ftpuser ftpusers 4394406 Nov 29 2001 amw2ksp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 34959 Nov 6 1996 anlmde.exe
+-rw-r--r-- 1 ftpuser ftpusers 70315 Nov 20 1996 antde.exe
+-rw-r--r-- 1 ftpuser ftpusers 2778036 Dec 10 1997 apinlm.exe
+-rw-r--r-- 1 ftpuser ftpusers 47772 Apr 5 1994 apinst.exe
+-rw-r--r-- 1 ftpuser ftpusers 3367826 Dec 10 1997 apios2.exe
+-rw-r--r-- 1 ftpuser ftpusers 44662 Sep 17 1993 apsvap.exe
+-rw-r--r-- 1 ftpuser ftpusers 1507519 Oct 5 1998 async1.exe
+-rw-r--r-- 1 ftpuser ftpusers 431852 Jul 25 1995 asyq4a.exe
+-rw-r--r-- 1 ftpuser ftpusers 68700 Sep 17 1993 atalk.exe
+-rw-r--r-- 1 ftpuser ftpusers 22944 Sep 17 1993 atdisk.exe
+-rw-r--r-- 1 ftpuser ftpusers 222222 Jan 29 1996 atk307.exe
+-rw-r--r-- 1 ftpuser ftpusers 259359 Sep 24 1999 atmdrv04.exe
+-rw-r--r-- 1 ftpuser ftpusers 22012 Jun 14 1996 atok31.exe
+-rw-r--r-- 1 ftpuser ftpusers 25152 Sep 17 1993 atps1.exe
+-rw-r--r-- 1 ftpuser ftpusers 25812 Sep 17 1993 atps2.exe
+-rw-r--r-- 1 ftpuser ftpusers 206226 Sep 17 1993 atsup.exe
+-rw-r--r-- 1 ftpuser ftpusers 249262 Sep 15 1997 audit410.exe
+-rw-r--r-- 1 ftpuser ftpusers 27058 Jul 25 1995 autoda.exe
+-rw-r--r-- 1 ftpuser ftpusers 53955568 Mar 28 2001 azfd3sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 1267891 Mar 30 2000 bacl105.exe
+-rw-r--r-- 1 ftpuser ftpusers 564407 Apr 24 1998 bm21y2k.exe
+-rw-r--r-- 1 ftpuser ftpusers 64354 Dec 9 1999 bm2ncs2.exe
+-rw-r--r-- 1 ftpuser ftpusers 5025837 Nov 13 2000 bm30sp3.exe
+-rw-r--r-- 1 ftpuser ftpusers 118425 Mar 27 10:48 bm35adm6.exe
+-rw-r--r-- 1 ftpuser ftpusers 8950758 Sep 25 2001 bm35sp3.exe
+-rw-r--r-- 1 ftpuser ftpusers 1560521 May 31 15:43 bm36c02.exe
+-rw-r--r-- 1 ftpuser ftpusers 1995053 Nov 6 2001 bm36nsp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 4187534 Jan 30 13:44 bm36sp1a.exe
+-rw-r--r-- 1 ftpuser ftpusers 146884 Apr 19 13:52 bm37flt.exe
+-rw-r--r-- 1 ftpuser ftpusers 3958423 Apr 19 13:54 bm37vpn1.exe
+-rw-r--r-- 1 ftpuser ftpusers 2192048 Jun 16 2000 bm3cp3.exe
+-rw-r--r-- 1 ftpuser ftpusers 521873 Mar 27 2000 bm3licfx.exe
+-rw-r--r-- 1 ftpuser ftpusers 550905 Jan 21 1999 bm3netad.exe
+-rw-r--r-- 1 ftpuser ftpusers 28435 Oct 8 1999 bm3pcfg.exe
+-rw-r--r-- 1 ftpuser ftpusers 30429 Oct 20 1999 bm3rmv3.exe
+-rw-r--r-- 1 ftpuser ftpusers 4865777 Sep 2 1999 bm3sp2.exe
+-rw-r--r-- 1 ftpuser ftpusers 3934644 Jan 11 2000 bm3sp2j.exe
+-rw-r--r-- 1 ftpuser ftpusers 105579 Jan 16 2001 bm3sso2.exe
+-rw-r--r-- 1 ftpuser ftpusers 114564 Nov 23 1998 bmas203.exe
+-rw-r--r-- 1 ftpuser ftpusers 185953 Jul 27 2000 bmas204.exe
+-rw-r--r-- 1 ftpuser ftpusers 227934 May 29 2001 bmas3x01.exe
+-rw-r--r-- 1 ftpuser ftpusers 2409166 Apr 14 1998 bmnwntb.exe
+-rw-r--r-- 1 ftpuser ftpusers 235512 Oct 7 1999 bmp114.exe
+-rw-r--r-- 1 ftpuser ftpusers 114828 Aug 25 2000 bmsamp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 7822377 Feb 5 2001 bmsp2d.exe
+-rw-r--r-- 1 ftpuser ftpusers 177276 May 9 2001 bmvpn3y.exe
+-rw-r--r-- 1 ftpuser ftpusers 57666 Jul 16 1997 bndfx4.exe
+-rw-r--r-- 1 ftpuser ftpusers 22178 Sep 26 1997 brdrem1.exe
+-rw-r--r-- 1 ftpuser ftpusers 140225 Sep 17 1993 brgcom.exe
+-rw-r--r-- 1 ftpuser ftpusers 64130 Mar 17 1998 bwcc.exe
+-rw-r--r-- 1 ftpuser ftpusers 106576 May 10 2000 c112brj.exe
+-rw-r--r-- 1 ftpuser ftpusers 107820 May 9 2000 c112crcj.exe
+-rw-r--r-- 1 ftpuser ftpusers 106834 May 10 2000 c112crj.exe
+-rw-r--r-- 1 ftpuser ftpusers 35687507 Mar 5 13:14 c1_132.exe
+-rw-r--r-- 1 ftpuser ftpusers 188379 Feb 27 2001 c1unx85a.exe
+-rw-r--r-- 1 ftpuser ftpusers 24246977 Jun 2 2000 c526aix.z
+-rw-r--r-- 1 ftpuser ftpusers 39823205 Jun 2 2000 c526hp.z
+-rw-r--r-- 1 ftpuser ftpusers 31630789 Jun 2 2000 c526sol.z
+-rw-r--r-- 1 ftpuser ftpusers 39967 Dec 4 1996 ccmdll.exe
+-rw-r--r-- 1 ftpuser ftpusers 3532557 Jun 21 1999 ccmln1.exe
+-rw-r--r-- 1 ftpuser ftpusers 3610648 Apr 21 2000 ccmln2.exe
+-rw-r--r-- 1 ftpuser ftpusers 7242383 Oct 30 1998 ccmlo1.exe
+-rw-r--r-- 1 ftpuser ftpusers 71189 Mar 6 1997 cdisc.exe
+-rw-r--r-- 1 ftpuser ftpusers 298109 Aug 19 1998 cdup5a.exe
+-rw-r--r-- 1 ftpuser ftpusers 1554357 Oct 14 1998 cfgrd6b.exe
+-rw-r--r-- 1 ftpuser ftpusers 30799 Sep 17 1993 chk375.exe
+-rw-r--r-- 1 ftpuser ftpusers 85538 Jun 16 1997 chtree1.exe
+-rw-r--r-- 1 ftpuser ftpusers 28171 Dec 8 1999 clibaux1.exe
+-rw-r--r-- 1 ftpuser ftpusers 2819 Sep 8 2000 clients.txt
+-rw-r--r-- 1 ftpuser ftpusers 16820824 Sep 11 2000 clntaot.exe
+-rw-r--r-- 1 ftpuser ftpusers 7269162 Jan 5 1999 clos2d1.exe
+-rw-r--r-- 1 ftpuser ftpusers 2960384 Jun 24 1996 clt511.bin
+-rw-r--r-- 1 ftpuser ftpusers 195429 Mar 24 1999 clty2kp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 76117 Nov 22 1993 comchk.exe
+-rw-r--r-- 1 ftpuser ftpusers 76738 Nov 1 1995 comsub.exe
+-rw-r--r-- 1 ftpuser ftpusers 74759 Aug 16 1996 comx.exe
+-rw-r--r-- 1 ftpuser ftpusers 102496 Feb 22 2001 comx218.exe
+-rw-r--r-- 1 ftpuser ftpusers 117584 Dec 5 2000 confg9.exe
+-rw-r--r-- 1 ftpuser ftpusers 27438 Sep 17 1993 conlog.exe
+-rw-r--r-- 1 ftpuser ftpusers 20051 Sep 17 1993 cpuchk.exe
+-rw-r--r-- 1 ftpuser ftpusers 96755 Jan 7 2000 cron5.exe
+-rw-r--r-- 1 ftpuser ftpusers 22439431 Jul 21 2001 cs1sp2.exe
+-rw-r--r-- 1 ftpuser ftpusers 12643202 Feb 19 16:08 cs1sp3.exe
+-rw-r--r-- 1 ftpuser ftpusers 2395980 Feb 28 2001 cs2ep2.exe
+-rw-r--r-- 1 ftpuser ftpusers 97982 Feb 17 2000 csatpxy2.exe
+-rw-r--r-- 1 ftpuser ftpusers 888262 Jan 30 1997 csserv.exe
+-rw-r--r-- 1 ftpuser ftpusers 888092 Jan 30 1997 ctserv.exe
+-rw-r--r-- 1 ftpuser ftpusers 1195718 Sep 20 1999 ctsv20_1.pdf
+-rw-r--r-- 1 ftpuser ftpusers 97050 Aug 30 2000 cvsbind.exe
+-rw-r--r-- 1 ftpuser ftpusers 1040603 Jan 5 1996 d70f15.exe
+-rw-r--r-- 1 ftpuser ftpusers 1046550 Jan 5 1996 d70g15.exe
+-rw-r--r-- 1 ftpuser ftpusers 1041138 Jan 5 1996 d70i15.exe
+-rw-r--r-- 1 ftpuser ftpusers 1042404 Jan 5 1996 d70s15.exe
+-rw-r--r-- 1 ftpuser ftpusers 1031373 Jan 5 1996 d70u15.exe
+-rw-r--r-- 1 ftpuser ftpusers 309903 Oct 31 1995 d72pnw.exe
+-rw-r--r-- 1 ftpuser ftpusers 26322 Aug 17 1995 daishm.exe
+-rw-r--r-- 1 ftpuser ftpusers 92347 Jan 17 09:55 decrenfx.exe
+-rw-r--r-- 1 ftpuser ftpusers 32573 Nov 24 1997 devmon.exe
+-rw-r--r-- 1 ftpuser ftpusers 225697 Nov 23 1999 dhcp21r.exe
+-rw-r--r-- 1 ftpuser ftpusers 65667 Jul 27 1994 diag.exe
+-rw-r--r-- 1 ftpuser ftpusers 776679 Jul 3 1996 dialr1.exe
+-rw-r--r-- 1 ftpuser ftpusers 1307891 Jul 3 1996 dialr2.exe
+-rw-r--r-- 1 ftpuser ftpusers 1330220 Jul 3 1996 dialr3.exe
+-rw-r--r-- 1 ftpuser ftpusers 62202 Sep 17 1993 distst.exe
+-rw-r--r-- 1 ftpuser ftpusers 133345 Feb 8 2001 dlttape.exe
+-rw-r--r-- 1 ftpuser ftpusers 174566 Dec 13 1995 dr6tid.exe
+-rw-r--r-- 1 ftpuser ftpusers 188110 May 15 15:23 dradpt1a.exe
+-rw-r--r-- 1 ftpuser ftpusers 12654872 Apr 11 10:37 drdt10.exe
+-rw-r--r-- 1 ftpuser ftpusers 7991951 Apr 11 10:50 drnt10.exe
+-rw-r--r-- 1 ftpuser ftpusers 259966 Dec 9 1993 drv2x.exe
+-rw-r--r-- 1 ftpuser ftpusers 366568 Sep 17 1993 drvkit.exe
+-rw-r--r-- 1 ftpuser ftpusers 28773 Dec 2 1998 drvspc.exe
+-rw-r--r-- 1 ftpuser ftpusers 242836 Jul 12 1994 ds310.exe
+-rw-r--r-- 1 ftpuser ftpusers 739008 Dec 13 1999 ds410q.exe
+-rw-r--r-- 1 ftpuser ftpusers 683746 Jun 11 18:01 ds616.exe
+-rw-r--r-- 1 ftpuser ftpusers 1237908 Jul 3 19:58 ds760a.exe
+-rw-r--r-- 1 ftpuser ftpusers 3084809 Jun 12 13:47 ds880d_a.exe
+-rw-r--r-- 1 ftpuser ftpusers 2366484 Jun 7 2000 ds8c.exe
+-rw-r--r-- 1 ftpuser ftpusers 300572 May 31 2000 dsbrowse.exe
+-rw-r--r-- 1 ftpuser ftpusers 425220 Nov 10 1998 dsdiag1.exe
+-rw-r--r-- 1 ftpuser ftpusers 766525 Apr 15 1998 dskdrv.exe
+-rw-r--r-- 1 ftpuser ftpusers 201308 May 2 1997 dsright2.exe
+-rw-r--r-- 1 ftpuser ftpusers 6620 Feb 4 14:26 dsrmenu5.tgz
+-rw-r--r-- 1 ftpuser ftpusers 32671 Jan 11 12:06 dsx86upg.tgz
+-rw-r--r-- 1 ftpuser ftpusers 438327 Nov 22 1999 duprid.exe
+-rw-r--r-- 1 ftpuser ftpusers 366231 Nov 30 1999 dw271i1.exe
+-rw-r--r-- 1 ftpuser ftpusers 6444327 Oct 31 2001 dx1patch.exe
+-rw-r--r-- 1 ftpuser ftpusers 764705 Dec 11 2001 dxjdbc1a.exe
+-rw-r--r-- 1 ftpuser ftpusers 369669 Jul 31 2001 dxnotp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 325977 Jan 11 17:32 dxntp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 6446882 Jan 11 17:11 dxnwp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 27292445 Jan 15 18:08 dxx10a.tgz
+-rw-r--r-- 1 ftpuser ftpusers 420838 Sep 18 1993 e2isa.exe
+-rw-r--r-- 1 ftpuser ftpusers 299227554 May 15 09:35 edir851.exe
+-rw-r--r-- 1 ftpuser ftpusers 13752049 Jun 19 22:20 edir8527.exe
+-rw-r--r-- 1 ftpuser ftpusers 8909701 Jun 18 17:22 edir8527.tgz
+-rw-r--r-- 1 ftpuser ftpusers 35256093 May 23 12:35 edir862.exe
+-rw-r--r-- 1 ftpuser ftpusers 30290896 Mar 7 13:22 edir862.tgz
+-rw-r--r-- 1 ftpuser ftpusers 12405810 Jun 20 11:12 edir862sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 7623982 Jun 20 11:07 edir862sp1a.tgz
+-rw-r--r-- 1 ftpuser ftpusers 178010922 Jun 19 23:26 edirw8527.exe
+-rw-r--r-- 1 ftpuser ftpusers 739976 Aug 1 1995 edvwin.exe
+-rw-r--r-- 1 ftpuser ftpusers 270433 Apr 12 15:32 egdpvr02.exe
+-rw-r--r-- 1 ftpuser ftpusers 6660863 Jan 15 10:28 einstall.exe
+-rw-r--r-- 1 ftpuser ftpusers 19547 Sep 18 1993 els2dt.exe
+-rw-r--r-- 1 ftpuser ftpusers 46872 Sep 18 1993 elsupd.exe
+-rw-r--r-- 1 ftpuser ftpusers 44481 Sep 18 1993 emshdl.exe
+-rw-r--r-- 1 ftpuser ftpusers 171889 Nov 30 2001 es7000.exe
+-rw-r--r-- 1 ftpuser ftpusers 395219 Sep 18 1993 escsi.exe
+-rw-r--r-- 1 ftpuser ftpusers 50164 Nov 1 1995 esdidr.exe
+-rw-r--r-- 1 ftpuser ftpusers 22207 Sep 18 1993 esdifx.exe
+-rw-r--r-- 1 ftpuser ftpusers 162763 Apr 16 2001 etbox7.exe
+-rw-r--r-- 1 ftpuser ftpusers 28262 Sep 18 1993 ethrlk.exe
+-rw-r--r-- 1 ftpuser ftpusers 21776 Sep 18 1993 ethsh.exe
+-rw-r--r-- 1 ftpuser ftpusers 3111638 Oct 13 1999 exchnt2.exe
+-rw-r--r-- 1 ftpuser ftpusers 32615 Mar 20 1995 exprul.exe
+-rw-r--r-- 1 ftpuser ftpusers 19910 Jul 18 1995 facwin.exe
+-rw-r--r-- 1 ftpuser ftpusers 193689 Sep 18 1993 faxdoc.exe
+-rw-r--r-- 1 ftpuser ftpusers 103299 Jul 18 1995 faxsv.exe
+-rw-r--r-- 1 ftpuser ftpusers 566087 Jul 20 1998 fbm21y2k.exe
+-rw-r--r-- 1 ftpuser ftpusers 263164 Jul 27 2000 ffwnt1a.exe
+-rw-r--r-- 1 ftpuser ftpusers 9564485 Jun 5 2000 fgwep1ad.exe
+-rw-r--r-- 1 ftpuser ftpusers 158446 Nov 10 1993 fil376.exe
+-rw-r--r-- 1 ftpuser ftpusers 35376 Sep 18 1993 filehd.exe
+-rw-r--r-- 1 ftpuser ftpusers 64924 Jan 28 1999 filt01a.exe
+-rw-r--r-- 1 ftpuser ftpusers 38027 Sep 18 1993 flgdir.exe
+-rw-r--r-- 1 ftpuser ftpusers 891667 Jun 25 10:26 flsysft7.exe
+-rw-r--r-- 1 ftpuser ftpusers 210958 Jul 20 1994 flx196.exe
+-rw-r--r-- 1 ftpuser ftpusers 23948 Sep 18 1993 fmtps2.exe
+-rw-r--r-- 1 ftpuser ftpusers 265642 Jun 1 1995 fmwin1.exe
+-rw-r--r-- 1 ftpuser ftpusers 44110 Jun 18 1997 fnasi21b.exe
+-rw-r--r-- 1 ftpuser ftpusers 35133 Apr 29 1997 fnwcrns.exe
+-rw-r--r-- 1 ftpuser ftpusers 64944 Jun 18 1997 fnwcss.exe
+-rw-r--r-- 1 ftpuser ftpusers 38169 Jun 18 1997 fnwcx25.exe
+-rw-r--r-- 1 ftpuser ftpusers 18985686 Feb 26 2001 fp3023a.exe
+-rw-r--r-- 1 ftpuser ftpusers 19034903 Feb 26 2001 fp3023s.exe
+-rw-r--r-- 1 ftpuser ftpusers 41401158 Jun 22 2001 fpa353.exe
+-rw-r--r-- 1 ftpuser ftpusers 40072174 Jun 22 2001 fps353.exe
+-rw-r--r-- 1 ftpuser ftpusers 40680 Aug 2 1996 frebld.exe
+-rw-r--r-- 1 ftpuser ftpusers 156117 Jan 30 15:36 ftpservk.exe
+-rw-r--r-- 1 ftpuser ftpusers 1427341 Jun 20 2001 fzd2abnd.exe
+-rw-r--r-- 1 ftpuser ftpusers 121436 May 22 2001 fzd2nal.exe
+-rw-r--r-- 1 ftpuser ftpusers 293122 May 22 2001 fzd2nal1.exe
+-rw-r--r-- 1 ftpuser ftpusers 333581 Dec 18 2001 fzd2nal2.exe
+-rw-r--r-- 1 ftpuser ftpusers 250445 May 23 2001 fzd2zapp.exe
+-rw-r--r-- 1 ftpuser ftpusers 2619032 Oct 10 2001 g32ep3b.exe
+-rw-r--r-- 1 ftpuser ftpusers 26193920 Nov 23 1998 g41c5a41.tar
+-rw-r--r-- 1 ftpuser ftpusers 25907200 Nov 23 1998 g41c5a43.tar
+-rw-r--r-- 1 ftpuser ftpusers 25190400 Nov 23 1998 g41c5dg.tar
+-rw-r--r-- 1 ftpuser ftpusers 27494400 Nov 23 1998 g41c5hp.tar
+-rw-r--r-- 1 ftpuser ftpusers 29337600 Nov 23 1998 g41c5ncr.tar
+-rw-r--r-- 1 ftpuser ftpusers 27146240 Nov 23 1998 g41c5sc5.tar
+-rw-r--r-- 1 ftpuser ftpusers 26982400 Nov 23 1998 g41c5sl5.tar
+-rw-r--r-- 1 ftpuser ftpusers 26961920 Nov 23 1998 g41c5sl6.tar
+-rw-r--r-- 1 ftpuser ftpusers 32808960 Nov 23 1998 g41c5sun.tar
+-rw-r--r-- 1 ftpuser ftpusers 3614720 Nov 23 1998 g41m5a41.tar
+-rw-r--r-- 1 ftpuser ftpusers 3604480 Nov 23 1998 g41m5a43.tar
+-rw-r--r-- 1 ftpuser ftpusers 3481600 Nov 23 1998 g41m5dg.tar
+-rw-r--r-- 1 ftpuser ftpusers 4280320 Nov 23 1998 g41m5hp.tar
+-rw-r--r-- 1 ftpuser ftpusers 4495360 Nov 23 1998 g41m5ncr.tar
+-rw-r--r-- 1 ftpuser ftpusers 5263360 Nov 23 1998 g41m5sc5.tar
+-rw-r--r-- 1 ftpuser ftpusers 4116480 Nov 23 1998 g41m5sl5.tar
+-rw-r--r-- 1 ftpuser ftpusers 4106240 Nov 23 1998 g41m5sl6.tar
+-rw-r--r-- 1 ftpuser ftpusers 5939200 Nov 20 1998 g41m5sun.tar
+-rw-r--r-- 1 ftpuser ftpusers 93137892 Jun 2 2000 g526east.exe
+-rw-r--r-- 1 ftpuser ftpusers 84011178 Jun 2 2000 g526kcc.exe
+-rw-r--r-- 1 ftpuser ftpusers 106015153 Jun 2 2000 g526mult.exe
+-rw-r--r-- 1 ftpuser ftpusers 92474943 Jun 2 2000 g526scan.exe
+-rw-r--r-- 1 ftpuser ftpusers 74449657 Jun 2 2000 g526us.exe
+-rw-r--r-- 1 ftpuser ftpusers 80161031 Jun 2 2000 g526usde.exe
+-rw-r--r-- 1 ftpuser ftpusers 77052636 Jun 2 2000 g526usjp.exe
+-rw-r--r-- 1 ftpuser ftpusers 20712319 Dec 18 1997 g52a1aix.z
+-rw-r--r-- 1 ftpuser ftpusers 17069171 Dec 18 1997 g52a1hp.z
+-rw-r--r-- 1 ftpuser ftpusers 14703356 Dec 18 1997 g52a1sol.z
+-rw-r--r-- 1 ftpuser ftpusers 7595802 Jun 2 2000 g554ar.exe
+-rw-r--r-- 1 ftpuser ftpusers 39304570 Jun 2 2000 g554en.exe
+-rw-r--r-- 1 ftpuser ftpusers 13651283 Jun 2 2000 g554est.exe
+-rw-r--r-- 1 ftpuser ftpusers 7587459 Jun 2 2000 g554he.exe
+-rw-r--r-- 1 ftpuser ftpusers 23317212 Jun 2 2000 g554jp.exe
+-rw-r--r-- 1 ftpuser ftpusers 22871590 Jun 2 2000 g554kcc.exe
+-rw-r--r-- 1 ftpuser ftpusers 30379240 Jun 2 2000 g554mlt.exe
+-rw-r--r-- 1 ftpuser ftpusers 30469595 Jun 2 2000 g554scn.exe
+-rw-r--r-- 1 ftpuser ftpusers 3252251 Aug 31 2000 g5notmb1.exe
+-rw-r--r-- 1 ftpuser ftpusers 1983381 Aug 8 1995 gam10.exe
+-rw-r--r-- 1 ftpuser ftpusers 565411 Jul 20 1998 gbm21y2k.exe
+-rw-r--r-- 1 ftpuser ftpusers 547498 Nov 19 1996 gdsmtp.exe
+-rw-r--r-- 1 ftpuser ftpusers 26760 Oct 21 1996 gdusc1.exe
+-rw-r--r-- 1 ftpuser ftpusers 32876 Sep 18 1993 genbio.exe
+-rw-r--r-- 1 ftpuser ftpusers 4589470 Oct 3 2001 gep3beng.exe
+-rw-r--r-- 1 ftpuser ftpusers 12570112 Aug 16 1999 gm527aen.bin
+-rw-r--r-- 1 ftpuser ftpusers 12556928 Jun 14 2000 gm528aen.bin
+-rw-r--r-- 1 ftpuser ftpusers 12558720 Jun 19 2000 gm528ben.bin
+-rw-r--r-- 1 ftpuser ftpusers 6081664 Dec 21 1996 gmcec3.hqx
+-rw-r--r-- 1 ftpuser ftpusers 6086656 Dec 21 1996 gmcfc3.hqx
+-rw-r--r-- 1 ftpuser ftpusers 6089856 Dec 24 1996 gmesc3.hqx
+-rw-r--r-- 1 ftpuser ftpusers 6087552 Dec 20 1996 gmfrc3.hqx
+-rw-r--r-- 1 ftpuser ftpusers 6084480 Dec 24 1996 gmitc3.hqx
+-rw-r--r-- 1 ftpuser ftpusers 6079872 Dec 19 1996 gmozc3.hqx
+-rw-r--r-- 1 ftpuser ftpusers 6081792 Dec 19 1996 gmukc3.hqx
+-rw-r--r-- 1 ftpuser ftpusers 6079872 Dec 11 1996 gmusc3.hqx
+-rw-r--r-- 1 ftpuser ftpusers 26557 Sep 18 1993 gpierr.exe
+-rw-r--r-- 1 ftpuser ftpusers 189508 Aug 28 1996 gpwrdk.exe
+-rw-r--r-- 1 ftpuser ftpusers 114332 Aug 28 1996 gpwrnl.exe
+-rw-r--r-- 1 ftpuser ftpusers 99491 Sep 3 1996 gpwrru.exe
+-rw-r--r-- 1 ftpuser ftpusers 131835 Aug 28 1996 gpwrsu.exe
+-rw-r--r-- 1 ftpuser ftpusers 170183 Aug 22 1996 gpwrsv.exe
+-rw-r--r-- 1 ftpuser ftpusers 74740 Feb 25 1998 gpwsbr.exe
+-rw-r--r-- 1 ftpuser ftpusers 44964 Feb 25 1998 gpwsde.exe
+-rw-r--r-- 1 ftpuser ftpusers 40264 Feb 25 1998 gpwsfr.exe
+-rw-r--r-- 1 ftpuser ftpusers 39224 Feb 25 1998 gpwsit.exe
+-rw-r--r-- 1 ftpuser ftpusers 47842 Jul 22 1999 groupfix.exe
+-rw-r--r-- 1 ftpuser ftpusers 207875 Oct 19 1995 gsc41.exe
+-rw-r--r-- 1 ftpuser ftpusers 811983 Dec 15 1997 gsc51.exe
+-rw-r--r-- 1 ftpuser ftpusers 9805824 Jun 8 1995 gusmtp.tar
+-rw-r--r-- 1 ftpuser ftpusers 4658880 Jul 28 1999 gw41api.exe
+-rw-r--r-- 1 ftpuser ftpusers 1307103 Nov 17 1999 gw41api2.exe
+-rw-r--r-- 1 ftpuser ftpusers 30983798 Jul 29 1999 gw41aus.exe
+-rw-r--r-- 1 ftpuser ftpusers 86653 Feb 28 1996 gw41qa.exe
+-rw-r--r-- 1 ftpuser ftpusers 20978 Dec 10 1998 gw41sp5.txt
+-rw-r--r-- 1 ftpuser ftpusers 18251583 Jan 8 1998 gw51sp2a.exe
+-rw-r--r-- 1 ftpuser ftpusers 3483616 Mar 10 1998 gw51w16a.exe
+-rw-r--r-- 1 ftpuser ftpusers 1931948 Jan 9 2001 gw52ch6.exe
+-rw-r--r-- 1 ftpuser ftpusers 479758 Jun 26 2000 gw52sp6.exe
+-rw-r--r-- 1 ftpuser ftpusers 35836761 Feb 28 16:19 gw555cck.exe
+-rw-r--r-- 1 ftpuser ftpusers 26614908 Feb 28 16:34 gw555ee.exe
+-rw-r--r-- 1 ftpuser ftpusers 372421 May 31 2000 gw55bk.exe
+-rw-r--r-- 1 ftpuser ftpusers 109887 Feb 3 2000 gw55bp.exe
+-rw-r--r-- 1 ftpuser ftpusers 847939 Jun 19 2000 gw55dutl.exe
+-rw-r--r-- 1 ftpuser ftpusers 58548675 Aug 14 2001 gw55ep3a.exe
+-rw-r--r-- 1 ftpuser ftpusers 2497895 Jun 25 1999 gw55inst.exe
+-rw-r--r-- 1 ftpuser ftpusers 3742457 Jun 27 2000 gw55ol2.exe
+-rw-r--r-- 1 ftpuser ftpusers 219963 Apr 18 2001 gw55puma.exe
+-rw-r--r-- 1 ftpuser ftpusers 19376 Mar 1 1999 gw55rc.exe
+-rw-r--r-- 1 ftpuser ftpusers 45201 Dec 18 1998 gw55sp1u.exe
+-rw-r--r-- 1 ftpuser ftpusers 21311033 Feb 28 16:08 gw55sp5a.exe
+-rw-r--r-- 1 ftpuser ftpusers 52764291 Feb 19 17:56 gw55sp5e.exe
+-rw-r--r-- 1 ftpuser ftpusers 21300984 Feb 28 16:13 gw55sp5h.exe
+-rw-r--r-- 1 ftpuser ftpusers 43933605 Feb 19 18:03 gw55sp5m.exe
+-rw-r--r-- 1 ftpuser ftpusers 44001516 Feb 28 16:03 gw55sp5s.exe
+-rw-r--r-- 1 ftpuser ftpusers 5867520 Mar 9 1999 gw55uagb.tar
+-rw-r--r-- 1 ftpuser ftpusers 20446033 Apr 23 1997 gw5img.exe
+-rw-r--r-- 1 ftpuser ftpusers 313843533 Nov 21 2001 gw6sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 313466368 Nov 20 2001 gw6sp1fr.exe
+-rw-r--r-- 1 ftpuser ftpusers 26705047 Mar 13 18:35 gw6tomcat_nt.exe
+-rw-r--r-- 1 ftpuser ftpusers 318278 Oct 15 2001 gw6wasf.exe
+-rw-r--r-- 1 ftpuser ftpusers 493803 Jan 22 1997 gwadm.exe
+-rw-r--r-- 1 ftpuser ftpusers 1900719 Jul 26 2000 gwafe510.exe
+-rw-r--r-- 1 ftpuser ftpusers 469618 Nov 2 1999 gwanlzr1.exe
+-rw-r--r-- 1 ftpuser ftpusers 479013 Jun 19 2000 gwata417.exe
+-rw-r--r-- 1 ftpuser ftpusers 1731751 Jun 19 2000 gwata517.exe
+-rw-r--r-- 1 ftpuser ftpusers 168008 Aug 12 1996 gwbr41.exe
+-rw-r--r-- 1 ftpuser ftpusers 17881984 Aug 31 1998 gwbrc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 194213 Aug 26 1997 gwbupaus.exe
+-rw-r--r-- 1 ftpuser ftpusers 885247 Jun 15 2000 gwca55.exe
+-rw-r--r-- 1 ftpuser ftpusers 902786 Jun 29 2000 gwca55us.exe
+-rw-r--r-- 1 ftpuser ftpusers 2398720 Aug 17 1999 gwccarch.exe
+-rw-r--r-- 1 ftpuser ftpusers 19751754 Aug 27 1998 gwcec5.exe
+-rw-r--r-- 1 ftpuser ftpusers 18095569 Dec 1 1998 gwcfc5a.exe
+-rw-r--r-- 1 ftpuser ftpusers 499022 Jan 22 1997 gwclint.exe
+-rw-r--r-- 1 ftpuser ftpusers 274618 Jun 19 2000 gwclust.exe
+-rw-r--r-- 1 ftpuser ftpusers 18704283 Aug 31 1998 gwczc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 18602662 Aug 28 1998 gwdec5.exe
+-rw-r--r-- 1 ftpuser ftpusers 577392 Jan 22 1997 gwdirsnc.exe
+-rw-r--r-- 1 ftpuser ftpusers 18044524 Aug 31 1998 gwdkc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 2064976 Aug 16 2001 gwe2mlfx.exe
+-rw-r--r-- 1 ftpuser ftpusers 220287 Apr 18 2001 gweppuma.exe
+-rw-r--r-- 1 ftpuser ftpusers 106411074 Feb 19 17:38 gwepsp4e.exe
+-rw-r--r-- 1 ftpuser ftpusers 105412358 Feb 19 17:48 gwepsp4m.exe
+-rw-r--r-- 1 ftpuser ftpusers 211359 Oct 15 2001 gwepwasf.exe
+-rw-r--r-- 1 ftpuser ftpusers 18156006 Aug 31 1998 gwesc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 2278400 Aug 20 1999 gwexarch.exe
+-rw-r--r-- 1 ftpuser ftpusers 18094284 Sep 2 1998 gwfrc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 14809111 Feb 5 12:22 gwia6sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 47599 Dec 24 1998 gwip.exe
+-rw-r--r-- 1 ftpuser ftpusers 2103744 Jun 26 2000 gwiru55.exe
+-rw-r--r-- 1 ftpuser ftpusers 18176506 Aug 31 1998 gwitc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 1643991 May 20 1997 gwjpc3.exe
+-rw-r--r-- 1 ftpuser ftpusers 2689024 Aug 24 1999 gwlnarch.exe
+-rw-r--r-- 1 ftpuser ftpusers 18450385 Aug 31 1998 gwmac5.exe
+-rw-r--r-- 1 ftpuser ftpusers 104256 Jun 19 2001 gwmacvew.exe
+-rw-r--r-- 1 ftpuser ftpusers 521879 Jan 22 1997 gwmigrat.exe
+-rw-r--r-- 1 ftpuser ftpusers 2387456 Aug 18 1999 gwmsarch.exe
+-rw-r--r-- 1 ftpuser ftpusers 18442250 Aug 31 1998 gwnlc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 18496070 Aug 31 1998 gwnoc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 18436940 Aug 27 1998 gwozc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 735542 Aug 29 2001 gwpdchk.exe
+-rw-r--r-- 1 ftpuser ftpusers 29521018 Aug 28 2001 gwpdlk56.exe
+-rw-r--r-- 1 ftpuser ftpusers 29520686 Aug 14 2001 gwpdlock.exe
+-rw-r--r-- 1 ftpuser ftpusers 18696898 Aug 31 1998 gwplc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 1388969 Mar 5 1996 gwpoc.exe
+-rw-r--r-- 1 ftpuser ftpusers 17738497 Aug 31 1998 gwpoc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 10400450 Jan 22 16:48 gwport32.exe
+-rw-r--r-- 1 ftpuser ftpusers 18492682 Aug 31 1998 gwruc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 18154021 Aug 31 1998 gwsuc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 17958220 Aug 31 1998 gwsvc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 536848 Jan 16 09:41 gwsyclo.exe
+-rw-r--r-- 1 ftpuser ftpusers 78740 Nov 9 1999 gwtps1v2.exe
+-rw-r--r-- 1 ftpuser ftpusers 18432206 Aug 28 1998 gwukc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 18544901 Aug 27 1998 gwusc5.exe
+-rw-r--r-- 1 ftpuser ftpusers 210542 Oct 18 1995 gwusr1.exe
+-rw-r--r-- 1 ftpuser ftpusers 1404401 Oct 18 1995 gwusr2.exe
+-rw-r--r-- 1 ftpuser ftpusers 2467040 Mar 28 1997 gwusr3.exe
+-rw-r--r-- 1 ftpuser ftpusers 767611 Oct 1 1997 gwwa4p1.exe
+-rw-r--r-- 1 ftpuser ftpusers 2092781 Mar 4 1998 gwwebcgi.z
+-rw-r--r-- 1 ftpuser ftpusers 345006 Aug 4 1999 gwy195.exe
+-rw-r--r-- 1 ftpuser ftpusers 102293 Oct 9 2001 hdir501c.exe
+-rw-r--r-- 1 ftpuser ftpusers 1138106 Aug 21 1998 highutl1.exe
+-rw-r--r-- 1 ftpuser ftpusers 567026 Mar 28 1995 hpt004.exe
+-rw-r--r-- 1 ftpuser ftpusers 64329 Mar 9 1995 hpt005.exe
+-rw-r--r-- 1 ftpuser ftpusers 602958 Aug 27 1996 hpt006.exe
+-rw-r--r-- 1 ftpuser ftpusers 497382 Mar 9 1995 hpt007.exe
+-rw-r--r-- 1 ftpuser ftpusers 510029 Mar 9 1995 hpt008.exe
+-rw-r--r-- 1 ftpuser ftpusers 596667 Mar 13 1995 hpt009.exe
+-rw-r--r-- 1 ftpuser ftpusers 942798 Nov 20 1995 hpt011.exe
+-rw-r--r-- 1 ftpuser ftpusers 176332 Aug 11 1995 hpt013.exe
+-rw-r--r-- 1 ftpuser ftpusers 1070543 Mar 16 1999 hstdev.exe
+-rw-r--r-- 1 ftpuser ftpusers 246782 Apr 5 14:51 httpstk1.exe
+-rw-r--r-- 1 ftpuser ftpusers 262791 Apr 19 2000 i2odrv5.exe
+-rw-r--r-- 1 ftpuser ftpusers 565749 Jul 20 1998 ibm21y2k.exe
+-rw-r--r-- 1 ftpuser ftpusers 24735 Sep 18 1993 ibmmem.exe
+-rw-r--r-- 1 ftpuser ftpusers 60883 Sep 18 1993 ibmrt.exe
+-rw-r--r-- 1 ftpuser ftpusers 2621963 Jul 11 2001 ic15fp2.exe
+-rw-r--r-- 1 ftpuser ftpusers 2905716 Feb 8 16:39 ic15fp3.exe
+-rw-r--r-- 1 ftpuser ftpusers 9581302 Apr 4 2001 ic15sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 1585498 Dec 14 2001 ic20fp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 4520778 Jan 14 09:32 ic20fp2.exe
+-rw-r--r-- 1 ftpuser ftpusers 4777958 May 30 15:01 ic20sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 142010 Mar 28 17:46 ichcdump.exe
+-rw-r--r-- 1 ftpuser ftpusers 36043 Sep 20 1994 ide.exe
+-rw-r--r-- 1 ftpuser ftpusers 24552 Sep 18 1993 ide286.exe
+-rw-r--r-- 1 ftpuser ftpusers 27300 Sep 18 1993 ide386.exe
+-rw-r--r-- 1 ftpuser ftpusers 105249 Jun 9 2000 ideata5a.exe
+-rw-r--r-- 1 ftpuser ftpusers 1356133 Apr 17 2000 ihp232.exe
+-rw-r--r-- 1 ftpuser ftpusers 93664 Mar 8 15:40 imspmfix.exe
+-rw-r--r-- 1 ftpuser ftpusers 7338542 Jul 23 1998 in42sp2.exe
+-rw-r--r-- 1 ftpuser ftpusers 430410 Nov 8 1995 ind41.exe
+-rw-r--r-- 1 ftpuser ftpusers 63751 Mar 20 1997 inetcs.exe
+-rw-r--r-- 1 ftpuser ftpusers 60414 Mar 20 1997 inetct.exe
+-rw-r--r-- 1 ftpuser ftpusers 69792 Apr 28 1997 inetha.exe
+-rw-r--r-- 1 ftpuser ftpusers 1094896 Oct 18 1995 inf1.exe
+-rw-r--r-- 1 ftpuser ftpusers 798379 Oct 18 1995 inff1.exe
+-rw-r--r-- 1 ftpuser ftpusers 151877 Jan 9 2002 installa.exe
+-rw-r--r-- 1 ftpuser ftpusers 836804 Jun 9 1995 instll.exe
+-rw-r--r-- 1 ftpuser ftpusers 101582 Oct 9 2000 instp5x.exe
+-rw-r--r-- 1 ftpuser ftpusers 39511 Sep 18 1993 int215.exe
+-rw-r--r-- 1 ftpuser ftpusers 19521 Sep 18 1993 intfix.exe
+-rw-r--r-- 1 ftpuser ftpusers 35521 Sep 18 1993 intrud.exe
+-rw-r--r-- 1 ftpuser ftpusers 5594188 Dec 4 1997 inwc2enh.exe
+-rw-r--r-- 1 ftpuser ftpusers 69340 Sep 18 1993 ipc212.exe
+-rw-r--r-- 1 ftpuser ftpusers 24423 Sep 18 1993 ipcfg.exe
+-rw-r--r-- 1 ftpuser ftpusers 593463 Aug 13 2001 ipcost.exe
+-rw-r--r-- 1 ftpuser ftpusers 131414 Jun 24 1999 ipg4201a.exe
+-rw-r--r-- 1 ftpuser ftpusers 617600 Jun 30 2000 ipgc07a.exe
+-rw-r--r-- 1 ftpuser ftpusers 233838 Feb 11 2000 ipgsb06.exe
+-rw-r--r-- 1 ftpuser ftpusers 107564 Oct 2 1999 ipgsn10a.exe
+-rw-r--r-- 1 ftpuser ftpusers 44888 Nov 15 1999 ipgwdoc.exe
+-rw-r--r-- 1 ftpuser ftpusers 65876 Sep 18 1993 ipt112.exe
+-rw-r--r-- 1 ftpuser ftpusers 842325 Sep 23 1998 ipx660.exe
+-rw-r--r-- 1 ftpuser ftpusers 37227 Dec 1 1995 ipxspx.exe
+-rw-r--r-- 1 ftpuser ftpusers 22904 Sep 18 1993 isa30.exe
+-rw-r--r-- 1 ftpuser ftpusers 31546 Sep 18 1993 isa311.exe
+-rw-r--r-- 1 ftpuser ftpusers 24391 Sep 18 1993 isarem.exe
+-rw-r--r-- 1 ftpuser ftpusers 49650 Apr 9 1997 iwsbp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 6073482 Nov 21 2000 jbm30sp3.exe
+-rw-r--r-- 1 ftpuser ftpusers 109173 Oct 19 1995 jcon.exe
+-rw-r--r-- 1 ftpuser ftpusers 185167 Aug 16 2001 jr08993.exe
+-rw-r--r-- 1 ftpuser ftpusers 54187 Aug 15 2001 jr10449.exe
+-rw-r--r-- 1 ftpuser ftpusers 51804 Aug 15 2001 jr10470.exe
+-rw-r--r-- 1 ftpuser ftpusers 105466 May 29 2001 jr10490.exe
+-rw-r--r-- 1 ftpuser ftpusers 17936 Oct 16 2000 jr10662.exe
+-rw-r--r-- 1 ftpuser ftpusers 51421 Oct 16 2000 jr10803.exe
+-rw-r--r-- 1 ftpuser ftpusers 884373 Aug 15 2001 jr10823.exe
+-rw-r--r-- 1 ftpuser ftpusers 1909 Aug 15 2001 jr10923.zip
+-rw-r--r-- 1 ftpuser ftpusers 53610 Oct 16 2000 jr11209.exe
+-rw-r--r-- 1 ftpuser ftpusers 272210 Oct 16 2000 jr11213.exe
+-rw-r--r-- 1 ftpuser ftpusers 32984 May 27 2001 jr11223.exe
+-rw-r--r-- 1 ftpuser ftpusers 45200 Oct 16 2000 jr11409.exe
+-rw-r--r-- 1 ftpuser ftpusers 488829 May 27 2001 jr12054.exe
+-rw-r--r-- 1 ftpuser ftpusers 17761 Aug 15 2001 jr12089.exe
+-rw-r--r-- 1 ftpuser ftpusers 245833 May 27 2001 jr12125.exe
+-rw-r--r-- 1 ftpuser ftpusers 242650 Aug 15 2001 jr12129.exe
+-rw-r--r-- 1 ftpuser ftpusers 144543 May 27 2001 jr12229.exe
+-rw-r--r-- 1 ftpuser ftpusers 23194 May 27 2001 jr12349.exe
+-rw-r--r-- 1 ftpuser ftpusers 158924 Aug 15 2001 jr12415.exe
+-rw-r--r-- 1 ftpuser ftpusers 39844 Oct 16 2000 jr12518.exe
+-rw-r--r-- 1 ftpuser ftpusers 42015 Oct 16 2000 jr12676.exe
+-rw-r--r-- 1 ftpuser ftpusers 42090 May 27 2001 jr12692.exe
+-rw-r--r-- 1 ftpuser ftpusers 41998 May 29 2001 jr12705.exe
+-rw-r--r-- 1 ftpuser ftpusers 21903 Oct 16 2000 jr12706.exe
+-rw-r--r-- 1 ftpuser ftpusers 75818 Oct 16 2000 jr12765.exe
+-rw-r--r-- 1 ftpuser ftpusers 245898 Aug 15 2001 jr12768.exe
+-rw-r--r-- 1 ftpuser ftpusers 144542 Oct 16 2000 jr12773.exe
+-rw-r--r-- 1 ftpuser ftpusers 144542 May 29 2001 jr12775.exe
+-rw-r--r-- 1 ftpuser ftpusers 144543 Aug 15 2001 jr12776.exe
+-rw-r--r-- 1 ftpuser ftpusers 44352 Oct 16 2000 jr12777.exe
+-rw-r--r-- 1 ftpuser ftpusers 44355 May 27 2001 jr12778.exe
+-rw-r--r-- 1 ftpuser ftpusers 43721 Aug 15 2001 jr12828.exe
+-rw-r--r-- 1 ftpuser ftpusers 52823 May 27 2001 jr12890.exe
+-rw-r--r-- 1 ftpuser ftpusers 52824 Oct 16 2000 jr12947.exe
+-rw-r--r-- 1 ftpuser ftpusers 74323 Oct 16 2000 jr12978.exe
+-rw-r--r-- 1 ftpuser ftpusers 74323 May 27 2001 jr12979.exe
+-rw-r--r-- 1 ftpuser ftpusers 74326 May 29 2001 jr12987.exe
+-rw-r--r-- 1 ftpuser ftpusers 16573256 May 27 2001 jr13042.exe
+-rw-r--r-- 1 ftpuser ftpusers 16586769 Aug 15 2001 jr13046.exe
+-rw-r--r-- 1 ftpuser ftpusers 170632 Aug 15 2001 jr13047.exe
+-rw-r--r-- 1 ftpuser ftpusers 55210 Aug 15 2001 jr13105.exe
+-rw-r--r-- 1 ftpuser ftpusers 124966 Aug 15 2001 jr13147.exe
+-rw-r--r-- 1 ftpuser ftpusers 43318 Oct 16 2000 jr13259.exe
+-rw-r--r-- 1 ftpuser ftpusers 33183 Oct 16 2000 jr13461.exe
+-rw-r--r-- 1 ftpuser ftpusers 84700 May 27 2001 jr13554.exe
+-rw-r--r-- 1 ftpuser ftpusers 84649 Oct 16 2000 jr13555.exe
+-rw-r--r-- 1 ftpuser ftpusers 52564 May 27 2001 jr13557.exe
+-rw-r--r-- 1 ftpuser ftpusers 52161 Oct 16 2000 jr13558.exe
+-rw-r--r-- 1 ftpuser ftpusers 52167 May 29 2001 jr13559.exe
+-rw-r--r-- 1 ftpuser ftpusers 54942 May 27 2001 jr13616.exe
+-rw-r--r-- 1 ftpuser ftpusers 54947 Oct 16 2000 jr13617.exe
+-rw-r--r-- 1 ftpuser ftpusers 56424 Aug 15 2001 jr13619.exe
+-rw-r--r-- 1 ftpuser ftpusers 71827 Sep 17 1999 jr13627.exe
+-rw-r--r-- 1 ftpuser ftpusers 83182 Oct 16 2000 jr13637.exe
+-rw-r--r-- 1 ftpuser ftpusers 108483 May 29 2001 jr13722.exe
+-rw-r--r-- 1 ftpuser ftpusers 170173 Aug 15 2001 jr13734.exe
+-rw-r--r-- 1 ftpuser ftpusers 641941 Aug 15 2001 jr13748.exe
+-rw-r--r-- 1 ftpuser ftpusers 641950 May 29 2001 jr13749.exe
+-rw-r--r-- 1 ftpuser ftpusers 579521 May 29 2001 jr13886.exe
+-rw-r--r-- 1 ftpuser ftpusers 126491 Aug 15 2001 jr13891.exe
+-rw-r--r-- 1 ftpuser ftpusers 109292 May 27 2001 jr14132.exe
+-rw-r--r-- 1 ftpuser ftpusers 109181 Aug 15 2001 jr14134.exe
+-rw-r--r-- 1 ftpuser ftpusers 39830 May 29 2001 jr14185.exe
+-rw-r--r-- 1 ftpuser ftpusers 72780 Aug 15 2001 jr14278.exe
+-rw-r--r-- 1 ftpuser ftpusers 345689 Aug 15 2001 jr14358.exe
+-rw-r--r-- 1 ftpuser ftpusers 642240 May 27 2001 jr14359.exe
+-rw-r--r-- 1 ftpuser ftpusers 642388 Oct 16 2000 jr14360.exe
+-rw-r--r-- 1 ftpuser ftpusers 313078 May 27 2001 jr14367.exe
+-rw-r--r-- 1 ftpuser ftpusers 109587 May 27 2001 jr14402.exe
+-rw-r--r-- 1 ftpuser ftpusers 71603 Aug 15 2001 jr14457.exe
+-rw-r--r-- 1 ftpuser ftpusers 567270 Aug 15 2001 jr14500.exe
+-rw-r--r-- 1 ftpuser ftpusers 157611 Aug 15 2001 jr14523.exe
+-rw-r--r-- 1 ftpuser ftpusers 161290 Aug 15 2001 jr14556.exe
+-rw-r--r-- 1 ftpuser ftpusers 161439 May 27 2001 jr14585.exe
+-rw-r--r-- 1 ftpuser ftpusers 161435 Oct 16 2000 jr14586.exe
+-rw-r--r-- 1 ftpuser ftpusers 186037 Oct 16 2000 jr14840.exe
+-rw-r--r-- 1 ftpuser ftpusers 368604 Oct 16 2000 jr14936.exe
+-rw-r--r-- 1 ftpuser ftpusers 115132 Aug 15 2001 jr15013.exe
+-rw-r--r-- 1 ftpuser ftpusers 568164 Aug 15 2001 jr15133.exe
+-rw-r--r-- 1 ftpuser ftpusers 217169 Aug 15 2001 jr15338.exe
+-rw-r--r-- 1 ftpuser ftpusers 362664 Aug 15 2001 jr15396.exe
+-rw-r--r-- 1 ftpuser ftpusers 104143 Aug 15 2001 jr15509.exe
+-rw-r--r-- 1 ftpuser ftpusers 473822 Aug 15 2001 jr15544.exe
+-rw-r--r-- 1 ftpuser ftpusers 484196 Jul 25 2001 jssl11d.exe
+-rw-r--r-- 1 ftpuser ftpusers 36251555 Jul 20 2001 jvm122.exe
+-rw-r--r-- 1 ftpuser ftpusers 49007010 Feb 28 08:06 jvm131.exe
+-rw-r--r-- 1 ftpuser ftpusers 48672254 Mar 28 2001 jzfd3sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 66469 Jul 17 1997 killq.exe
+-rw-r--r-- 1 ftpuser ftpusers 202716 Mar 1 1994 l11f01.exe
+-rw-r--r-- 1 ftpuser ftpusers 202878 Mar 1 1994 l11g01.exe
+-rw-r--r-- 1 ftpuser ftpusers 202460 Mar 1 1994 l11i01.exe
+-rw-r--r-- 1 ftpuser ftpusers 23583 Jan 6 1994 l11p06.exe
+-rw-r--r-- 1 ftpuser ftpusers 202249 Mar 1 1994 l11s01.exe
+-rw-r--r-- 1 ftpuser ftpusers 30533 Nov 24 1993 l11u05.exe
+-rw-r--r-- 1 ftpuser ftpusers 206616 Apr 20 1999 lanchk.exe
+-rw-r--r-- 1 ftpuser ftpusers 341280 Oct 21 1996 landr9.exe
+-rw-r--r-- 1 ftpuser ftpusers 4610909 May 14 1998 landrv.exe
+-rw-r--r-- 1 ftpuser ftpusers 24799 Sep 10 1996 lanwcs.exe
+-rw-r--r-- 1 ftpuser ftpusers 37088 Sep 4 1996 lanwct.exe
+-rw-r--r-- 1 ftpuser ftpusers 42514 Sep 4 1996 lanwes.exe
+-rw-r--r-- 1 ftpuser ftpusers 42417 Sep 11 1996 lanwfr.exe
+-rw-r--r-- 1 ftpuser ftpusers 36855 Sep 10 1996 lanwha.exe
+-rw-r--r-- 1 ftpuser ftpusers 42397 Sep 4 1996 lanwit.exe
+-rw-r--r-- 1 ftpuser ftpusers 16012931 Jan 31 16:00 lanwp02.exe
+-rw-r--r-- 1 ftpuser ftpusers 342390 Dec 16 1996 lat002.exe
+-rw-r--r-- 1 ftpuser ftpusers 133326 Sep 18 1993 ld401a.exe
+-rw-r--r-- 1 ftpuser ftpusers 166765 Feb 7 2000 ldr312ft.exe
+-rw-r--r-- 1 ftpuser ftpusers 26450 Sep 18 1993 leap.exe
+-rw-r--r-- 1 ftpuser ftpusers 180540 Jun 9 1994 lg4084.exe
+-rw-r--r-- 1 ftpuser ftpusers 91840 Dec 14 1995 lg42l4.exe
+-rw-r--r-- 1 ftpuser ftpusers 72061 Oct 11 1995 li3pre.exe
+-rw-r--r-- 1 ftpuser ftpusers 258605 Jun 2 1998 lib311b.exe
+-rw-r--r-- 1 ftpuser ftpusers 273268 Sep 20 1999 lib312d.exe
+-rw-r--r-- 1 ftpuser ftpusers 977416 Feb 23 2000 libupj4.exe
+-rw-r--r-- 1 ftpuser ftpusers 111269 Jun 26 1995 lo30a2.exe
+-rw-r--r-- 1 ftpuser ftpusers 94927 Jun 26 1995 lo30t1.exe
+-rw-r--r-- 1 ftpuser ftpusers 105128 Sep 18 1993 load.exe
+-rw-r--r-- 1 ftpuser ftpusers 111722 Aug 26 1998 loaddll1.exe
+-rw-r--r-- 1 ftpuser ftpusers 20810 Sep 18 1993 locins.exe
+-rw-r--r-- 1 ftpuser ftpusers 74768 Mar 9 1995 log376.exe
+-rw-r--r-- 1 ftpuser ftpusers 269333 Sep 15 1997 log410a.exe
+-rw-r--r-- 1 ftpuser ftpusers 99524 Feb 23 2000 longnam.exe
+-rw-r--r-- 1 ftpuser ftpusers 1563061 Mar 5 1997 lpo51a.exe
+-rw-r--r-- 1 ftpuser ftpusers 115601 Mar 14 1997 lpo51b.exe
+-rw-r--r-- 1 ftpuser ftpusers 373114 Jun 12 1995 lw42w2.exe
+-rw-r--r-- 1 ftpuser ftpusers 986579 Sep 20 1996 lw50w1.exe
+-rw-r--r-- 1 ftpuser ftpusers 79276 Aug 21 1996 lwg50a.exe
+-rw-r--r-- 1 ftpuser ftpusers 115527 Aug 18 1998 lwp001.exe
+-rw-r--r-- 1 ftpuser ftpusers 54951 Aug 20 1998 lwp002.exe
+-rw-r--r-- 1 ftpuser ftpusers 447982 May 6 1996 lwp413.exe
+-rw-r--r-- 1 ftpuser ftpusers 231591 Nov 30 1994 lwp423.exe
+-rw-r--r-- 1 ftpuser ftpusers 26516 May 18 1998 lwp501.exe
+-rw-r--r-- 1 ftpuser ftpusers 27895 May 15 1998 lwp511.exe
+-rw-r--r-- 1 ftpuser ftpusers 21913 Sep 18 1993 lwpo30.exe
+-rw-r--r-- 1 ftpuser ftpusers 47334 Sep 3 1997 lwpping.exe
+-rw-r--r-- 1 ftpuser ftpusers 356846 Aug 9 1999 lzfw01c.exe
+-rw-r--r-- 1 ftpuser ftpusers 33252 Oct 18 1993 lzfwdi.exe
+-rw-r--r-- 1 ftpuser ftpusers 1083871 Jul 14 1999 lzfwlf.exe
+-rw-r--r-- 1 ftpuser ftpusers 29057 Jun 16 1995 lzfwqa.exe
+-rw-r--r-- 1 ftpuser ftpusers 26848 Sep 7 1994 lzw001.exe
+-rw-r--r-- 1 ftpuser ftpusers 29431 May 16 1995 lzw002.exe
+-rw-r--r-- 1 ftpuser ftpusers 151295 Sep 18 1993 lzw40.exe
+-rw-r--r-- 1 ftpuser ftpusers 29379 Sep 18 1993 m30pat.exe
+-rw-r--r-- 1 ftpuser ftpusers 122720 Jul 24 1996 macfil.exe
+-rw-r--r-- 1 ftpuser ftpusers 347278 Jul 10 1997 macpt3d.exe
+-rw-r--r-- 1 ftpuser ftpusers 193792 May 29 1996 mactsa.bin
+-rw-r--r-- 1 ftpuser ftpusers 232349 Jul 23 1996 mactsa.exe
+-rw-r--r-- 1 ftpuser ftpusers 210004 Feb 21 1996 mailcl.exe
+-rw-r--r-- 1 ftpuser ftpusers 48964 Mar 13 1995 map312.exe
+-rw-r--r-- 1 ftpuser ftpusers 144574 Nov 24 1997 map410b.exe
+-rw-r--r-- 1 ftpuser ftpusers 127956 Oct 10 2001 masv_sp3.exe
+-rw-r--r-- 1 ftpuser ftpusers 266084 Feb 28 2001 mbcmnup1.exe
+-rw-r--r-- 1 ftpuser ftpusers 508288 Aug 13 1998 mclupd6a.bin
+-rw-r--r-- 1 ftpuser ftpusers 22803 Sep 18 1993 mcmfm.exe
+-rw-r--r-- 1 ftpuser ftpusers 37710 Feb 11 1994 mdf178.exe
+-rw-r--r-- 1 ftpuser ftpusers 87520 Sep 18 1993 menu34.exe
+-rw-r--r-- 1 ftpuser ftpusers 20214 Nov 15 1993 menuhi.exe
+-rw-r--r-- 1 ftpuser ftpusers 91376 Sep 18 1993 menus.exe
+-rw-r--r-- 1 ftpuser ftpusers 5040875 Aug 15 2001 mgt22010.exe
+-rw-r--r-- 1 ftpuser ftpusers 683946 Nov 3 1993 mhs173.exe
+-rw-r--r-- 1 ftpuser ftpusers 656047 Feb 14 1994 mhs183.exe
+-rw-r--r-- 1 ftpuser ftpusers 664789 Feb 7 1994 mhs184.exe
+-rw-r--r-- 1 ftpuser ftpusers 85281 Nov 3 1997 mhsmcu.exe
+-rw-r--r-- 1 ftpuser ftpusers 54627 Sep 18 1993 mhsmd.exe
+-rw-r--r-- 1 ftpuser ftpusers 604103 Aug 16 1994 mhsslt.exe
+-rw-r--r-- 1 ftpuser ftpusers 132603 Jun 30 1995 mhsvr1.exe
+-rw-r--r-- 1 ftpuser ftpusers 449605 Aug 1 1996 migrat.exe
+-rw-r--r-- 1 ftpuser ftpusers 33708 Sep 18 1993 mirrem.exe
+-rw-r--r-- 1 ftpuser ftpusers 27044 Jan 5 2000 mixmod6.exe
+-rw-r--r-- 1 ftpuser ftpusers 96673 Sep 18 1993 mkuser.exe
+-rw-r--r-- 1 ftpuser ftpusers 68504 Sep 18 1995 mon176.exe
+-rw-r--r-- 1 ftpuser ftpusers 66024 May 18 1995 monsft.exe
+-rw-r--r-- 1 ftpuser ftpusers 424897 Sep 18 1993 morebk.exe
+-rw-r--r-- 1 ftpuser ftpusers 37818 Sep 18 1993 mountr.exe
+-rw-r--r-- 1 ftpuser ftpusers 19452 Sep 18 1993 mouse.exe
+-rw-r--r-- 1 ftpuser ftpusers 32801 Feb 3 1994 mpr182.exe
+-rw-r--r-- 1 ftpuser ftpusers 125166 Jul 15 1994 mpr199.exe
+-rw-r--r-- 1 ftpuser ftpusers 1066126 Jun 4 1996 mpr31a.exe
+-rw-r--r-- 1 ftpuser ftpusers 1082729 Oct 28 1996 mpr31b.exe
+-rw-r--r-- 1 ftpuser ftpusers 31245 Sep 23 1994 mprper.exe
+-rw-r--r-- 1 ftpuser ftpusers 38997 Mar 24 1995 mprul3.exe
+-rw-r--r-- 1 ftpuser ftpusers 122440 Oct 11 1995 mprx25.exe
+-rw-r--r-- 1 ftpuser ftpusers 328085 Aug 2 1996 msml21.exe
+-rw-r--r-- 1 ftpuser ftpusers 5876316 Oct 5 1998 msmlos21.exe
+-rw-r--r-- 1 ftpuser ftpusers 70822 Aug 2 1999 msmpcu.exe
+-rw-r--r-- 1 ftpuser ftpusers 6355314 May 31 2000 mw26sp3.exe
+-rw-r--r-- 1 ftpuser ftpusers 119531 Jul 31 2000 mw26trd1.exe
+-rw-r--r-- 1 ftpuser ftpusers 3054719 Nov 13 2000 mw27sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 27173 Nov 23 1999 mw5mcal1.exe
+-rw-r--r-- 1 ftpuser ftpusers 253007 May 17 2000 mwhp01b.exe
+-rw-r--r-- 1 ftpuser ftpusers 255066 Jan 25 11:47 mwhp01c.exe
+-rw-r--r-- 1 ftpuser ftpusers 9813556 Sep 1 2000 mwinoc1k.exe
+-rw-r--r-- 1 ftpuser ftpusers 8298211 Sep 1 2000 mwinoc2k.exe
+-rw-r--r-- 1 ftpuser ftpusers 141530 Feb 20 14:35 mwipgrop.exe
+-rw-r--r-- 1 ftpuser ftpusers 566807 Oct 9 1996 mwmis01.exe
+-rw-r--r-- 1 ftpuser ftpusers 1070535 Jan 26 1999 mwnma26.exe
+-rw-r--r-- 1 ftpuser ftpusers 265545 Oct 16 1998 mwnma3a.exe
+-rw-r--r-- 1 ftpuser ftpusers 300304 Oct 16 1998 mwnma4a.exe
+-rw-r--r-- 1 ftpuser ftpusers 2180810 Apr 12 2001 mwnmaupd.exe
+-rw-r--r-- 1 ftpuser ftpusers 154598 Jan 12 2000 mwnxp26.exe
+-rw-r--r-- 1 ftpuser ftpusers 339201 Apr 9 2001 mwnxpfix.exe
+-rw-r--r-- 1 ftpuser ftpusers 1502706 Aug 20 1999 mwzen01.exe
+-rw-r--r-- 1 ftpuser ftpusers 58460632 Mar 28 2001 mzfd3sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 2407200 May 24 2001 n51_nis1.exe
+-rw-r--r-- 1 ftpuser ftpusers 6840962 May 16 2000 n8slinux.01
+-rw-r--r-- 1 ftpuser ftpusers 217465 Feb 18 1998 na4nty2k.exe
+-rw-r--r-- 1 ftpuser ftpusers 48510 Sep 18 1993 nacs2.exe
+-rw-r--r-- 1 ftpuser ftpusers 64238 May 28 1996 nadhep.exe
+-rw-r--r-- 1 ftpuser ftpusers 1777018 Mar 23 1999 nal201p2.exe
+-rw-r--r-- 1 ftpuser ftpusers 38276 Oct 29 1996 nam312.exe
+-rw-r--r-- 1 ftpuser ftpusers 199571 Jun 6 2000 nam41d.exe
+-rw-r--r-- 1 ftpuser ftpusers 104382 May 24 15:12 nat600d.exe
+-rw-r--r-- 1 ftpuser ftpusers 322806 Sep 18 1993 nbckup.exe
+-rw-r--r-- 1 ftpuser ftpusers 21328 Jan 10 1997 nbora.exe
+-rw-r--r-- 1 ftpuser ftpusers 18843 Aug 12 1997 nc1tip.txt
+-rw-r--r-- 1 ftpuser ftpusers 838367 Jun 3 09:30 nccutl10.exe
+-rw-r--r-- 1 ftpuser ftpusers 7725859 Oct 24 2000 nce8slr1.z
+-rw-r--r-- 1 ftpuser ftpusers 77251 Sep 18 1993 nconfg.exe
+-rw-r--r-- 1 ftpuser ftpusers 19889 Oct 10 1997 ncpec.exe
+-rw-r--r-- 1 ftpuser ftpusers 144892 Sep 15 1997 ncpy410a.exe
+-rw-r--r-- 1 ftpuser ftpusers 136962 Nov 21 2000 ncs3.exe
+-rw-r--r-- 1 ftpuser ftpusers 704205 Oct 1 1996 ncv201.exe
+-rw-r--r-- 1 ftpuser ftpusers 811853 Oct 1 1996 ncv202.exe
+-rw-r--r-- 1 ftpuser ftpusers 489774 Apr 21 1998 ncv20y2k.exe
+-rw-r--r-- 1 ftpuser ftpusers 142314 Nov 12 2001 ndb410q.exe
+-rw-r--r-- 1 ftpuser ftpusers 820712 Feb 21 13:36 ndp21p3c.exe
+-rw-r--r-- 1 ftpuser ftpusers 853818 Jun 21 12:06 ndp21p4.exe
+-rw-r--r-- 1 ftpuser ftpusers 1008445 Oct 17 2001 ndp2xp7.exe
+-rw-r--r-- 1 ftpuser ftpusers 530101 Feb 21 13:53 ndpcnw6.exe
+-rw-r--r-- 1 ftpuser ftpusers 909348 Feb 21 13:48 ndpcsp3a.exe
+-rw-r--r-- 1 ftpuser ftpusers 94047 Jul 17 2001 ndpsinf.exe
+-rw-r--r-- 1 ftpuser ftpusers 11673051 Oct 4 2001 ndpsw2k.exe
+-rw-r--r-- 1 ftpuser ftpusers 59495 Sep 18 1993 ndr345.exe
+-rw-r--r-- 1 ftpuser ftpusers 112722 Feb 2 2000 nds4ntp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 507960 Aug 17 2000 nds4ntu3.exe
+-rw-r--r-- 1 ftpuser ftpusers 3437308 Oct 27 2000 nds8lnx1.gz
+-rw-r--r-- 1 ftpuser ftpusers 14133762 Jul 17 2001 ndsas3s1.exe
+-rw-r--r-- 1 ftpuser ftpusers 24182 Sep 18 1993 ndscsi.exe
+-rw-r--r-- 1 ftpuser ftpusers 41652 Sep 18 1993 ndspx.exe
+-rw-r--r-- 1 ftpuser ftpusers 6712 Jul 20 2000 ndssch.gz
+-rw-r--r-- 1 ftpuser ftpusers 20023 Sep 18 1993 ndstac.exe
+-rw-r--r-- 1 ftpuser ftpusers 211239 Sep 18 1993 ne286.exe
+-rw-r--r-- 1 ftpuser ftpusers 5894979 Oct 24 2000 ned8slr1.z
+-rw-r--r-- 1 ftpuser ftpusers 82148198 May 3 2000 nesn451a.exe
+-rw-r--r-- 1 ftpuser ftpusers 1157470 Dec 20 2000 nesn51b.exe
+-rw-r--r-- 1 ftpuser ftpusers 866140 Nov 28 2001 nesn51c.exe
+-rw-r--r-- 1 ftpuser ftpusers 865375 Mar 5 06:54 nesn51d.exe
+-rw-r--r-- 1 ftpuser ftpusers 138202 Mar 16 1999 netarng3.exe
+-rw-r--r-- 1 ftpuser ftpusers 73822 Feb 4 1998 netfr.exe
+-rw-r--r-- 1 ftpuser ftpusers 306211 Mar 12 1996 netusr.exe
+-rw-r--r-- 1 ftpuser ftpusers 74054 Feb 4 1998 netwbr.exe
+-rw-r--r-- 1 ftpuser ftpusers 56229 Feb 4 1998 netwde.exe
+-rw-r--r-- 1 ftpuser ftpusers 27699 Aug 28 1996 netwdk.exe
+-rw-r--r-- 1 ftpuser ftpusers 67776 Feb 2 1998 netwes.exe
+-rw-r--r-- 1 ftpuser ftpusers 59210 Feb 12 1998 netwit.exe
+-rw-r--r-- 1 ftpuser ftpusers 28000 Aug 28 1996 netwno.exe
+-rw-r--r-- 1 ftpuser ftpusers 53501 Sep 10 1996 netwru.exe
+-rw-r--r-- 1 ftpuser ftpusers 27741 Aug 23 1996 netwsv.exe
+-rw-r--r-- 1 ftpuser ftpusers 1539054 Mar 18 15:27 nfap1sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 290090 Aug 22 1994 nfs193.exe
+-rw-r--r-- 1 ftpuser ftpusers 2266015 Dec 30 1997 nfs203.exe
+-rw-r--r-- 1 ftpuser ftpusers 2007873 Nov 23 1998 nfs205.exe
+-rw-r--r-- 1 ftpuser ftpusers 6472548 Jul 20 2001 nfs30sp2.exe
+-rw-r--r-- 1 ftpuser ftpusers 1310460 Mar 6 15:56 nfs30sp3a.exe
+-rw-r--r-- 1 ftpuser ftpusers 1186381 May 29 14:59 nfs3nwci.exe
+-rw-r--r-- 1 ftpuser ftpusers 5440307 Aug 28 2001 nfs3sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 1280002 Feb 2 2001 nfsadmn2.exe
+-rw-r--r-- 1 ftpuser ftpusers 38114 Jan 20 1998 nfsmlaup.exe
+-rw-r--r-- 1 ftpuser ftpusers 35803 Aug 2 1999 nfsnam23.exe
+-rw-r--r-- 1 ftpuser ftpusers 245015 Sep 18 1993 ngm121.exe
+-rw-r--r-- 1 ftpuser ftpusers 1240500 Sep 18 1993 ngm137.exe
+-rw-r--r-- 1 ftpuser ftpusers 145298 Sep 18 1993 ngm138.exe
+-rw-r--r-- 1 ftpuser ftpusers 145010 Sep 18 1993 ngm139.exe
+-rw-r--r-- 1 ftpuser ftpusers 781292 Oct 28 1993 ngm170.exe
+-rw-r--r-- 1 ftpuser ftpusers 202093 Dec 3 1993 ngm175.exe
+-rw-r--r-- 1 ftpuser ftpusers 201248 Dec 6 1993 ngm176.exe
+-rw-r--r-- 1 ftpuser ftpusers 279862 May 26 1994 ngm192.exe
+-rw-r--r-- 1 ftpuser ftpusers 945888 Dec 20 1996 ngm211.exe
+-rw-r--r-- 1 ftpuser ftpusers 1403369 Aug 5 1995 ngwadm.exe
+-rw-r--r-- 1 ftpuser ftpusers 1810533 Aug 13 1996 ngwaup.exe
+-rw-r--r-- 1 ftpuser ftpusers 106862 Oct 31 1995 ngwmfc.exe
+-rw-r--r-- 1 ftpuser ftpusers 662953 Mar 15 1995 ngwsnc.exe
+-rw-r--r-- 1 ftpuser ftpusers 27283 Sep 18 1993 ni5010.exe
+-rw-r--r-- 1 ftpuser ftpusers 786223 Jul 10 2001 nicid157.exe
+-rw-r--r-- 1 ftpuser ftpusers 761229 Jul 10 2001 nicie157.exe
+-rw-r--r-- 1 ftpuser ftpusers 59300 Jan 15 11:38 nicimig.tgz
+-rw-r--r-- 1 ftpuser ftpusers 6802379 Jan 17 09:35 nims265.tgz
+-rw-r--r-- 1 ftpuser ftpusers 2526415 Jan 31 10:19 nims265.zip
+-rw-r--r-- 1 ftpuser ftpusers 121765 Jan 31 11:14 nims265a.zip
+-rw-r--r-- 1 ftpuser ftpusers 4894646 Jan 14 16:09 nims26l.tgz
+-rw-r--r-- 1 ftpuser ftpusers 2492982 Jan 14 16:05 nims26n.zip
+-rw-r--r-- 1 ftpuser ftpusers 5385381 Feb 27 16:49 nims26sb.z
+-rw-r--r-- 1 ftpuser ftpusers 343052 Nov 8 1999 nims2_1a.exe
+-rw-r--r-- 1 ftpuser ftpusers 12495019 Apr 15 15:54 nims303.tar.z
+-rw-r--r-- 1 ftpuser ftpusers 7563916 Apr 15 15:53 nims303.tgz
+-rw-r--r-- 1 ftpuser ftpusers 7696073 Apr 15 15:54 nims303.zip
+-rw-r--r-- 1 ftpuser ftpusers 1252720 May 23 1997 nip202.exe
+-rw-r--r-- 1 ftpuser ftpusers 1180104 Oct 23 1998 nip203.exe
+-rw-r--r-- 1 ftpuser ftpusers 6212358 Apr 19 1996 nip22b.exe
+-rw-r--r-- 1 ftpuser ftpusers 555990 Jul 9 1996 nip318.exe
+-rw-r--r-- 1 ftpuser ftpusers 69020 Jul 9 1996 nip319.exe
+-rw-r--r-- 1 ftpuser ftpusers 2489203 Nov 2 2001 nipp10a.exe
+-rw-r--r-- 1 ftpuser ftpusers 3753117 Sep 19 2000 nipt1.exe
+-rw-r--r-- 1 ftpuser ftpusers 6369467 Mar 5 1996 nipw22.exe
+-rw-r--r-- 1 ftpuser ftpusers 42316 May 29 1996 nipw2x.exe
+-rw-r--r-- 1 ftpuser ftpusers 4286209 Jun 7 2000 njcl5a.exe
+-rw-r--r-- 1 ftpuser ftpusers 102325 Jun 10 1998 nls212.exe
+-rw-r--r-- 1 ftpuser ftpusers 155414 Jul 24 2000 nlsdll.exe
+-rw-r--r-- 1 ftpuser ftpusers 16952278 May 25 2001 nlslsp6.exe
+-rw-r--r-- 1 ftpuser ftpusers 215729 Jan 4 2000 nlsty2k.exe
+-rw-r--r-- 1 ftpuser ftpusers 1462112 Nov 28 1995 nms002.exe
+-rw-r--r-- 1 ftpuser ftpusers 48618 Mar 8 1994 nmsddf.exe
+-rw-r--r-- 1 ftpuser ftpusers 434841 Nov 15 1994 nmsxp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 28499 Sep 18 1993 nnscpt.exe
+-rw-r--r-- 1 ftpuser ftpusers 88964 Sep 18 1993 nnst40.exe
+-rw-r--r-- 1 ftpuser ftpusers 89092 Sep 18 1993 nnstll.exe
+-rw-r--r-- 1 ftpuser ftpusers 32483 Jul 25 1995 not131.exe
+-rw-r--r-- 1 ftpuser ftpusers 6707094 Oct 12 1998 notes41.exe
+-rw-r--r-- 1 ftpuser ftpusers 4556171 Jun 8 2000 notes51.exe
+-rw-r--r-- 1 ftpuser ftpusers 24900 Sep 18 1993 novadf.exe
+-rw-r--r-- 1 ftpuser ftpusers 30691 Sep 18 1993 np600a.exe
+-rw-r--r-- 1 ftpuser ftpusers 17097790 Aug 2 1999 nppb_1.exe
+-rw-r--r-- 1 ftpuser ftpusers 17097771 Aug 2 1999 nppb_2.exe
+-rw-r--r-- 1 ftpuser ftpusers 633317 Mar 15 16:33 nps15dpa.exe
+-rw-r--r-- 1 ftpuser ftpusers 31183790 Jun 28 09:08 nps15sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 8200267 Sep 10 2001 npsgad01.exe
+-rw-r--r-- 1 ftpuser ftpusers 114902 Apr 12 2001 npsnsapi.exe
+-rw-r--r-- 1 ftpuser ftpusers 615863 Feb 11 15:57 nptr95b.exe
+-rw-r--r-- 1 ftpuser ftpusers 22915 Jun 30 1997 nrsbuild.exe
+-rw-r--r-- 1 ftpuser ftpusers 793469 Jun 23 1997 nrsnt.exe
+-rw-r--r-- 1 ftpuser ftpusers 53586091 Jun 18 1998 nsbgwsp3.exe
+-rw-r--r-- 1 ftpuser ftpusers 455182 Mar 22 1994 nsd004.exe
+-rw-r--r-- 1 ftpuser ftpusers 1964329 Aug 21 1998 nsr511n.zip
+-rw-r--r-- 1 ftpuser ftpusers 1935353 Jun 24 1996 nsrtr51n.zip
+-rw-r--r-- 1 ftpuser ftpusers 93785 Feb 13 13:36 nsschk1a.exe
+-rw-r--r-- 1 ftpuser ftpusers 116845 Jul 30 1997 nsync1.exe
+-rw-r--r-- 1 ftpuser ftpusers 10933860 Sep 16 1999 nt411b.exe
+-rw-r--r-- 1 ftpuser ftpusers 1084753 Oct 26 1998 nt411p1.exe
+-rw-r--r-- 1 ftpuser ftpusers 744586 Apr 6 1999 nt430p2.exe
+-rw-r--r-- 1 ftpuser ftpusers 834534 Jun 22 1999 nt451p1.exe
+-rw-r--r-- 1 ftpuser ftpusers 920405 Mar 23 2000 nt46pt1.exe
+-rw-r--r-- 1 ftpuser ftpusers 4017917 Oct 3 1999 nt46sp2.exe
+-rw-r--r-- 1 ftpuser ftpusers 765887 Aug 11 2000 nt471pt2.exe
+-rw-r--r-- 1 ftpuser ftpusers 448015 Jun 13 2000 nt47pt3.exe
+-rw-r--r-- 1 ftpuser ftpusers 1081217 May 9 07:26 nt480pt5.exe
+-rw-r--r-- 1 ftpuser ftpusers 898400 Nov 6 2001 nt481pt1.exe
+-rw-r--r-- 1 ftpuser ftpusers 291026 Jun 14 14:07 nt483pt2.exe
+-rw-r--r-- 1 ftpuser ftpusers 150230 Nov 19 1998 ntprint.exe
+-rw-r--r-- 1 ftpuser ftpusers 114593 Jul 18 1995 ntwin.exe
+-rw-r--r-- 1 ftpuser ftpusers 28138 Sep 18 1993 nver30.exe
+-rw-r--r-- 1 ftpuser ftpusers 28798 Jan 7 1997 nw3dfs.exe
+-rw-r--r-- 1 ftpuser ftpusers 24257 Jan 7 1997 nw4dfs.exe
+-rw-r--r-- 1 ftpuser ftpusers 80377034 Nov 13 2000 nw4sp9.exe
+-rw-r--r-- 1 ftpuser ftpusers 866224 Aug 27 2001 nw4wsock.exe
+-rw-r--r-- 1 ftpuser ftpusers 158210659 Dec 20 2000 nw50sp6a.exe
+-rw-r--r-- 1 ftpuser ftpusers 490599 Jul 23 2001 nw51fs1.exe
+-rw-r--r-- 1 ftpuser ftpusers 684981 Feb 17 2000 nw51inst.exe
+-rw-r--r-- 1 ftpuser ftpusers 513640 Aug 21 2001 nw51nrm1.exe
+-rw-r--r-- 1 ftpuser ftpusers 294037836 Jul 24 2001 nw51sp3.exe
+-rw-r--r-- 1 ftpuser ftpusers 300006242 Feb 25 11:26 nw51sp4.exe
+-rw-r--r-- 1 ftpuser ftpusers 332610 Apr 27 2000 nw51upd1.exe
+-rw-r--r-- 1 ftpuser ftpusers 3250529 Jun 24 14:54 nw56up1.exe
+-rw-r--r-- 1 ftpuser ftpusers 38435 Sep 9 1999 nw5nss.exe
+-rw-r--r-- 1 ftpuser ftpusers 251511 Feb 17 2000 nw5nwip.exe
+-rw-r--r-- 1 ftpuser ftpusers 169896 May 16 2000 nw5psrv2.exe
+-rw-r--r-- 1 ftpuser ftpusers 378831 Jun 20 2000 nw5tcp.exe
+-rw-r--r-- 1 ftpuser ftpusers 1533365 Mar 22 16:33 nw6nss1a.exe
+-rw-r--r-- 1 ftpuser ftpusers 633339 Oct 22 2001 nw6sms1a.exe
+-rw-r--r-- 1 ftpuser ftpusers 237785656 Mar 6 17:15 nw6sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 568253661 Mar 11 12:19 nw6sp1ef.exe
+-rw-r--r-- 1 ftpuser ftpusers 567439767 Mar 11 12:59 nw6sp1ef56.exe
+-rw-r--r-- 1 ftpuser ftpusers 566604272 Mar 22 12:23 nw6sp1eg.exe
+-rw-r--r-- 1 ftpuser ftpusers 566458792 Mar 22 13:02 nw6sp1ei.exe
+-rw-r--r-- 1 ftpuser ftpusers 566528467 Mar 11 10:57 nw6sp1ep.exe
+-rw-r--r-- 1 ftpuser ftpusers 567333526 Mar 22 16:05 nw6sp1er.exe
+-rw-r--r-- 1 ftpuser ftpusers 565955338 Mar 13 16:55 nw6sp1es.exe
+-rw-r--r-- 1 ftpuser ftpusers 1344552 Mar 23 1999 nwadmnp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 478371 May 10 1995 nwc001.exe
+-rw-r--r-- 1 ftpuser ftpusers 251557 Feb 24 1995 nwc002.exe
+-rw-r--r-- 1 ftpuser ftpusers 1598884 Jan 31 1996 nwc201.exe
+-rw-r--r-- 1 ftpuser ftpusers 1524102 Nov 22 1995 nwc202.exe
+-rw-r--r-- 1 ftpuser ftpusers 1328082 Jan 22 1998 nwc206.exe
+-rw-r--r-- 1 ftpuser ftpusers 1382188 Nov 18 1996 nwc207.exe
+-rw-r--r-- 1 ftpuser ftpusers 547655 Oct 24 1996 nwc208.exe
+-rw-r--r-- 1 ftpuser ftpusers 29587 Aug 12 1997 nwc2tp.txt
+-rw-r--r-- 1 ftpuser ftpusers 21386 Sep 18 1993 nwc386.exe
+-rw-r--r-- 1 ftpuser ftpusers 102446 Oct 5 1994 nwcdll.exe
+-rw-r--r-- 1 ftpuser ftpusers 33443 Feb 27 1996 nwcinf.exe
+-rw-r--r-- 1 ftpuser ftpusers 22331 Sep 11 1998 nwcmod.exe
+-rw-r--r-- 1 ftpuser ftpusers 71514 Sep 20 1995 nwcncs.exe
+-rw-r--r-- 1 ftpuser ftpusers 37424 Jul 11 1997 nwcppp.exe
+-rw-r--r-- 1 ftpuser ftpusers 36603464 Mar 13 2000 nwcssp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 170397 Mar 23 2000 nwcsupd1.exe
+-rw-r--r-- 1 ftpuser ftpusers 367734 Feb 14 1995 nwcwin.exe
+-rw-r--r-- 1 ftpuser ftpusers 367002 May 22 1998 nwcwrt.exe
+-rw-r--r-- 1 ftpuser ftpusers 77941 Dec 16 1996 nwda01.exe
+-rw-r--r-- 1 ftpuser ftpusers 147838 May 8 12:21 nwftpd6.exe
+-rw-r--r-- 1 ftpuser ftpusers 128211 Jan 29 15:30 nwipadm4.exe
+-rw-r--r-- 1 ftpuser ftpusers 571623 Nov 1 1995 nwl11e.exe
+-rw-r--r-- 1 ftpuser ftpusers 579046 Nov 1 1995 nwl11f.exe
+-rw-r--r-- 1 ftpuser ftpusers 585907 Nov 1 1995 nwl11g.exe
+-rw-r--r-- 1 ftpuser ftpusers 576676 Nov 1 1995 nwl11i.exe
+-rw-r--r-- 1 ftpuser ftpusers 579137 Nov 1 1995 nwl11s.exe
+-rw-r--r-- 1 ftpuser ftpusers 184137 Dec 13 1995 nwltid.exe
+-rw-r--r-- 1 ftpuser ftpusers 13639159 Apr 9 1999 nwmac.exe
+-rw-r--r-- 1 ftpuser ftpusers 2264037 Mar 23 1999 nwmp2.exe
+-rw-r--r-- 1 ftpuser ftpusers 16902888 Jun 15 2001 nwovly2.exe
+-rw-r--r-- 1 ftpuser ftpusers 2536221 Apr 2 1997 nwpa300.exe
+-rw-r--r-- 1 ftpuser ftpusers 155727 May 7 2001 nwpa5.exe
+-rw-r--r-- 1 ftpuser ftpusers 254835 Dec 7 2001 nwpapt2a.exe
+-rw-r--r-- 1 ftpuser ftpusers 26440 Nov 15 1993 nwparr.exe
+-rw-r--r-- 1 ftpuser ftpusers 1341903 Jun 9 1999 nwpaup1a.exe
+-rw-r--r-- 1 ftpuser ftpusers 7763041 Feb 27 1997 nws25b.exe
+-rw-r--r-- 1 ftpuser ftpusers 73306 Mar 11 1999 nwsaahpr.exe
+-rw-r--r-- 1 ftpuser ftpusers 12091868 Feb 29 2000 nwsb41wa.exe
+-rw-r--r-- 1 ftpuser ftpusers 38819 Aug 11 1999 nwsp2aai.exe
+-rw-r--r-- 1 ftpuser ftpusers 287681 Feb 4 2000 nwsso.exe
+-rw-r--r-- 1 ftpuser ftpusers 112641 Feb 24 2000 nwtape1.exe
+-rw-r--r-- 1 ftpuser ftpusers 66111 Nov 15 1993 nwtlg.exe
+-rw-r--r-- 1 ftpuser ftpusers 126883 Jun 16 1995 o31mci.exe
+-rw-r--r-- 1 ftpuser ftpusers 73911 Jul 25 1995 o4crtl.exe
+-rw-r--r-- 1 ftpuser ftpusers 4957532 Apr 27 1999 odbcbeta.exe
+-rw-r--r-- 1 ftpuser ftpusers 6488080 Nov 8 2001 odemandb.exe
+-rw-r--r-- 1 ftpuser ftpusers 671132 Jul 28 1999 odi33g.exe
+-rw-r--r-- 1 ftpuser ftpusers 409920 Jul 30 1997 odiwan1.exe
+-rw-r--r-- 1 ftpuser ftpusers 48296 Jul 18 1995 of31pt.exe
+-rw-r--r-- 1 ftpuser ftpusers 111219 Sep 18 1993 os2p1.exe
+-rw-r--r-- 1 ftpuser ftpusers 610164 Dec 18 1998 os2pt2.exe
+-rw-r--r-- 1 ftpuser ftpusers 896477 Nov 26 1996 os2u1.exe
+-rw-r--r-- 1 ftpuser ftpusers 4229434 May 30 2000 os4pt1.exe
+-rw-r--r-- 1 ftpuser ftpusers 4227811 Mar 2 2001 os5pt2a.exe
+-rw-r--r-- 1 ftpuser ftpusers 216072 Sep 18 1993 othdrv.exe
+-rw-r--r-- 1 ftpuser ftpusers 66394 Dec 4 1996 ovvmdl.exe
+-rw-r--r-- 1 ftpuser ftpusers 26153 Oct 25 1996 p1000.exe
+-rw-r--r-- 1 ftpuser ftpusers 242785 Jan 3 1995 p10g05.exe
+-rw-r--r-- 1 ftpuser ftpusers 242514 Jan 3 1995 p10i05.exe
+-rw-r--r-- 1 ftpuser ftpusers 242647 Jan 3 1995 p10s05.exe
+-rw-r--r-- 1 ftpuser ftpusers 33893 May 12 1995 p2scsi.exe
+-rw-r--r-- 1 ftpuser ftpusers 659891 May 11 1999 pager1.exe
+-rw-r--r-- 1 ftpuser ftpusers 50002 Sep 18 1993 pat301.exe
+-rw-r--r-- 1 ftpuser ftpusers 50015 Sep 18 1993 pat303.exe
+-rw-r--r-- 1 ftpuser ftpusers 49975 Sep 18 1993 pat304.exe
+-rw-r--r-- 1 ftpuser ftpusers 49878 Sep 18 1993 pat306.exe
+-rw-r--r-- 1 ftpuser ftpusers 29863 Sep 18 1993 pat311.exe
+-rw-r--r-- 1 ftpuser ftpusers 49888 Sep 18 1993 pat312.exe
+-rw-r--r-- 1 ftpuser ftpusers 49823 Sep 18 1993 pat314.exe
+-rw-r--r-- 1 ftpuser ftpusers 33034 Sep 18 1993 pat315.exe
+-rw-r--r-- 1 ftpuser ftpusers 49900 Sep 18 1993 pat317.exe
+-rw-r--r-- 1 ftpuser ftpusers 49858 Sep 18 1993 pat321.exe
+-rw-r--r-- 1 ftpuser ftpusers 49901 Sep 18 1993 pat323.exe
+-rw-r--r-- 1 ftpuser ftpusers 50232 Sep 18 1993 pat326.exe
+-rw-r--r-- 1 ftpuser ftpusers 49842 Sep 18 1993 pat334.exe
+-rw-r--r-- 1 ftpuser ftpusers 49930 Sep 18 1993 pat354.exe
+-rw-r--r-- 1 ftpuser ftpusers 30322 Sep 18 1993 patman.exe
+-rw-r--r-- 1 ftpuser ftpusers 35789 Sep 18 1993 paudfx.exe
+-rw-r--r-- 1 ftpuser ftpusers 565974 Jul 20 1998 pbm21y2k.exe
+-rw-r--r-- 1 ftpuser ftpusers 63237 Jun 15 1994 pburst.exe
+-rw-r--r-- 1 ftpuser ftpusers 35522 Sep 18 1993 pcn22x.exe
+-rw-r--r-- 1 ftpuser ftpusers 25266 Sep 18 1993 pcn23x.exe
+-rw-r--r-- 1 ftpuser ftpusers 46823 Sep 18 1993 pcn2pa.exe
+-rw-r--r-- 1 ftpuser ftpusers 30816 Sep 18 1993 pcnscs.exe
+-rw-r--r-- 1 ftpuser ftpusers 22801 Sep 18 1993 pdf.exe
+-rw-r--r-- 1 ftpuser ftpusers 38018 Sep 18 1993 pdf311.exe
+-rw-r--r-- 1 ftpuser ftpusers 8552350 Aug 27 2001 pdlckcmp.exe
+-rw-r--r-- 1 ftpuser ftpusers 699150 Jan 22 1997 perfectf.exe
+-rw-r--r-- 1 ftpuser ftpusers 214597 Jun 7 2000 pfar6.exe
+-rw-r--r-- 1 ftpuser ftpusers 26618 Sep 18 1993 pfix1.exe
+-rw-r--r-- 1 ftpuser ftpusers 26734 Sep 18 1993 pfix3.exe
+-rw-r--r-- 1 ftpuser ftpusers 23435 Jul 18 1995 pifoff.exe
+-rw-r--r-- 1 ftpuser ftpusers 70799 Aug 26 1999 pkernel.exe
+-rw-r--r-- 1 ftpuser ftpusers 252312 Aug 12 1999 pkis.pdf
+-rw-r--r-- 1 ftpuser ftpusers 772450 Feb 8 2000 pkisnmas.exe
+-rw-r--r-- 1 ftpuser ftpusers 63891 Sep 11 1995 plpd8.exe
+-rw-r--r-- 1 ftpuser ftpusers 212389 Mar 28 12:17 plpdsoc2.exe
+-rw-r--r-- 1 ftpuser ftpusers 181777 Dec 13 1995 pnwtid.exe
+-rw-r--r-- 1 ftpuser ftpusers 186437 Nov 17 1995 pnxtfx.exe
+-rw-r--r-- 1 ftpuser ftpusers 4463887 Jan 3 2001 preds8a.exe
+-rw-r--r-- 1 ftpuser ftpusers 75026 Sep 18 1993 pro10.exe
+-rw-r--r-- 1 ftpuser ftpusers 28781 Nov 1 1995 prog.exe
+-rw-r--r-- 1 ftpuser ftpusers 23802 Sep 18 1993 propth.exe
+-rw-r--r-- 1 ftpuser ftpusers 25129 Jan 29 1996 prt312.exe
+-rw-r--r-- 1 ftpuser ftpusers 31193 Sep 18 1993 prtime.exe
+-rw-r--r-- 1 ftpuser ftpusers 130827 Jul 20 1995 prupc.exe
+-rw-r--r-- 1 ftpuser ftpusers 34893 Sep 18 1993 ps110.exe
+-rw-r--r-- 1 ftpuser ftpusers 25963 Sep 18 1993 ps2286.exe
+-rw-r--r-- 1 ftpuser ftpusers 24576 Sep 18 1993 ps22is.exe
+-rw-r--r-- 1 ftpuser ftpusers 42925 Sep 18 1993 ps2311.exe
+-rw-r--r-- 1 ftpuser ftpusers 29867 Sep 18 1993 ps2386.exe
+-rw-r--r-- 1 ftpuser ftpusers 28205 Sep 18 1993 ps2cmb.exe
+-rw-r--r-- 1 ftpuser ftpusers 23372 Sep 18 1993 ps2esd.exe
+-rw-r--r-- 1 ftpuser ftpusers 24336 Sep 18 1993 ps2fx.exe
+-rw-r--r-- 1 ftpuser ftpusers 76207 Sep 18 1993 ps2isa.exe
+-rw-r--r-- 1 ftpuser ftpusers 23955 Sep 18 1993 ps2m57.exe
+-rw-r--r-- 1 ftpuser ftpusers 36351 Dec 14 1993 ps2opt.exe
+-rw-r--r-- 1 ftpuser ftpusers 162367 Jun 30 1995 ps3x02.exe
+-rw-r--r-- 1 ftpuser ftpusers 85566 Jan 23 1996 ps4x03.exe
+-rw-r--r-- 1 ftpuser ftpusers 172677 Oct 24 2001 psrvr112.exe
+-rw-r--r-- 1 ftpuser ftpusers 52324 Sep 18 1993 ptf286.exe
+-rw-r--r-- 1 ftpuser ftpusers 112633 Sep 18 1993 ptf350.exe
+-rw-r--r-- 1 ftpuser ftpusers 48074 Sep 18 1993 ptf374.exe
+-rw-r--r-- 1 ftpuser ftpusers 39025 Sep 18 1993 ptf378.exe
+-rw-r--r-- 1 ftpuser ftpusers 30600 Sep 18 1993 ptf380.exe
+-rw-r--r-- 1 ftpuser ftpusers 525519 Mar 25 1994 ptf410.exe
+-rw-r--r-- 1 ftpuser ftpusers 501986 Sep 29 1993 ptf411.exe
+-rw-r--r-- 1 ftpuser ftpusers 350294 Sep 29 1993 ptf412.exe
+-rw-r--r-- 1 ftpuser ftpusers 473981 Sep 29 1993 ptf413.exe
+-rw-r--r-- 1 ftpuser ftpusers 480777 Sep 29 1993 ptf414.exe
+-rw-r--r-- 1 ftpuser ftpusers 488223 Sep 29 1993 ptf415.exe
+-rw-r--r-- 1 ftpuser ftpusers 147005 Oct 22 1993 ptf424.exe
+-rw-r--r-- 1 ftpuser ftpusers 109320 Aug 14 1995 ptf425.exe
+-rw-r--r-- 1 ftpuser ftpusers 109064 Oct 18 1993 ptf426.exe
+-rw-r--r-- 1 ftpuser ftpusers 292097 Oct 7 1994 ptf437.exe
+-rw-r--r-- 1 ftpuser ftpusers 464842 Apr 12 1994 ptf569.exe
+-rw-r--r-- 1 ftpuser ftpusers 453054 Nov 16 1994 pu3x01.exe
+-rw-r--r-- 1 ftpuser ftpusers 553603 May 2 1995 pu4x03.exe
+-rw-r--r-- 1 ftpuser ftpusers 280505 May 29 17:29 pwdsnc1.exe
+-rw-r--r-- 1 ftpuser ftpusers 758411 May 31 11:45 pxy031.exe
+-rw-r--r-- 1 ftpuser ftpusers 785651 Aug 31 2000 pxyauth.exe
+-rw-r--r-- 1 ftpuser ftpusers 25270 Nov 1 1995 qa.exe
+-rw-r--r-- 1 ftpuser ftpusers 22674 Jul 25 1995 qaos2.exe
+-rw-r--r-- 1 ftpuser ftpusers 21421 Jul 18 1995 qkinst.exe
+-rw-r--r-- 1 ftpuser ftpusers 390862 Dec 4 1995 qlfix.exe
+-rw-r--r-- 1 ftpuser ftpusers 21028 Oct 5 1995 quest.exe
+-rw-r--r-- 1 ftpuser ftpusers 13847723 Nov 23 1998 r16524br.exe
+-rw-r--r-- 1 ftpuser ftpusers 15367885 Nov 23 1998 r16524ce.exe
+-rw-r--r-- 1 ftpuser ftpusers 13974046 Nov 23 1998 r16524cf.exe
+-rw-r--r-- 1 ftpuser ftpusers 14636368 Nov 23 1998 r16524cs.exe
+-rw-r--r-- 1 ftpuser ftpusers 14606815 Nov 23 1998 r16524ct.exe
+-rw-r--r-- 1 ftpuser ftpusers 14396472 Nov 23 1998 r16524cz.exe
+-rw-r--r-- 1 ftpuser ftpusers 14526983 Nov 23 1998 r16524de.exe
+-rw-r--r-- 1 ftpuser ftpusers 13996151 Nov 23 1998 r16524dk.exe
+-rw-r--r-- 1 ftpuser ftpusers 14083999 Nov 24 1998 r16524es.exe
+-rw-r--r-- 1 ftpuser ftpusers 14036283 Nov 24 1998 r16524fr.exe
+-rw-r--r-- 1 ftpuser ftpusers 14168496 Nov 24 1998 r16524it.exe
+-rw-r--r-- 1 ftpuser ftpusers 15194180 Nov 30 1998 r16524jp.exe
+-rw-r--r-- 1 ftpuser ftpusers 14717983 Nov 24 1998 r16524kr.exe
+-rw-r--r-- 1 ftpuser ftpusers 13957605 Nov 24 1998 r16524ma.exe
+-rw-r--r-- 1 ftpuser ftpusers 13018097 Nov 24 1998 r16524nl.exe
+-rw-r--r-- 1 ftpuser ftpusers 14282386 Nov 24 1998 r16524no.exe
+-rw-r--r-- 1 ftpuser ftpusers 14592999 Nov 24 1998 r16524oz.exe
+-rw-r--r-- 1 ftpuser ftpusers 14683051 Nov 24 1998 r16524pl.exe
+-rw-r--r-- 1 ftpuser ftpusers 13868667 Nov 24 1998 r16524ru.exe
+-rw-r--r-- 1 ftpuser ftpusers 14075816 Nov 24 1998 r16524su.exe
+-rw-r--r-- 1 ftpuser ftpusers 14090792 Nov 24 1998 r16524sv.exe
+-rw-r--r-- 1 ftpuser ftpusers 14598010 Nov 24 1998 r16524uk.exe
+-rw-r--r-- 1 ftpuser ftpusers 14633171 Nov 23 1998 r16524us.exe
+-rw-r--r-- 1 ftpuser ftpusers 13892746 Oct 8 1999 r16525br.exe
+-rw-r--r-- 1 ftpuser ftpusers 15412960 Oct 8 1999 r16525ce.exe
+-rw-r--r-- 1 ftpuser ftpusers 14019075 Oct 8 1999 r16525cf.exe
+-rw-r--r-- 1 ftpuser ftpusers 14681320 Oct 8 1999 r16525cs.exe
+-rw-r--r-- 1 ftpuser ftpusers 14651741 Oct 8 1999 r16525ct.exe
+-rw-r--r-- 1 ftpuser ftpusers 14443408 Oct 8 1999 r16525cz.exe
+-rw-r--r-- 1 ftpuser ftpusers 14572069 Aug 24 1999 r16525de.exe
+-rw-r--r-- 1 ftpuser ftpusers 14041141 Oct 8 1999 r16525dk.exe
+-rw-r--r-- 1 ftpuser ftpusers 14129003 Oct 8 1999 r16525es.exe
+-rw-r--r-- 1 ftpuser ftpusers 14081224 Oct 8 1999 r16525fr.exe
+-rw-r--r-- 1 ftpuser ftpusers 14213472 Oct 8 1999 r16525it.exe
+-rw-r--r-- 1 ftpuser ftpusers 15239217 Oct 8 1999 r16525jp.exe
+-rw-r--r-- 1 ftpuser ftpusers 14762877 Oct 8 1999 r16525kr.exe
+-rw-r--r-- 1 ftpuser ftpusers 14004580 Oct 8 1999 r16525ma.exe
+-rw-r--r-- 1 ftpuser ftpusers 14520305 Oct 8 1999 r16525nl.exe
+-rw-r--r-- 1 ftpuser ftpusers 14327373 Oct 8 1999 r16525no.exe
+-rw-r--r-- 1 ftpuser ftpusers 14638087 Oct 8 1999 r16525oz.exe
+-rw-r--r-- 1 ftpuser ftpusers 14730025 Oct 8 1999 r16525pl.exe
+-rw-r--r-- 1 ftpuser ftpusers 13915653 Oct 8 1999 r16525ru.exe
+-rw-r--r-- 1 ftpuser ftpusers 14120807 Oct 8 1999 r16525su.exe
+-rw-r--r-- 1 ftpuser ftpusers 14135776 Oct 8 1999 r16525sv.exe
+-rw-r--r-- 1 ftpuser ftpusers 14643087 Oct 8 1999 r16525uk.exe
+-rw-r--r-- 1 ftpuser ftpusers 14678258 Aug 24 1999 r16525us.exe
+-rw-r--r-- 1 ftpuser ftpusers 34195974 Nov 20 1998 r524east.exe
+-rw-r--r-- 1 ftpuser ftpusers 29934184 Nov 21 1998 r524kcc.exe
+-rw-r--r-- 1 ftpuser ftpusers 40546603 Nov 21 1998 r524mult.exe
+-rw-r--r-- 1 ftpuser ftpusers 33377124 Nov 21 1998 r524scan.exe
+-rw-r--r-- 1 ftpuser ftpusers 17133749 Nov 21 1998 r524us.exe
+-rw-r--r-- 1 ftpuser ftpusers 20919099 Nov 21 1998 r524usde.exe
+-rw-r--r-- 1 ftpuser ftpusers 21776910 Dec 21 1998 r524usjp.exe
+-rw-r--r-- 1 ftpuser ftpusers 33695357 Oct 11 1999 r525east.exe
+-rw-r--r-- 1 ftpuser ftpusers 29387389 Oct 11 1999 r525kcc.exe
+-rw-r--r-- 1 ftpuser ftpusers 39896412 Oct 11 1999 r525mult.exe
+-rw-r--r-- 1 ftpuser ftpusers 32843372 Oct 11 1999 r525scan.exe
+-rw-r--r-- 1 ftpuser ftpusers 18205814 Oct 11 1999 r525us.exe
+-rw-r--r-- 1 ftpuser ftpusers 20711743 Oct 11 1999 r525usde.exe
+-rw-r--r-- 1 ftpuser ftpusers 19744627 Oct 14 1999 r525usjp.exe
+-rw-r--r-- 1 ftpuser ftpusers 21883384 Feb 25 1999 r551en.exe
+-rw-r--r-- 1 ftpuser ftpusers 38653860 Feb 26 1999 r551mult.exe
+-rw-r--r-- 1 ftpuser ftpusers 38136537 Feb 25 1999 r551scan.exe
+-rw-r--r-- 1 ftpuser ftpusers 37890181 Oct 19 1999 r552mult.exe
+-rw-r--r-- 1 ftpuser ftpusers 37404535 Oct 19 1999 r552scan.exe
+-rw-r--r-- 1 ftpuser ftpusers 22183381 Feb 11 2000 r553aen.exe
+-rw-r--r-- 1 ftpuser ftpusers 32216751 Feb 11 2000 r553aest.exe
+-rw-r--r-- 1 ftpuser ftpusers 24925427 Feb 11 2000 r553ajp.exe
+-rw-r--r-- 1 ftpuser ftpusers 27749699 Feb 11 2000 r553akcc.exe
+-rw-r--r-- 1 ftpuser ftpusers 38006860 Feb 11 2000 r553amlt.exe
+-rw-r--r-- 1 ftpuser ftpusers 37454601 Feb 11 2000 r553ascn.exe
+-rw-r--r-- 1 ftpuser ftpusers 87518 Apr 6 1998 rad102.exe
+-rw-r--r-- 1 ftpuser ftpusers 260651 Nov 23 1998 rad104.exe
+-rw-r--r-- 1 ftpuser ftpusers 118100 Mar 4 16:09 radatr4.exe
+-rw-r--r-- 1 ftpuser ftpusers 575303 Feb 28 1997 ramac.exe
+-rw-r--r-- 1 ftpuser ftpusers 66500 Jun 7 1995 rbuild.exe
+-rw-r--r-- 1 ftpuser ftpusers 23539 Feb 27 1998 rdmsg0.exe
+-rw-r--r-- 1 ftpuser ftpusers 91403 Apr 3 2000 revfhrft.exe
+-rw-r--r-- 1 ftpuser ftpusers 138359 Jul 27 2001 rinstall.exe
+-rw-r--r-- 1 ftpuser ftpusers 211615 Nov 4 1993 ripsap.exe
+-rw-r--r-- 1 ftpuser ftpusers 192641 Sep 18 1993 rlit92.exe
+-rw-r--r-- 1 ftpuser ftpusers 155252 Sep 18 1993 rlit93.exe
+-rw-r--r-- 1 ftpuser ftpusers 780218 Jul 18 1995 rofwin.exe
+-rw-r--r-- 1 ftpuser ftpusers 36120 Sep 18 1993 routez.exe
+-rw-r--r-- 1 ftpuser ftpusers 96684 Sep 18 1993 routfx.exe
+-rw-r--r-- 1 ftpuser ftpusers 141605 Oct 5 1998 rplkt5.exe
+-rw-r--r-- 1 ftpuser ftpusers 72596 May 11 1994 rsync1.exe
+-rw-r--r-- 1 ftpuser ftpusers 23294 Sep 18 1993 rxnet.exe
+-rw-r--r-- 1 ftpuser ftpusers 48534006 Mar 28 2001 rzfd3sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 39313 Jun 16 1994 saa005.exe
+-rw-r--r-- 1 ftpuser ftpusers 534003 Aug 16 2001 saa008.exe
+-rw-r--r-- 1 ftpuser ftpusers 80219 Aug 11 1994 saa010.exe
+-rw-r--r-- 1 ftpuser ftpusers 142620 Sep 1 1994 saa012.exe
+-rw-r--r-- 1 ftpuser ftpusers 943059 Aug 16 2001 saa023.exe
+-rw-r--r-- 1 ftpuser ftpusers 154805 Dec 24 1996 saa031.exe
+-rw-r--r-- 1 ftpuser ftpusers 4659719 Aug 16 2001 saa20040.exe
+-rw-r--r-- 1 ftpuser ftpusers 2550556 Aug 16 2001 saa21030.exe
+-rw-r--r-- 1 ftpuser ftpusers 63688405 Aug 15 2001 saa22010.exe
+-rw-r--r-- 1 ftpuser ftpusers 9396094 Aug 15 2001 saa2210e.exe
+-rw-r--r-- 1 ftpuser ftpusers 10338946 Aug 15 2001 saa30020.exe
+-rw-r--r-- 1 ftpuser ftpusers 9315931 Aug 15 2001 saa40020.exe
+-rw-r--r-- 1 ftpuser ftpusers 169062 Jun 29 2000 saa4pt1.exe
+-rw-r--r-- 1 ftpuser ftpusers 20269 Jun 27 1995 saamic.exe
+-rw-r--r-- 1 ftpuser ftpusers 1646 Jun 3 1998 saapatch.txt
+-rw-r--r-- 1 ftpuser ftpusers 32233 Nov 30 1995 saarot.exe
+-rw-r--r-- 1 ftpuser ftpusers 29347 Aug 3 1995 saaupg.exe
+-rw-r--r-- 1 ftpuser ftpusers 282396 Jun 26 1996 sback6.exe
+-rw-r--r-- 1 ftpuser ftpusers 376293 Jun 7 2001 sbcon1.exe
+-rw-r--r-- 1 ftpuser ftpusers 564045 Jul 20 1998 sbm21y2k.exe
+-rw-r--r-- 1 ftpuser ftpusers 8341393 Mar 2 2001 sbs5pt2a.exe
+-rw-r--r-- 1 ftpuser ftpusers 244604 Dec 5 1997 schcmp2.exe
+-rw-r--r-- 1 ftpuser ftpusers 151673 Aug 12 1999 scmddoc.exe
+-rw-r--r-- 1 ftpuser ftpusers 167290 May 4 2001 scmdflt.exe
+-rw-r--r-- 1 ftpuser ftpusers 40595 Sep 18 1993 sec286.exe
+-rw-r--r-- 1 ftpuser ftpusers 29661 May 25 1994 secdoc.exe
+-rw-r--r-- 1 ftpuser ftpusers 310609 Sep 18 1993 secdos.exe
+-rw-r--r-- 1 ftpuser ftpusers 7039614 May 26 2000 secexp64.01
+-rw-r--r-- 1 ftpuser ftpusers 5016153 Aug 10 2000 secexp64.z
+-rw-r--r-- 1 ftpuser ftpusers 167904 Nov 15 1993 seclog.exe
+-rw-r--r-- 1 ftpuser ftpusers 632669 Sep 18 1993 secnns.exe
+-rw-r--r-- 1 ftpuser ftpusers 449590 Sep 18 1993 secprn.exe
+-rw-r--r-- 1 ftpuser ftpusers 322007 Sep 18 1993 secsys.exe
+-rw-r--r-- 1 ftpuser ftpusers 7351040 May 26 2000 secus64.01
+-rw-r--r-- 1 ftpuser ftpusers 5457221 Aug 10 2000 secus64.z
+-rw-r--r-- 1 ftpuser ftpusers 510733 Sep 18 1993 secut1.exe
+-rw-r--r-- 1 ftpuser ftpusers 556873 Sep 18 1993 secut2.exe
+-rw-r--r-- 1 ftpuser ftpusers 431691 Sep 18 1993 secut3.exe
+-rw-r--r-- 1 ftpuser ftpusers 134525 Aug 29 2001 servinst.exe
+-rw-r--r-- 1 ftpuser ftpusers 367009 May 25 1996 setdoc.exe
+-rw-r--r-- 1 ftpuser ftpusers 24541 Sep 18 1993 setfx.exe
+-rw-r--r-- 1 ftpuser ftpusers 56904 Sep 18 1993 setupc.exe
+-rw-r--r-- 1 ftpuser ftpusers 75076 Jul 21 1997 sftpt2.exe
+-rw-r--r-- 1 ftpuser ftpusers 67817 Sep 18 1993 sftutl.exe
+-rw-r--r-- 1 ftpuser ftpusers 49345 Sep 2 1999 showenv1.exe
+-rw-r--r-- 1 ftpuser ftpusers 25774 Nov 6 1997 silent.exe
+-rw-r--r-- 1 ftpuser ftpusers 181878 Apr 23 09:38 slp107g.exe
+-rw-r--r-- 1 ftpuser ftpusers 184680 Aug 28 2001 slpinsp3.exe
+-rw-r--r-- 1 ftpuser ftpusers 59254 Sep 18 1993 smfsel.exe
+-rw-r--r-- 1 ftpuser ftpusers 2747104 Jan 31 1997 smsup6.exe
+-rw-r--r-- 1 ftpuser ftpusers 131884 Sep 25 1996 smt121.exe
+-rw-r--r-- 1 ftpuser ftpusers 105031 Nov 10 1993 smt171.exe
+-rw-r--r-- 1 ftpuser ftpusers 1764462 May 24 1996 smtos2.exe
+-rw-r--r-- 1 ftpuser ftpusers 322954 Oct 11 1996 smtp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 441783 May 19 1997 smtp2.exe
+-rw-r--r-- 1 ftpuser ftpusers 2862615 Sep 19 1997 smtp3.exe
+-rw-r--r-- 1 ftpuser ftpusers 2882529 Jul 14 1998 smtp4.exe
+-rw-r--r-- 1 ftpuser ftpusers 53164 Aug 1 1995 smtslp.exe
+-rw-r--r-- 1 ftpuser ftpusers 298148 Jun 4 1996 sna31a.exe
+-rw-r--r-- 1 ftpuser ftpusers 148302 Mar 7 10:28 snmpfix.exe
+-rw-r--r-- 1 ftpuser ftpusers 147265 Jul 20 1995 snupd2.exe
+-rw-r--r-- 1 ftpuser ftpusers 163195 Jul 20 1995 snupdu.exe
+-rw-r--r-- 1 ftpuser ftpusers 12675957 Jul 14 2000 sp1_ce.02
+-rw-r--r-- 1 ftpuser ftpusers 9547499 Apr 25 2000 sp1_edir.02
+-rw-r--r-- 1 ftpuser ftpusers 230035 Sep 20 1999 sp3to3a.exe
+-rw-r--r-- 1 ftpuser ftpusers 74372 Feb 25 1998 spanish.exe
+-rw-r--r-- 1 ftpuser ftpusers 5323860 May 23 1996 srapi.exe
+-rw-r--r-- 1 ftpuser ftpusers 38967 Nov 8 1996 srout4.exe
+-rw-r--r-- 1 ftpuser ftpusers 812158 Jul 24 2000 srvinst.exe
+-rw-r--r-- 1 ftpuser ftpusers 54912 Aug 23 1996 srvmn1.exe
+-rw-r--r-- 1 ftpuser ftpusers 38078 Jun 30 1995 stampd.exe
+-rw-r--r-- 1 ftpuser ftpusers 126042 Feb 21 11:15 strmft1.exe
+-rw-r--r-- 1 ftpuser ftpusers 250945 Aug 23 2000 strtl8a.exe
+-rw-r--r-- 1 ftpuser ftpusers 36114 Mar 8 1999 stufkey5.exe
+-rw-r--r-- 1 ftpuser ftpusers 52344 Feb 11 1998 stylbr.exe
+-rw-r--r-- 1 ftpuser ftpusers 50098 Apr 14 1997 stylct.exe
+-rw-r--r-- 1 ftpuser ftpusers 35645 Feb 11 1998 styles.exe
+-rw-r--r-- 1 ftpuser ftpusers 37446 Feb 11 1998 stylfr.exe
+-rw-r--r-- 1 ftpuser ftpusers 77671 Apr 14 1997 stylha.exe
+-rw-r--r-- 1 ftpuser ftpusers 36626 Feb 12 1998 stylit.exe
+-rw-r--r-- 1 ftpuser ftpusers 55674 Apr 16 1997 stylsc.exe
+-rw-r--r-- 1 ftpuser ftpusers 37297 Dec 12 1995 supwin.exe
+-rw-r--r-- 1 ftpuser ftpusers 186171 Sep 18 1993 sys233.exe
+-rw-r--r-- 1 ftpuser ftpusers 159178 Sep 18 1993 sys368.exe
+-rw-r--r-- 1 ftpuser ftpusers 161608 May 10 1995 sys376.exe
+-rw-r--r-- 1 ftpuser ftpusers 61914 Sep 18 1993 syschk.exe
+-rw-r--r-- 1 ftpuser ftpusers 81300 Sep 18 1993 syslod.exe
+-rw-r--r-- 1 ftpuser ftpusers 418576 Jul 14 1997 tabnd2a.exe
+-rw-r--r-- 1 ftpuser ftpusers 9831871 Jan 13 1997 tambt1.exe
+-rw-r--r-- 1 ftpuser ftpusers 172843 May 4 1998 tback3.exe
+-rw-r--r-- 1 ftpuser ftpusers 58235 Sep 1 1998 tbox7.exe
+-rw-r--r-- 1 ftpuser ftpusers 174964 May 4 1998 tcopy2.exe
+-rw-r--r-- 1 ftpuser ftpusers 781603 May 18 1998 tcp312.exe
+-rw-r--r-- 1 ftpuser ftpusers 1223056 Aug 1 2001 tcp542u.exe
+-rw-r--r-- 1 ftpuser ftpusers 1124221 Apr 10 08:49 tcp553v.exe
+-rw-r--r-- 1 ftpuser ftpusers 1221375 May 24 16:03 tcp590s.exe
+-rw-r--r-- 1 ftpuser ftpusers 315088 Apr 15 10:50 tcp604m.exe
+-rw-r--r-- 1 ftpuser ftpusers 1118361 May 7 11:27 tcp604s.exe
+-rw-r--r-- 1 ftpuser ftpusers 6902826 Feb 9 1998 tel01a.exe
+-rw-r--r-- 1 ftpuser ftpusers 97084 Jan 21 1998 tel40d.exe
+-rw-r--r-- 1 ftpuser ftpusers 33888 Sep 18 1993 tim286.exe
+-rw-r--r-- 1 ftpuser ftpusers 20453 Sep 18 1993 tim386.exe
+-rw-r--r-- 1 ftpuser ftpusers 105037 Nov 2 1998 timefx.exe
+-rw-r--r-- 1 ftpuser ftpusers 31521 Mar 29 1994 tokws.exe
+-rw-r--r-- 1 ftpuser ftpusers 123321 Jan 25 2001 trpmon.exe
+-rw-r--r-- 1 ftpuser ftpusers 26100 Sep 18 1993 trxnet.exe
+-rw-r--r-- 1 ftpuser ftpusers 88579 Aug 21 1996 tsa410.exe
+-rw-r--r-- 1 ftpuser ftpusers 724997 Jun 25 10:29 tsa5up9.exe
+-rw-r--r-- 1 ftpuser ftpusers 127632 Jan 12 2000 tsands.exe
+-rw-r--r-- 1 ftpuser ftpusers 20771 Nov 2 1999 ttsy2k.exe
+-rw-r--r-- 1 ftpuser ftpusers 19933 Sep 22 1995 ttyncs.exe
+-rw-r--r-- 1 ftpuser ftpusers 834048 Aug 3 1994 tux001.tar
+-rw-r--r-- 1 ftpuser ftpusers 845824 Aug 3 1994 tux002.tar
+-rw-r--r-- 1 ftpuser ftpusers 1034240 Aug 3 1994 tux003.tar
+-rw-r--r-- 1 ftpuser ftpusers 2764800 Aug 3 1994 tux004.tar
+-rw-r--r-- 1 ftpuser ftpusers 2836480 Aug 3 1994 tux005.tar
+-rw-r--r-- 1 ftpuser ftpusers 1252352 Aug 3 1994 tux006.tar
+-rw-r--r-- 1 ftpuser ftpusers 1355264 Aug 3 1994 tux007.tar
+-rw-r--r-- 1 ftpuser ftpusers 1710080 Aug 3 1994 tux008.tar
+-rw-r--r-- 1 ftpuser ftpusers 3502080 Aug 3 1994 tux009.tar
+-rw-r--r-- 1 ftpuser ftpusers 3573760 Aug 3 1994 tux010.tar
+-rw-r--r-- 1 ftpuser ftpusers 258236 Aug 4 1994 tux011.exe
+-rw-r--r-- 1 ftpuser ftpusers 258250 Aug 4 1994 tux012.exe
+-rw-r--r-- 1 ftpuser ftpusers 304451 Aug 4 1994 tux013.exe
+-rw-r--r-- 1 ftpuser ftpusers 602748 Aug 4 1994 tux014.exe
+-rw-r--r-- 1 ftpuser ftpusers 351070 Aug 4 1994 tux015.exe
+-rw-r--r-- 1 ftpuser ftpusers 460026 Aug 4 1994 tux016.exe
+-rw-r--r-- 1 ftpuser ftpusers 34077 Sep 18 1993 ucpy.exe
+-rw-r--r-- 1 ftpuser ftpusers 103354 Sep 18 1993 udf355.exe
+-rw-r--r-- 1 ftpuser ftpusers 6994 Feb 22 12:49 unixinf1.tgz
+-rw-r--r-- 1 ftpuser ftpusers 107260 May 8 14:56 unixins2.tgz
+-rw-r--r-- 1 ftpuser ftpusers 25508 Sep 18 1993 unld.exe
+-rw-r--r-- 1 ftpuser ftpusers 30830 Sep 18 1993 unps2.exe
+-rw-r--r-- 1 ftpuser ftpusers 34857 Sep 18 1993 up215c.exe
+-rw-r--r-- 1 ftpuser ftpusers 509794 Jan 6 1994 upd311.exe
+-rw-r--r-- 1 ftpuser ftpusers 24330 Sep 18 1993 ups.exe
+-rw-r--r-- 1 ftpuser ftpusers 20837 Sep 18 1993 ups22.exe
+-rw-r--r-- 1 ftpuser ftpusers 45537 Sep 18 1993 upsm70.exe
+-rw-r--r-- 1 ftpuser ftpusers 43743 Jul 22 1997 upsnlm.exe
+-rw-r--r-- 1 ftpuser ftpusers 28519 Oct 13 1998 userhlp.exe
+-rw-r--r-- 1 ftpuser ftpusers 134247 Sep 18 1993 ut1192.exe
+-rw-r--r-- 1 ftpuser ftpusers 1632088 Jan 14 1998 uxp203.exe
+-rw-r--r-- 1 ftpuser ftpusers 1389072 Nov 19 1998 uxp205.exe
+-rw-r--r-- 1 ftpuser ftpusers 1341347 Jun 6 2001 uxp23j.exe
+-rw-r--r-- 1 ftpuser ftpusers 1098502 Nov 2 1995 uxpsft.exe
+-rw-r--r-- 1 ftpuser ftpusers 58761 Nov 1 1995 v2014.exe
+-rw-r--r-- 1 ftpuser ftpusers 40944 Sep 18 1993 v286ol.exe
+-rw-r--r-- 1 ftpuser ftpusers 95133 Jun 8 2001 v_nfs914.exe
+-rw-r--r-- 1 ftpuser ftpusers 34278 Sep 18 1993 vapvol.exe
+-rw-r--r-- 1 ftpuser ftpusers 38035 Sep 18 1993 vgetsc.exe
+-rw-r--r-- 1 ftpuser ftpusers 52323 Sep 18 1993 volinf.exe
+-rw-r--r-- 1 ftpuser ftpusers 2444080 May 3 2001 vpn35e.exe
+-rw-r--r-- 1 ftpuser ftpusers 3838701 Feb 13 14:50 vpn36d.exe
+-rw-r--r-- 1 ftpuser ftpusers 3837285 Feb 13 14:48 vpn36e.exe
+-rw-r--r-- 1 ftpuser ftpusers 96158 Aug 31 1999 vpnbs01.exe
+-rw-r--r-- 1 ftpuser ftpusers 49835 Sep 18 1993 vrepfa.exe
+-rw-r--r-- 1 ftpuser ftpusers 86326 Sep 18 1993 vrp215.exe
+-rw-r--r-- 1 ftpuser ftpusers 59336 Jul 31 1997 vrp386.exe
+-rw-r--r-- 1 ftpuser ftpusers 43438 Jul 31 1997 vrpa286.exe
+-rw-r--r-- 1 ftpuser ftpusers 486274 Sep 18 1993 vrpels.exe
+-rw-r--r-- 1 ftpuser ftpusers 103958 Sep 18 1993 vrpps2.exe
+-rw-r--r-- 1 ftpuser ftpusers 699367 Sep 9 1999 w2n213.exe
+-rw-r--r-- 1 ftpuser ftpusers 771404 Sep 25 2000 w2ny2k.exe
+-rw-r--r-- 1 ftpuser ftpusers 240179 Jun 4 1996 wan31a.exe
+-rw-r--r-- 1 ftpuser ftpusers 135592 Jun 16 1995 wanx02.exe
+-rw-r--r-- 1 ftpuser ftpusers 79254744 Jul 20 2001 was351.exe
+-rw-r--r-- 1 ftpuser ftpusers 28100 Sep 18 1993 watch.exe
+-rw-r--r-- 1 ftpuser ftpusers 7884641 Apr 23 10:17 waview71.exe
+-rw-r--r-- 1 ftpuser ftpusers 182961 Jul 9 2001 wbdav51.exe
+-rw-r--r-- 1 ftpuser ftpusers 53418 Aug 21 1996 web002.exe
+-rw-r--r-- 1 ftpuser ftpusers 182219 Mar 1 2001 webdv51.exe
+-rw-r--r-- 1 ftpuser ftpusers 2574191 Oct 5 1999 weblsp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 3589741 Jun 22 1998 webser31.exe
+-rw-r--r-- 1 ftpuser ftpusers 27063 Nov 1 1995 welcom.exe
+-rw-r--r-- 1 ftpuser ftpusers 1887450 Apr 23 2001 winntwms.exe
+-rw-r--r-- 1 ftpuser ftpusers 28104 Jul 17 1997 wkstrk.exe
+-rw-r--r-- 1 ftpuser ftpusers 52547 Jul 20 1995 wpca31.exe
+-rw-r--r-- 1 ftpuser ftpusers 89314 Jul 20 1995 wpfm31.exe
+-rw-r--r-- 1 ftpuser ftpusers 41008 Sep 11 1995 wpmdm2.exe
+-rw-r--r-- 1 ftpuser ftpusers 36824 Jul 18 1995 wpofus.exe
+-rw-r--r-- 1 ftpuser ftpusers 51398 Jul 20 1995 wprp31.exe
+-rw-r--r-- 1 ftpuser ftpusers 21092 Jul 18 1995 wpvwin.exe
+-rw-r--r-- 1 ftpuser ftpusers 52738 Mar 15 1994 wrkdoc.exe
+-rw-r--r-- 1 ftpuser ftpusers 683395 May 29 1997 ws250c.exe
+-rw-r--r-- 1 ftpuser ftpusers 765234 May 29 1997 ws251c.exe
+-rw-r--r-- 1 ftpuser ftpusers 212586 May 9 1997 ws3tk2b.exe
+-rw-r--r-- 1 ftpuser ftpusers 371 Mar 7 12:40 ws_ftp.log
+-rw-r--r-- 1 ftpuser ftpusers 1507075 Jun 29 1999 wtmc1.exe
+-rw-r--r-- 1 ftpuser ftpusers 32697 Jun 16 1995 x400fx.exe
+-rw-r--r-- 1 ftpuser ftpusers 660700 Jun 9 1999 x400nlm1.exe
+-rw-r--r-- 1 ftpuser ftpusers 1134477 Aug 5 1999 x400os21.exe
+-rw-r--r-- 1 ftpuser ftpusers 156483 Jan 30 17:09 xconss9f.exe
+-rw-r--r-- 1 ftpuser ftpusers 49801 Sep 18 1993 xld386.exe
+-rw-r--r-- 1 ftpuser ftpusers 1043685 Jul 27 2001 zd2dmi1.exe
+-rw-r--r-- 1 ftpuser ftpusers 365016 Dec 19 2001 zd2dstfx.exe
+-rw-r--r-- 1 ftpuser ftpusers 338947 Jul 20 2001 zd2scan.exe
+-rw-r--r-- 1 ftpuser ftpusers 24341430 Mar 1 09:27 zd322k1.exe
+-rw-r--r-- 1 ftpuser ftpusers 24350056 Mar 1 09:35 zd322k2.exe
+-rw-r--r-- 1 ftpuser ftpusers 95330 Mar 14 18:03 zd32dupw.exe
+-rw-r--r-- 1 ftpuser ftpusers 26945103 Mar 1 09:16 zd32nw.exe
+-rw-r--r-- 1 ftpuser ftpusers 26937023 Mar 1 09:21 zd32nw4.exe
+-rw-r--r-- 1 ftpuser ftpusers 26944994 Feb 5 12:18 zd32nw5.exe
+-rw-r--r-- 1 ftpuser ftpusers 24349972 Jul 29 2001 zd32p2k1.exe
+-rw-r--r-- 1 ftpuser ftpusers 24349896 Jul 29 2001 zd32p2k2.exe
+-rw-r--r-- 1 ftpuser ftpusers 26936865 Jul 29 2001 zd32pnw4.exe
+-rw-r--r-- 1 ftpuser ftpusers 26944944 Jul 29 2001 zd32pnw5.exe
+-rw-r--r-- 1 ftpuser ftpusers 128456 Jan 29 14:50 zd3awsr1.exe
+-rw-r--r-- 1 ftpuser ftpusers 1242985 Dec 19 2001 zd3ccpp.exe
+-rw-r--r-- 1 ftpuser ftpusers 304923 Dec 19 2001 zd3dac.exe
+-rw-r--r-- 1 ftpuser ftpusers 537680 Feb 22 15:31 zd3dupws.exe
+-rw-r--r-- 1 ftpuser ftpusers 142260 Nov 8 2001 zd3dxsnp.exe
+-rw-r--r-- 1 ftpuser ftpusers 94250 Dec 4 2001 zd3idnt1.exe
+-rw-r--r-- 1 ftpuser ftpusers 1443132 Jan 2 2002 zd3ntmsi.exe
+-rw-r--r-- 1 ftpuser ftpusers 15512136 Dec 20 2001 zd3o8i1.exe
+-rw-r--r-- 1 ftpuser ftpusers 15575094 Dec 19 2001 zd3o8i2.exe
+-rw-r--r-- 1 ftpuser ftpusers 204858 May 24 10:03 zd3rosnp.exe
+-rw-r--r-- 1 ftpuser ftpusers 298505 Jan 9 2002 zd3scn.exe
+-rw-r--r-- 1 ftpuser ftpusers 149726 Dec 18 2001 zd3wm95.exe
+-rw-r--r-- 1 ftpuser ftpusers 180523 Jan 9 2002 zd3wminv.exe
+-rw-r--r-- 1 ftpuser ftpusers 166988 Dec 18 2001 zd3wmsch.exe
+-rw-r--r-- 1 ftpuser ftpusers 195447 Dec 19 2001 zd3wsmg.exe
+-rw-r--r-- 1 ftpuser ftpusers 381437 Mar 19 14:21 zd3xwsrg.exe
+-rw-r--r-- 1 ftpuser ftpusers 125036 Apr 18 15:53 zd3xwuol.exe
+-rw-r--r-- 1 ftpuser ftpusers 180429 Apr 18 16:30 zd3xzisw.exe
+-rw-r--r-- 1 ftpuser ftpusers 226961 Mar 14 10:01 zd3zpol.exe
+-rw-r--r-- 1 ftpuser ftpusers 1274878 May 1 17:41 zenintg.exe
+-rw-r--r-- 1 ftpuser ftpusers 1865 May 9 2001 zenstart.txt
+-rw-r--r-- 1 ftpuser ftpusers 2408726 Feb 23 2001 zfd2pt3b.exe
+-rw-r--r-- 1 ftpuser ftpusers 62396652 Mar 21 2000 zfd2sp1.exe
+-rw-r--r-- 1 ftpuser ftpusers 293221 May 22 2001 zfd2tsfx.exe
+-rw-r--r-- 1 ftpuser ftpusers 97309 Oct 20 2000 zfd3site.exe
+-rw-r--r-- 1 ftpuser ftpusers 46890033 Nov 23 2001 zfd3sp1a.exe
+-rw-r--r-- 1 ftpuser ftpusers 31452136 Aug 10 2000 zfn101.exe
+-rw-r--r-- 1 ftpuser ftpusers 1647316 Nov 23 2001 zfs2jvm.exe
+-rw-r--r-- 1 ftpuser ftpusers 215046 Dec 18 2001 zfs2pt1.exe
+-rw-r--r-- 1 ftpuser ftpusers 262640 Dec 20 2001 zfs2sel.exe
+-rw-r--r-- 1 ftpuser ftpusers 54876346 Jan 18 14:59 zfs2sp1a.exe
+-rw-r--r-- 1 ftpuser ftpusers 1903657 Feb 5 2001 zfsdbpb.exe
+-rw-r--r-- 1 ftpuser ftpusers 96356 Oct 11 2000 zisclr.exe
+-rw-r--r-- 1 ftpuser ftpusers 94293 Dec 19 2001 zs2dbbk.exe
+-rw-r--r-- 1 ftpuser ftpusers 141307 Dec 20 2001 zs2ipg.exe
+-rw-r--r-- 1 ftpuser ftpusers 1173802 Dec 19 2001 zs2mibs.exe
+-rw-r--r-- 1 ftpuser ftpusers 943931 Jan 23 08:51 zs2ndst1.exe
+-rw-r--r-- 1 ftpuser ftpusers 516401 Jun 4 13:23 zs2rbs.exe
+-rw-r--r-- 1 ftpuser ftpusers 1095584 Mar 13 11:41 zs2smtp.exe
+-rw-r--r-- 1 ftpuser ftpusers 981014 Dec 5 2001 zs2util2.exe
+-rw-r--r-- 1 ftpuser ftpusers 92151 Jun 6 11:14 zs3sch.exe
+-rw-r--r-- 1 ftpuser ftpusers 1039314 Oct 13 1998 zw100p1.exe
+-rw-r--r-- 1 ftpuser ftpusers 194420 Nov 6 1998 zw101p1.exe
+-rw-r--r-- 1 ftpuser ftpusers 1065727 Nov 22 1999 zw110p3.exe
+226 Listing completed.
+ftp> close
+221-Thank you and have a nice day.
+221
+ftp> \ No newline at end of file
diff --git a/netwerk/test/gtest/parse-ftp/U-ncFTPd.out b/netwerk/test/gtest/parse-ftp/U-ncFTPd.out
new file mode 100644
index 0000000000..6e3bac46f0
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-ncFTPd.out
@@ -0,0 +1,1377 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+01-01-1997 00:00:00 2419 Readme
+07-10-2002 11:03:00 <DIR> incoming
+07-10-2002 10:18:00 <DIR> outgoing
+05-10-2002 10:16:00 <DIR> priv
+07-10-2002 08:12:00 <DIR> pub
+07-08-2002 19:20:00 <DIR> allupdates
+01-01-1997 00:00:00 <DIR> netwire
+07-09-2002 19:20:00 <DIR> support
+01-10-2002 00:00:00 <JUNCTION> updates -> allupdates
+07-27-1999 00:00:00 19448 0dsb.exe
+09-17-1993 00:00:00 49351 215mir.exe
+09-17-1993 00:00:00 32586 215sec.exe
+09-17-1993 00:00:00 136344 21fix.exe
+09-17-1993 00:00:00 57276 223200.exe
+11-01-1995 00:00:00 119616 22bkup.exe
+10-13-1995 00:00:00 19820 22dos5.exe
+11-01-1995 00:00:00 32980 22nd2f.exe
+05-09-2002 11:30:00 92151 236779.exe
+01-17-2001 00:00:00 273958 242234.exe
+07-10-2001 00:00:00 283462 243798.exe
+12-18-2001 00:00:00 160893 251000.exe
+12-18-2001 00:00:00 205531 252143.exe
+12-18-2001 00:00:00 183262 253720.exe
+11-06-2001 00:00:00 231638 260624.exe
+06-21-2001 00:00:00 250545 264837.exe
+02-21-2002 16:33:00 589722 265058a.exe
+11-07-2001 00:00:00 336586 269308.exe
+11-06-2001 00:00:00 345275 270050.exe
+11-06-2001 00:00:00 103027 270410.exe
+05-09-2002 07:13:00 341460 275382.exe
+05-09-2002 11:23:00 365835 275436.exe
+10-09-2001 00:00:00 230063 275520.exe
+10-02-2001 00:00:00 285953 275820.exe
+11-16-2001 00:00:00 156517 277412.exe
+12-19-2001 00:00:00 839272 278415.exe
+12-19-2001 00:00:00 130983 282848.exe
+09-17-1993 00:00:00 61891 286dwn.exe
+05-09-2002 07:18:00 136529 290254.exe
+04-12-2002 17:07:00 96694 290261.exe
+05-09-2002 07:22:00 230377 291158.exe
+06-27-2002 12:20:00 114329 295137.exe
+09-17-1993 00:00:00 84885 2xto3x.exe
+09-17-1993 00:00:00 60861 300pt1.exe
+09-17-1993 00:00:00 180279 310pt1.exe
+11-11-1996 00:00:00 178076 311ptg.exe
+04-29-1996 00:00:00 188572 312du1.exe
+03-18-1998 00:00:00 168258 312ptd.exe
+08-25-1999 00:00:00 436629 312y2kp2.exe
+06-29-2001 00:00:00 2365264 33sp3.exe
+09-17-1993 00:00:00 28881 386dcb.exe
+09-17-1993 00:00:00 33111 3cboot.exe
+11-10-1995 00:00:00 27583 400pt1.exe
+10-23-1995 00:00:00 79295 401pt6.exe
+08-26-1994 00:00:00 42873 402pa1.exe
+03-28-1995 00:00:00 50737 402pt1.exe
+06-08-1998 00:00:00 548398 410pt8b.exe
+08-25-1999 00:00:00 285471 410y2kp2.exe
+07-23-1999 00:00:00 538544 411y2kp2.exe
+04-29-1996 00:00:00 370469 41filr.exe
+01-02-1997 00:00:00 58965 41migutl.exe
+08-29-1995 00:00:00 173978 41ndir.exe
+11-24-1997 00:00:00 42101 41rem1.exe
+07-09-1996 00:00:00 855292 41rtr2.exe
+07-23-1999 00:00:00 212785 42y2kp1.exe
+07-03-2001 00:00:00 3043701 48sp3.exe
+05-22-2001 00:00:00 308856 4pent.exe
+04-06-1998 00:00:00 1072023 4xmigr2.exe
+11-17-1995 00:00:00 125457 4xrep1.exe
+07-06-1994 00:00:00 29001 50z70.exe
+11-01-1995 00:00:00 23248 55sx60.exe
+12-04-1998 00:00:00 647818 95220p1.exe
+03-25-1999 00:00:00 905243 95250p2.exe
+06-21-1999 00:00:00 694344 9530p1.exe
+06-13-2000 00:00:00 794454 9531pt2.exe
+10-03-1999 00:00:00 4380134 9531sp2.exe
+11-16-2000 00:00:00 1306298 95321pt5.exe
+06-13-2000 00:00:00 494394 9532pt3.exe
+11-06-2001 00:00:00 483405 95331pt1.exe
+06-02-2000 00:00:00 19588899 a526aix.z
+06-02-2000 00:00:00 17196628 a526hp.z
+06-02-2000 00:00:00 14813432 a526sol.z
+07-09-1999 00:00:00 238005 accupg1b.exe
+09-17-1993 00:00:00 19472 aceclp.exe
+06-19-2001 00:00:00 2061142 acmgtcl.exe
+12-17-1996 00:00:00 52792 actkey.exe
+11-08-1996 00:00:00 1026928 adde1.exe
+09-10-1997 00:00:00 476094 adm32_22.exe
+01-10-1997 00:00:00 4204451 adm4113x.exe
+01-10-1997 00:00:00 3813257 adm41195.exe
+01-10-1997 00:00:00 5377426 adm411nt.exe
+05-23-2001 00:00:00 130826 admattrs.exe
+05-23-2001 00:00:00 3291925 admn519f.exe
+07-15-1997 00:00:00 273404 adtus3.exe
+09-17-1993 00:00:00 25475 afix1.exe
+09-17-1993 00:00:00 25929 afix2.exe
+09-17-1993 00:00:00 26184 afix4.exe
+04-10-2001 00:00:00 116034 afnwcgi1.exe
+01-28-2002 13:43:00 175949 afp100j.exe
+07-12-1999 00:00:00 90415 afp11.exe
+11-01-1995 00:00:00 513677 aiodrv.exe
+08-12-1995 00:00:00 26752 aiotrm.exe
+02-08-2000 00:00:00 113328 alx311cm.exe
+12-11-2001 00:00:00 202594 am210pt2.exe
+12-10-2001 00:00:00 1126900 am210snp.exe
+12-11-2001 00:00:00 267218 amsammg.exe
+11-29-2001 00:00:00 4394406 amw2ksp1.exe
+11-06-1996 00:00:00 34959 anlmde.exe
+11-20-1996 00:00:00 70315 antde.exe
+12-10-1997 00:00:00 2778036 apinlm.exe
+04-05-1994 00:00:00 47772 apinst.exe
+12-10-1997 00:00:00 3367826 apios2.exe
+09-17-1993 00:00:00 44662 apsvap.exe
+10-05-1998 00:00:00 1507519 async1.exe
+07-25-1995 00:00:00 431852 asyq4a.exe
+09-17-1993 00:00:00 68700 atalk.exe
+09-17-1993 00:00:00 22944 atdisk.exe
+01-29-1996 00:00:00 222222 atk307.exe
+09-24-1999 00:00:00 259359 atmdrv04.exe
+06-14-1996 00:00:00 22012 atok31.exe
+09-17-1993 00:00:00 25152 atps1.exe
+09-17-1993 00:00:00 25812 atps2.exe
+09-17-1993 00:00:00 206226 atsup.exe
+09-15-1997 00:00:00 249262 audit410.exe
+07-25-1995 00:00:00 27058 autoda.exe
+03-28-2001 00:00:00 53955568 azfd3sp1.exe
+03-30-2000 00:00:00 1267891 bacl105.exe
+04-24-1998 00:00:00 564407 bm21y2k.exe
+12-09-1999 00:00:00 64354 bm2ncs2.exe
+11-13-2000 00:00:00 5025837 bm30sp3.exe
+03-27-2002 10:48:00 118425 bm35adm6.exe
+09-25-2001 00:00:00 8950758 bm35sp3.exe
+05-31-2002 15:43:00 1560521 bm36c02.exe
+11-06-2001 00:00:00 1995053 bm36nsp1.exe
+01-30-2002 13:44:00 4187534 bm36sp1a.exe
+04-19-2002 13:52:00 146884 bm37flt.exe
+04-19-2002 13:54:00 3958423 bm37vpn1.exe
+06-16-2000 00:00:00 2192048 bm3cp3.exe
+03-27-2000 00:00:00 521873 bm3licfx.exe
+01-21-1999 00:00:00 550905 bm3netad.exe
+10-08-1999 00:00:00 28435 bm3pcfg.exe
+10-20-1999 00:00:00 30429 bm3rmv3.exe
+09-02-1999 00:00:00 4865777 bm3sp2.exe
+01-11-2000 00:00:00 3934644 bm3sp2j.exe
+01-16-2001 00:00:00 105579 bm3sso2.exe
+11-23-1998 00:00:00 114564 bmas203.exe
+07-27-2000 00:00:00 185953 bmas204.exe
+05-29-2001 00:00:00 227934 bmas3x01.exe
+04-14-1998 00:00:00 2409166 bmnwntb.exe
+10-07-1999 00:00:00 235512 bmp114.exe
+08-25-2000 00:00:00 114828 bmsamp1.exe
+02-05-2001 00:00:00 7822377 bmsp2d.exe
+05-09-2001 00:00:00 177276 bmvpn3y.exe
+07-16-1997 00:00:00 57666 bndfx4.exe
+09-26-1997 00:00:00 22178 brdrem1.exe
+09-17-1993 00:00:00 140225 brgcom.exe
+03-17-1998 00:00:00 64130 bwcc.exe
+05-10-2000 00:00:00 106576 c112brj.exe
+05-09-2000 00:00:00 107820 c112crcj.exe
+05-10-2000 00:00:00 106834 c112crj.exe
+03-05-2002 13:14:00 35687507 c1_132.exe
+02-27-2001 00:00:00 188379 c1unx85a.exe
+06-02-2000 00:00:00 24246977 c526aix.z
+06-02-2000 00:00:00 39823205 c526hp.z
+06-02-2000 00:00:00 31630789 c526sol.z
+12-04-1996 00:00:00 39967 ccmdll.exe
+06-21-1999 00:00:00 3532557 ccmln1.exe
+04-21-2000 00:00:00 3610648 ccmln2.exe
+10-30-1998 00:00:00 7242383 ccmlo1.exe
+03-06-1997 00:00:00 71189 cdisc.exe
+08-19-1998 00:00:00 298109 cdup5a.exe
+10-14-1998 00:00:00 1554357 cfgrd6b.exe
+09-17-1993 00:00:00 30799 chk375.exe
+06-16-1997 00:00:00 85538 chtree1.exe
+12-08-1999 00:00:00 28171 clibaux1.exe
+09-08-2000 00:00:00 2819 clients.txt
+09-11-2000 00:00:00 16820824 clntaot.exe
+01-05-1999 00:00:00 7269162 clos2d1.exe
+06-24-1996 00:00:00 2960384 clt511.bin
+03-24-1999 00:00:00 195429 clty2kp1.exe
+11-22-1993 00:00:00 76117 comchk.exe
+11-01-1995 00:00:00 76738 comsub.exe
+08-16-1996 00:00:00 74759 comx.exe
+02-22-2001 00:00:00 102496 comx218.exe
+12-05-2000 00:00:00 117584 confg9.exe
+09-17-1993 00:00:00 27438 conlog.exe
+09-17-1993 00:00:00 20051 cpuchk.exe
+01-07-2000 00:00:00 96755 cron5.exe
+07-21-2001 00:00:00 22439431 cs1sp2.exe
+02-19-2002 16:08:00 12643202 cs1sp3.exe
+02-28-2001 00:00:00 2395980 cs2ep2.exe
+02-17-2000 00:00:00 97982 csatpxy2.exe
+01-30-1997 00:00:00 888262 csserv.exe
+01-30-1997 00:00:00 888092 ctserv.exe
+09-20-1999 00:00:00 1195718 ctsv20_1.pdf
+08-30-2000 00:00:00 97050 cvsbind.exe
+01-05-1996 00:00:00 1040603 d70f15.exe
+01-05-1996 00:00:00 1046550 d70g15.exe
+01-05-1996 00:00:00 1041138 d70i15.exe
+01-05-1996 00:00:00 1042404 d70s15.exe
+01-05-1996 00:00:00 1031373 d70u15.exe
+10-31-1995 00:00:00 309903 d72pnw.exe
+08-17-1995 00:00:00 26322 daishm.exe
+01-17-2002 09:55:00 92347 decrenfx.exe
+11-24-1997 00:00:00 32573 devmon.exe
+11-23-1999 00:00:00 225697 dhcp21r.exe
+07-27-1994 00:00:00 65667 diag.exe
+07-03-1996 00:00:00 776679 dialr1.exe
+07-03-1996 00:00:00 1307891 dialr2.exe
+07-03-1996 00:00:00 1330220 dialr3.exe
+09-17-1993 00:00:00 62202 distst.exe
+02-08-2001 00:00:00 133345 dlttape.exe
+12-13-1995 00:00:00 174566 dr6tid.exe
+05-15-2002 15:23:00 188110 dradpt1a.exe
+04-11-2002 10:37:00 12654872 drdt10.exe
+04-11-2002 10:50:00 7991951 drnt10.exe
+12-09-1993 00:00:00 259966 drv2x.exe
+09-17-1993 00:00:00 366568 drvkit.exe
+12-02-1998 00:00:00 28773 drvspc.exe
+07-12-1994 00:00:00 242836 ds310.exe
+12-13-1999 00:00:00 739008 ds410q.exe
+06-11-2002 18:01:00 683746 ds616.exe
+07-03-2002 19:58:00 1237908 ds760a.exe
+06-12-2002 13:47:00 3084809 ds880d_a.exe
+06-07-2000 00:00:00 2366484 ds8c.exe
+05-31-2000 00:00:00 300572 dsbrowse.exe
+11-10-1998 00:00:00 425220 dsdiag1.exe
+04-15-1998 00:00:00 766525 dskdrv.exe
+05-02-1997 00:00:00 201308 dsright2.exe
+02-04-2002 14:26:00 6620 dsrmenu5.tgz
+01-11-2002 12:06:00 32671 dsx86upg.tgz
+11-22-1999 00:00:00 438327 duprid.exe
+11-30-1999 00:00:00 366231 dw271i1.exe
+10-31-2001 00:00:00 6444327 dx1patch.exe
+12-11-2001 00:00:00 764705 dxjdbc1a.exe
+07-31-2001 00:00:00 369669 dxnotp1.exe
+01-11-2002 17:32:00 325977 dxntp1.exe
+01-11-2002 17:11:00 6446882 dxnwp1.exe
+01-15-2002 18:08:00 27292445 dxx10a.tgz
+09-18-1993 00:00:00 420838 e2isa.exe
+05-15-2002 09:35:00 299227554 edir851.exe
+06-19-2002 22:20:00 13752049 edir8527.exe
+06-18-2002 17:22:00 8909701 edir8527.tgz
+05-23-2002 12:35:00 35256093 edir862.exe
+03-07-2002 13:22:00 30290896 edir862.tgz
+06-20-2002 11:12:00 12405810 edir862sp1.exe
+06-20-2002 11:07:00 7623982 edir862sp1a.tgz
+06-19-2002 23:26:00 178010922 edirw8527.exe
+08-01-1995 00:00:00 739976 edvwin.exe
+04-12-2002 15:32:00 270433 egdpvr02.exe
+01-15-2002 10:28:00 6660863 einstall.exe
+09-18-1993 00:00:00 19547 els2dt.exe
+09-18-1993 00:00:00 46872 elsupd.exe
+09-18-1993 00:00:00 44481 emshdl.exe
+11-30-2001 00:00:00 171889 es7000.exe
+09-18-1993 00:00:00 395219 escsi.exe
+11-01-1995 00:00:00 50164 esdidr.exe
+09-18-1993 00:00:00 22207 esdifx.exe
+04-16-2001 00:00:00 162763 etbox7.exe
+09-18-1993 00:00:00 28262 ethrlk.exe
+09-18-1993 00:00:00 21776 ethsh.exe
+10-13-1999 00:00:00 3111638 exchnt2.exe
+03-20-1995 00:00:00 32615 exprul.exe
+07-18-1995 00:00:00 19910 facwin.exe
+09-18-1993 00:00:00 193689 faxdoc.exe
+07-18-1995 00:00:00 103299 faxsv.exe
+07-20-1998 00:00:00 566087 fbm21y2k.exe
+07-27-2000 00:00:00 263164 ffwnt1a.exe
+06-05-2000 00:00:00 9564485 fgwep1ad.exe
+11-10-1993 00:00:00 158446 fil376.exe
+09-18-1993 00:00:00 35376 filehd.exe
+01-28-1999 00:00:00 64924 filt01a.exe
+09-18-1993 00:00:00 38027 flgdir.exe
+06-25-2002 10:26:00 891667 flsysft7.exe
+07-20-1994 00:00:00 210958 flx196.exe
+09-18-1993 00:00:00 23948 fmtps2.exe
+06-01-1995 00:00:00 265642 fmwin1.exe
+06-18-1997 00:00:00 44110 fnasi21b.exe
+04-29-1997 00:00:00 35133 fnwcrns.exe
+06-18-1997 00:00:00 64944 fnwcss.exe
+06-18-1997 00:00:00 38169 fnwcx25.exe
+02-26-2001 00:00:00 18985686 fp3023a.exe
+02-26-2001 00:00:00 19034903 fp3023s.exe
+06-22-2001 00:00:00 41401158 fpa353.exe
+06-22-2001 00:00:00 40072174 fps353.exe
+08-02-1996 00:00:00 40680 frebld.exe
+01-30-2002 15:36:00 156117 ftpservk.exe
+06-20-2001 00:00:00 1427341 fzd2abnd.exe
+05-22-2001 00:00:00 121436 fzd2nal.exe
+05-22-2001 00:00:00 293122 fzd2nal1.exe
+12-18-2001 00:00:00 333581 fzd2nal2.exe
+05-23-2001 00:00:00 250445 fzd2zapp.exe
+10-10-2001 00:00:00 2619032 g32ep3b.exe
+11-23-1998 00:00:00 26193920 g41c5a41.tar
+11-23-1998 00:00:00 25907200 g41c5a43.tar
+11-23-1998 00:00:00 25190400 g41c5dg.tar
+11-23-1998 00:00:00 27494400 g41c5hp.tar
+11-23-1998 00:00:00 29337600 g41c5ncr.tar
+11-23-1998 00:00:00 27146240 g41c5sc5.tar
+11-23-1998 00:00:00 26982400 g41c5sl5.tar
+11-23-1998 00:00:00 26961920 g41c5sl6.tar
+11-23-1998 00:00:00 32808960 g41c5sun.tar
+11-23-1998 00:00:00 3614720 g41m5a41.tar
+11-23-1998 00:00:00 3604480 g41m5a43.tar
+11-23-1998 00:00:00 3481600 g41m5dg.tar
+11-23-1998 00:00:00 4280320 g41m5hp.tar
+11-23-1998 00:00:00 4495360 g41m5ncr.tar
+11-23-1998 00:00:00 5263360 g41m5sc5.tar
+11-23-1998 00:00:00 4116480 g41m5sl5.tar
+11-23-1998 00:00:00 4106240 g41m5sl6.tar
+11-20-1998 00:00:00 5939200 g41m5sun.tar
+06-02-2000 00:00:00 93137892 g526east.exe
+06-02-2000 00:00:00 84011178 g526kcc.exe
+06-02-2000 00:00:00 106015153 g526mult.exe
+06-02-2000 00:00:00 92474943 g526scan.exe
+06-02-2000 00:00:00 74449657 g526us.exe
+06-02-2000 00:00:00 80161031 g526usde.exe
+06-02-2000 00:00:00 77052636 g526usjp.exe
+12-18-1997 00:00:00 20712319 g52a1aix.z
+12-18-1997 00:00:00 17069171 g52a1hp.z
+12-18-1997 00:00:00 14703356 g52a1sol.z
+06-02-2000 00:00:00 7595802 g554ar.exe
+06-02-2000 00:00:00 39304570 g554en.exe
+06-02-2000 00:00:00 13651283 g554est.exe
+06-02-2000 00:00:00 7587459 g554he.exe
+06-02-2000 00:00:00 23317212 g554jp.exe
+06-02-2000 00:00:00 22871590 g554kcc.exe
+06-02-2000 00:00:00 30379240 g554mlt.exe
+06-02-2000 00:00:00 30469595 g554scn.exe
+08-31-2000 00:00:00 3252251 g5notmb1.exe
+08-08-1995 00:00:00 1983381 gam10.exe
+07-20-1998 00:00:00 565411 gbm21y2k.exe
+11-19-1996 00:00:00 547498 gdsmtp.exe
+10-21-1996 00:00:00 26760 gdusc1.exe
+09-18-1993 00:00:00 32876 genbio.exe
+10-03-2001 00:00:00 4589470 gep3beng.exe
+08-16-1999 00:00:00 12570112 gm527aen.bin
+06-14-2000 00:00:00 12556928 gm528aen.bin
+06-19-2000 00:00:00 12558720 gm528ben.bin
+12-21-1996 00:00:00 6081664 gmcec3.hqx
+12-21-1996 00:00:00 6086656 gmcfc3.hqx
+12-24-1996 00:00:00 6089856 gmesc3.hqx
+12-20-1996 00:00:00 6087552 gmfrc3.hqx
+12-24-1996 00:00:00 6084480 gmitc3.hqx
+12-19-1996 00:00:00 6079872 gmozc3.hqx
+12-19-1996 00:00:00 6081792 gmukc3.hqx
+12-11-1996 00:00:00 6079872 gmusc3.hqx
+09-18-1993 00:00:00 26557 gpierr.exe
+08-28-1996 00:00:00 189508 gpwrdk.exe
+08-28-1996 00:00:00 114332 gpwrnl.exe
+09-03-1996 00:00:00 99491 gpwrru.exe
+08-28-1996 00:00:00 131835 gpwrsu.exe
+08-22-1996 00:00:00 170183 gpwrsv.exe
+02-25-1998 00:00:00 74740 gpwsbr.exe
+02-25-1998 00:00:00 44964 gpwsde.exe
+02-25-1998 00:00:00 40264 gpwsfr.exe
+02-25-1998 00:00:00 39224 gpwsit.exe
+07-22-1999 00:00:00 47842 groupfix.exe
+10-19-1995 00:00:00 207875 gsc41.exe
+12-15-1997 00:00:00 811983 gsc51.exe
+06-08-1995 00:00:00 9805824 gusmtp.tar
+07-28-1999 00:00:00 4658880 gw41api.exe
+11-17-1999 00:00:00 1307103 gw41api2.exe
+07-29-1999 00:00:00 30983798 gw41aus.exe
+02-28-1996 00:00:00 86653 gw41qa.exe
+12-10-1998 00:00:00 20978 gw41sp5.txt
+01-08-1998 00:00:00 18251583 gw51sp2a.exe
+03-10-1998 00:00:00 3483616 gw51w16a.exe
+01-09-2001 00:00:00 1931948 gw52ch6.exe
+06-26-2000 00:00:00 479758 gw52sp6.exe
+02-28-2002 16:19:00 35836761 gw555cck.exe
+02-28-2002 16:34:00 26614908 gw555ee.exe
+05-31-2000 00:00:00 372421 gw55bk.exe
+02-03-2000 00:00:00 109887 gw55bp.exe
+06-19-2000 00:00:00 847939 gw55dutl.exe
+08-14-2001 00:00:00 58548675 gw55ep3a.exe
+06-25-1999 00:00:00 2497895 gw55inst.exe
+06-27-2000 00:00:00 3742457 gw55ol2.exe
+04-18-2001 00:00:00 219963 gw55puma.exe
+03-01-1999 00:00:00 19376 gw55rc.exe
+12-18-1998 00:00:00 45201 gw55sp1u.exe
+02-28-2002 16:08:00 21311033 gw55sp5a.exe
+02-19-2002 17:56:00 52764291 gw55sp5e.exe
+02-28-2002 16:13:00 21300984 gw55sp5h.exe
+02-19-2002 18:03:00 43933605 gw55sp5m.exe
+02-28-2002 16:03:00 44001516 gw55sp5s.exe
+03-09-1999 00:00:00 5867520 gw55uagb.tar
+04-23-1997 00:00:00 20446033 gw5img.exe
+11-21-2001 00:00:00 313843533 gw6sp1.exe
+11-20-2001 00:00:00 313466368 gw6sp1fr.exe
+03-13-2002 18:35:00 26705047 gw6tomcat_nt.exe
+10-15-2001 00:00:00 318278 gw6wasf.exe
+01-22-1997 00:00:00 493803 gwadm.exe
+07-26-2000 00:00:00 1900719 gwafe510.exe
+11-02-1999 00:00:00 469618 gwanlzr1.exe
+06-19-2000 00:00:00 479013 gwata417.exe
+06-19-2000 00:00:00 1731751 gwata517.exe
+08-12-1996 00:00:00 168008 gwbr41.exe
+08-31-1998 00:00:00 17881984 gwbrc5.exe
+08-26-1997 00:00:00 194213 gwbupaus.exe
+06-15-2000 00:00:00 885247 gwca55.exe
+06-29-2000 00:00:00 902786 gwca55us.exe
+08-17-1999 00:00:00 2398720 gwccarch.exe
+08-27-1998 00:00:00 19751754 gwcec5.exe
+12-01-1998 00:00:00 18095569 gwcfc5a.exe
+01-22-1997 00:00:00 499022 gwclint.exe
+06-19-2000 00:00:00 274618 gwclust.exe
+08-31-1998 00:00:00 18704283 gwczc5.exe
+08-28-1998 00:00:00 18602662 gwdec5.exe
+01-22-1997 00:00:00 577392 gwdirsnc.exe
+08-31-1998 00:00:00 18044524 gwdkc5.exe
+08-16-2001 00:00:00 2064976 gwe2mlfx.exe
+04-18-2001 00:00:00 220287 gweppuma.exe
+02-19-2002 17:38:00 106411074 gwepsp4e.exe
+02-19-2002 17:48:00 105412358 gwepsp4m.exe
+10-15-2001 00:00:00 211359 gwepwasf.exe
+08-31-1998 00:00:00 18156006 gwesc5.exe
+08-20-1999 00:00:00 2278400 gwexarch.exe
+09-02-1998 00:00:00 18094284 gwfrc5.exe
+02-05-2002 12:22:00 14809111 gwia6sp1.exe
+12-24-1998 00:00:00 47599 gwip.exe
+06-26-2000 00:00:00 2103744 gwiru55.exe
+08-31-1998 00:00:00 18176506 gwitc5.exe
+05-20-1997 00:00:00 1643991 gwjpc3.exe
+08-24-1999 00:00:00 2689024 gwlnarch.exe
+08-31-1998 00:00:00 18450385 gwmac5.exe
+06-19-2001 00:00:00 104256 gwmacvew.exe
+01-22-1997 00:00:00 521879 gwmigrat.exe
+08-18-1999 00:00:00 2387456 gwmsarch.exe
+08-31-1998 00:00:00 18442250 gwnlc5.exe
+08-31-1998 00:00:00 18496070 gwnoc5.exe
+08-27-1998 00:00:00 18436940 gwozc5.exe
+08-29-2001 00:00:00 735542 gwpdchk.exe
+08-28-2001 00:00:00 29521018 gwpdlk56.exe
+08-14-2001 00:00:00 29520686 gwpdlock.exe
+08-31-1998 00:00:00 18696898 gwplc5.exe
+03-05-1996 00:00:00 1388969 gwpoc.exe
+08-31-1998 00:00:00 17738497 gwpoc5.exe
+01-22-2002 16:48:00 10400450 gwport32.exe
+08-31-1998 00:00:00 18492682 gwruc5.exe
+08-31-1998 00:00:00 18154021 gwsuc5.exe
+08-31-1998 00:00:00 17958220 gwsvc5.exe
+01-16-2002 09:41:00 536848 gwsyclo.exe
+11-09-1999 00:00:00 78740 gwtps1v2.exe
+08-28-1998 00:00:00 18432206 gwukc5.exe
+08-27-1998 00:00:00 18544901 gwusc5.exe
+10-18-1995 00:00:00 210542 gwusr1.exe
+10-18-1995 00:00:00 1404401 gwusr2.exe
+03-28-1997 00:00:00 2467040 gwusr3.exe
+10-01-1997 00:00:00 767611 gwwa4p1.exe
+03-04-1998 00:00:00 2092781 gwwebcgi.z
+08-04-1999 00:00:00 345006 gwy195.exe
+10-09-2001 00:00:00 102293 hdir501c.exe
+08-21-1998 00:00:00 1138106 highutl1.exe
+03-28-1995 00:00:00 567026 hpt004.exe
+03-09-1995 00:00:00 64329 hpt005.exe
+08-27-1996 00:00:00 602958 hpt006.exe
+03-09-1995 00:00:00 497382 hpt007.exe
+03-09-1995 00:00:00 510029 hpt008.exe
+03-13-1995 00:00:00 596667 hpt009.exe
+11-20-1995 00:00:00 942798 hpt011.exe
+08-11-1995 00:00:00 176332 hpt013.exe
+03-16-1999 00:00:00 1070543 hstdev.exe
+04-05-2002 14:51:00 246782 httpstk1.exe
+04-19-2000 00:00:00 262791 i2odrv5.exe
+07-20-1998 00:00:00 565749 ibm21y2k.exe
+09-18-1993 00:00:00 24735 ibmmem.exe
+09-18-1993 00:00:00 60883 ibmrt.exe
+07-11-2001 00:00:00 2621963 ic15fp2.exe
+02-08-2002 16:39:00 2905716 ic15fp3.exe
+04-04-2001 00:00:00 9581302 ic15sp1.exe
+12-14-2001 00:00:00 1585498 ic20fp1.exe
+01-14-2002 09:32:00 4520778 ic20fp2.exe
+05-30-2002 15:01:00 4777958 ic20sp1.exe
+03-28-2002 17:46:00 142010 ichcdump.exe
+09-20-1994 00:00:00 36043 ide.exe
+09-18-1993 00:00:00 24552 ide286.exe
+09-18-1993 00:00:00 27300 ide386.exe
+06-09-2000 00:00:00 105249 ideata5a.exe
+04-17-2000 00:00:00 1356133 ihp232.exe
+03-08-2002 15:40:00 93664 imspmfix.exe
+07-23-1998 00:00:00 7338542 in42sp2.exe
+11-08-1995 00:00:00 430410 ind41.exe
+03-20-1997 00:00:00 63751 inetcs.exe
+03-20-1997 00:00:00 60414 inetct.exe
+04-28-1997 00:00:00 69792 inetha.exe
+10-18-1995 00:00:00 1094896 inf1.exe
+10-18-1995 00:00:00 798379 inff1.exe
+01-09-2002 00:00:00 151877 installa.exe
+06-09-1995 00:00:00 836804 instll.exe
+10-09-2000 00:00:00 101582 instp5x.exe
+09-18-1993 00:00:00 39511 int215.exe
+09-18-1993 00:00:00 19521 intfix.exe
+09-18-1993 00:00:00 35521 intrud.exe
+12-04-1997 00:00:00 5594188 inwc2enh.exe
+09-18-1993 00:00:00 69340 ipc212.exe
+09-18-1993 00:00:00 24423 ipcfg.exe
+08-13-2001 00:00:00 593463 ipcost.exe
+06-24-1999 00:00:00 131414 ipg4201a.exe
+06-30-2000 00:00:00 617600 ipgc07a.exe
+02-11-2000 00:00:00 233838 ipgsb06.exe
+10-02-1999 00:00:00 107564 ipgsn10a.exe
+11-15-1999 00:00:00 44888 ipgwdoc.exe
+09-18-1993 00:00:00 65876 ipt112.exe
+09-23-1998 00:00:00 842325 ipx660.exe
+12-01-1995 00:00:00 37227 ipxspx.exe
+09-18-1993 00:00:00 22904 isa30.exe
+09-18-1993 00:00:00 31546 isa311.exe
+09-18-1993 00:00:00 24391 isarem.exe
+04-09-1997 00:00:00 49650 iwsbp1.exe
+11-21-2000 00:00:00 6073482 jbm30sp3.exe
+10-19-1995 00:00:00 109173 jcon.exe
+08-16-2001 00:00:00 185167 jr08993.exe
+08-15-2001 00:00:00 54187 jr10449.exe
+08-15-2001 00:00:00 51804 jr10470.exe
+05-29-2001 00:00:00 105466 jr10490.exe
+10-16-2000 00:00:00 17936 jr10662.exe
+10-16-2000 00:00:00 51421 jr10803.exe
+08-15-2001 00:00:00 884373 jr10823.exe
+08-15-2001 00:00:00 1909 jr10923.zip
+10-16-2000 00:00:00 53610 jr11209.exe
+10-16-2000 00:00:00 272210 jr11213.exe
+05-27-2001 00:00:00 32984 jr11223.exe
+10-16-2000 00:00:00 45200 jr11409.exe
+05-27-2001 00:00:00 488829 jr12054.exe
+08-15-2001 00:00:00 17761 jr12089.exe
+05-27-2001 00:00:00 245833 jr12125.exe
+08-15-2001 00:00:00 242650 jr12129.exe
+05-27-2001 00:00:00 144543 jr12229.exe
+05-27-2001 00:00:00 23194 jr12349.exe
+08-15-2001 00:00:00 158924 jr12415.exe
+10-16-2000 00:00:00 39844 jr12518.exe
+10-16-2000 00:00:00 42015 jr12676.exe
+05-27-2001 00:00:00 42090 jr12692.exe
+05-29-2001 00:00:00 41998 jr12705.exe
+10-16-2000 00:00:00 21903 jr12706.exe
+10-16-2000 00:00:00 75818 jr12765.exe
+08-15-2001 00:00:00 245898 jr12768.exe
+10-16-2000 00:00:00 144542 jr12773.exe
+05-29-2001 00:00:00 144542 jr12775.exe
+08-15-2001 00:00:00 144543 jr12776.exe
+10-16-2000 00:00:00 44352 jr12777.exe
+05-27-2001 00:00:00 44355 jr12778.exe
+08-15-2001 00:00:00 43721 jr12828.exe
+05-27-2001 00:00:00 52823 jr12890.exe
+10-16-2000 00:00:00 52824 jr12947.exe
+10-16-2000 00:00:00 74323 jr12978.exe
+05-27-2001 00:00:00 74323 jr12979.exe
+05-29-2001 00:00:00 74326 jr12987.exe
+05-27-2001 00:00:00 16573256 jr13042.exe
+08-15-2001 00:00:00 16586769 jr13046.exe
+08-15-2001 00:00:00 170632 jr13047.exe
+08-15-2001 00:00:00 55210 jr13105.exe
+08-15-2001 00:00:00 124966 jr13147.exe
+10-16-2000 00:00:00 43318 jr13259.exe
+10-16-2000 00:00:00 33183 jr13461.exe
+05-27-2001 00:00:00 84700 jr13554.exe
+10-16-2000 00:00:00 84649 jr13555.exe
+05-27-2001 00:00:00 52564 jr13557.exe
+10-16-2000 00:00:00 52161 jr13558.exe
+05-29-2001 00:00:00 52167 jr13559.exe
+05-27-2001 00:00:00 54942 jr13616.exe
+10-16-2000 00:00:00 54947 jr13617.exe
+08-15-2001 00:00:00 56424 jr13619.exe
+09-17-1999 00:00:00 71827 jr13627.exe
+10-16-2000 00:00:00 83182 jr13637.exe
+05-29-2001 00:00:00 108483 jr13722.exe
+08-15-2001 00:00:00 170173 jr13734.exe
+08-15-2001 00:00:00 641941 jr13748.exe
+05-29-2001 00:00:00 641950 jr13749.exe
+05-29-2001 00:00:00 579521 jr13886.exe
+08-15-2001 00:00:00 126491 jr13891.exe
+05-27-2001 00:00:00 109292 jr14132.exe
+08-15-2001 00:00:00 109181 jr14134.exe
+05-29-2001 00:00:00 39830 jr14185.exe
+08-15-2001 00:00:00 72780 jr14278.exe
+08-15-2001 00:00:00 345689 jr14358.exe
+05-27-2001 00:00:00 642240 jr14359.exe
+10-16-2000 00:00:00 642388 jr14360.exe
+05-27-2001 00:00:00 313078 jr14367.exe
+05-27-2001 00:00:00 109587 jr14402.exe
+08-15-2001 00:00:00 71603 jr14457.exe
+08-15-2001 00:00:00 567270 jr14500.exe
+08-15-2001 00:00:00 157611 jr14523.exe
+08-15-2001 00:00:00 161290 jr14556.exe
+05-27-2001 00:00:00 161439 jr14585.exe
+10-16-2000 00:00:00 161435 jr14586.exe
+10-16-2000 00:00:00 186037 jr14840.exe
+10-16-2000 00:00:00 368604 jr14936.exe
+08-15-2001 00:00:00 115132 jr15013.exe
+08-15-2001 00:00:00 568164 jr15133.exe
+08-15-2001 00:00:00 217169 jr15338.exe
+08-15-2001 00:00:00 362664 jr15396.exe
+08-15-2001 00:00:00 104143 jr15509.exe
+08-15-2001 00:00:00 473822 jr15544.exe
+07-25-2001 00:00:00 484196 jssl11d.exe
+07-20-2001 00:00:00 36251555 jvm122.exe
+02-28-2002 08:06:00 49007010 jvm131.exe
+03-28-2001 00:00:00 48672254 jzfd3sp1.exe
+07-17-1997 00:00:00 66469 killq.exe
+03-01-1994 00:00:00 202716 l11f01.exe
+03-01-1994 00:00:00 202878 l11g01.exe
+03-01-1994 00:00:00 202460 l11i01.exe
+01-06-1994 00:00:00 23583 l11p06.exe
+03-01-1994 00:00:00 202249 l11s01.exe
+11-24-1993 00:00:00 30533 l11u05.exe
+04-20-1999 00:00:00 206616 lanchk.exe
+10-21-1996 00:00:00 341280 landr9.exe
+05-14-1998 00:00:00 4610909 landrv.exe
+09-10-1996 00:00:00 24799 lanwcs.exe
+09-04-1996 00:00:00 37088 lanwct.exe
+09-04-1996 00:00:00 42514 lanwes.exe
+09-11-1996 00:00:00 42417 lanwfr.exe
+09-10-1996 00:00:00 36855 lanwha.exe
+09-04-1996 00:00:00 42397 lanwit.exe
+01-31-2002 16:00:00 16012931 lanwp02.exe
+12-16-1996 00:00:00 342390 lat002.exe
+09-18-1993 00:00:00 133326 ld401a.exe
+02-07-2000 00:00:00 166765 ldr312ft.exe
+09-18-1993 00:00:00 26450 leap.exe
+06-09-1994 00:00:00 180540 lg4084.exe
+12-14-1995 00:00:00 91840 lg42l4.exe
+10-11-1995 00:00:00 72061 li3pre.exe
+06-02-1998 00:00:00 258605 lib311b.exe
+09-20-1999 00:00:00 273268 lib312d.exe
+02-23-2000 00:00:00 977416 libupj4.exe
+06-26-1995 00:00:00 111269 lo30a2.exe
+06-26-1995 00:00:00 94927 lo30t1.exe
+09-18-1993 00:00:00 105128 load.exe
+08-26-1998 00:00:00 111722 loaddll1.exe
+09-18-1993 00:00:00 20810 locins.exe
+03-09-1995 00:00:00 74768 log376.exe
+09-15-1997 00:00:00 269333 log410a.exe
+02-23-2000 00:00:00 99524 longnam.exe
+03-05-1997 00:00:00 1563061 lpo51a.exe
+03-14-1997 00:00:00 115601 lpo51b.exe
+06-12-1995 00:00:00 373114 lw42w2.exe
+09-20-1996 00:00:00 986579 lw50w1.exe
+08-21-1996 00:00:00 79276 lwg50a.exe
+08-18-1998 00:00:00 115527 lwp001.exe
+08-20-1998 00:00:00 54951 lwp002.exe
+05-06-1996 00:00:00 447982 lwp413.exe
+11-30-1994 00:00:00 231591 lwp423.exe
+05-18-1998 00:00:00 26516 lwp501.exe
+05-15-1998 00:00:00 27895 lwp511.exe
+09-18-1993 00:00:00 21913 lwpo30.exe
+09-03-1997 00:00:00 47334 lwpping.exe
+08-09-1999 00:00:00 356846 lzfw01c.exe
+10-18-1993 00:00:00 33252 lzfwdi.exe
+07-14-1999 00:00:00 1083871 lzfwlf.exe
+06-16-1995 00:00:00 29057 lzfwqa.exe
+09-07-1994 00:00:00 26848 lzw001.exe
+05-16-1995 00:00:00 29431 lzw002.exe
+09-18-1993 00:00:00 151295 lzw40.exe
+09-18-1993 00:00:00 29379 m30pat.exe
+07-24-1996 00:00:00 122720 macfil.exe
+07-10-1997 00:00:00 347278 macpt3d.exe
+05-29-1996 00:00:00 193792 mactsa.bin
+07-23-1996 00:00:00 232349 mactsa.exe
+02-21-1996 00:00:00 210004 mailcl.exe
+03-13-1995 00:00:00 48964 map312.exe
+11-24-1997 00:00:00 144574 map410b.exe
+10-10-2001 00:00:00 127956 masv_sp3.exe
+02-28-2001 00:00:00 266084 mbcmnup1.exe
+08-13-1998 00:00:00 508288 mclupd6a.bin
+09-18-1993 00:00:00 22803 mcmfm.exe
+02-11-1994 00:00:00 37710 mdf178.exe
+09-18-1993 00:00:00 87520 menu34.exe
+11-15-1993 00:00:00 20214 menuhi.exe
+09-18-1993 00:00:00 91376 menus.exe
+08-15-2001 00:00:00 5040875 mgt22010.exe
+11-03-1993 00:00:00 683946 mhs173.exe
+02-14-1994 00:00:00 656047 mhs183.exe
+02-07-1994 00:00:00 664789 mhs184.exe
+11-03-1997 00:00:00 85281 mhsmcu.exe
+09-18-1993 00:00:00 54627 mhsmd.exe
+08-16-1994 00:00:00 604103 mhsslt.exe
+06-30-1995 00:00:00 132603 mhsvr1.exe
+08-01-1996 00:00:00 449605 migrat.exe
+09-18-1993 00:00:00 33708 mirrem.exe
+01-05-2000 00:00:00 27044 mixmod6.exe
+09-18-1993 00:00:00 96673 mkuser.exe
+09-18-1995 00:00:00 68504 mon176.exe
+05-18-1995 00:00:00 66024 monsft.exe
+09-18-1993 00:00:00 424897 morebk.exe
+09-18-1993 00:00:00 37818 mountr.exe
+09-18-1993 00:00:00 19452 mouse.exe
+02-03-1994 00:00:00 32801 mpr182.exe
+07-15-1994 00:00:00 125166 mpr199.exe
+06-04-1996 00:00:00 1066126 mpr31a.exe
+10-28-1996 00:00:00 1082729 mpr31b.exe
+09-23-1994 00:00:00 31245 mprper.exe
+03-24-1995 00:00:00 38997 mprul3.exe
+10-11-1995 00:00:00 122440 mprx25.exe
+08-02-1996 00:00:00 328085 msml21.exe
+10-05-1998 00:00:00 5876316 msmlos21.exe
+08-02-1999 00:00:00 70822 msmpcu.exe
+05-31-2000 00:00:00 6355314 mw26sp3.exe
+07-31-2000 00:00:00 119531 mw26trd1.exe
+11-13-2000 00:00:00 3054719 mw27sp1.exe
+11-23-1999 00:00:00 27173 mw5mcal1.exe
+05-17-2000 00:00:00 253007 mwhp01b.exe
+01-25-2002 11:47:00 255066 mwhp01c.exe
+09-01-2000 00:00:00 9813556 mwinoc1k.exe
+09-01-2000 00:00:00 8298211 mwinoc2k.exe
+02-20-2002 14:35:00 141530 mwipgrop.exe
+10-09-1996 00:00:00 566807 mwmis01.exe
+01-26-1999 00:00:00 1070535 mwnma26.exe
+10-16-1998 00:00:00 265545 mwnma3a.exe
+10-16-1998 00:00:00 300304 mwnma4a.exe
+04-12-2001 00:00:00 2180810 mwnmaupd.exe
+01-12-2000 00:00:00 154598 mwnxp26.exe
+04-09-2001 00:00:00 339201 mwnxpfix.exe
+08-20-1999 00:00:00 1502706 mwzen01.exe
+03-28-2001 00:00:00 58460632 mzfd3sp1.exe
+05-24-2001 00:00:00 2407200 n51_nis1.exe
+05-16-2000 00:00:00 6840962 n8slinux.01
+02-18-1998 00:00:00 217465 na4nty2k.exe
+09-18-1993 00:00:00 48510 nacs2.exe
+05-28-1996 00:00:00 64238 nadhep.exe
+03-23-1999 00:00:00 1777018 nal201p2.exe
+10-29-1996 00:00:00 38276 nam312.exe
+06-06-2000 00:00:00 199571 nam41d.exe
+05-24-2002 15:12:00 104382 nat600d.exe
+09-18-1993 00:00:00 322806 nbckup.exe
+01-10-1997 00:00:00 21328 nbora.exe
+08-12-1997 00:00:00 18843 nc1tip.txt
+06-03-2002 09:30:00 838367 nccutl10.exe
+10-24-2000 00:00:00 7725859 nce8slr1.z
+09-18-1993 00:00:00 77251 nconfg.exe
+10-10-1997 00:00:00 19889 ncpec.exe
+09-15-1997 00:00:00 144892 ncpy410a.exe
+11-21-2000 00:00:00 136962 ncs3.exe
+10-01-1996 00:00:00 704205 ncv201.exe
+10-01-1996 00:00:00 811853 ncv202.exe
+04-21-1998 00:00:00 489774 ncv20y2k.exe
+11-12-2001 00:00:00 142314 ndb410q.exe
+02-21-2002 13:36:00 820712 ndp21p3c.exe
+06-21-2002 12:06:00 853818 ndp21p4.exe
+10-17-2001 00:00:00 1008445 ndp2xp7.exe
+02-21-2002 13:53:00 530101 ndpcnw6.exe
+02-21-2002 13:48:00 909348 ndpcsp3a.exe
+07-17-2001 00:00:00 94047 ndpsinf.exe
+10-04-2001 00:00:00 11673051 ndpsw2k.exe
+09-18-1993 00:00:00 59495 ndr345.exe
+02-02-2000 00:00:00 112722 nds4ntp1.exe
+08-17-2000 00:00:00 507960 nds4ntu3.exe
+10-27-2000 00:00:00 3437308 nds8lnx1.gz
+07-17-2001 00:00:00 14133762 ndsas3s1.exe
+09-18-1993 00:00:00 24182 ndscsi.exe
+09-18-1993 00:00:00 41652 ndspx.exe
+07-20-2000 00:00:00 6712 ndssch.gz
+09-18-1993 00:00:00 20023 ndstac.exe
+09-18-1993 00:00:00 211239 ne286.exe
+10-24-2000 00:00:00 5894979 ned8slr1.z
+05-03-2000 00:00:00 82148198 nesn451a.exe
+12-20-2000 00:00:00 1157470 nesn51b.exe
+11-28-2001 00:00:00 866140 nesn51c.exe
+03-05-2002 06:54:00 865375 nesn51d.exe
+03-16-1999 00:00:00 138202 netarng3.exe
+02-04-1998 00:00:00 73822 netfr.exe
+03-12-1996 00:00:00 306211 netusr.exe
+02-04-1998 00:00:00 74054 netwbr.exe
+02-04-1998 00:00:00 56229 netwde.exe
+08-28-1996 00:00:00 27699 netwdk.exe
+02-02-1998 00:00:00 67776 netwes.exe
+02-12-1998 00:00:00 59210 netwit.exe
+08-28-1996 00:00:00 28000 netwno.exe
+09-10-1996 00:00:00 53501 netwru.exe
+08-23-1996 00:00:00 27741 netwsv.exe
+03-18-2002 15:27:00 1539054 nfap1sp1.exe
+08-22-1994 00:00:00 290090 nfs193.exe
+12-30-1997 00:00:00 2266015 nfs203.exe
+11-23-1998 00:00:00 2007873 nfs205.exe
+07-20-2001 00:00:00 6472548 nfs30sp2.exe
+03-06-2002 15:56:00 1310460 nfs30sp3a.exe
+05-29-2002 14:59:00 1186381 nfs3nwci.exe
+08-28-2001 00:00:00 5440307 nfs3sp1.exe
+02-02-2001 00:00:00 1280002 nfsadmn2.exe
+01-20-1998 00:00:00 38114 nfsmlaup.exe
+08-02-1999 00:00:00 35803 nfsnam23.exe
+09-18-1993 00:00:00 245015 ngm121.exe
+09-18-1993 00:00:00 1240500 ngm137.exe
+09-18-1993 00:00:00 145298 ngm138.exe
+09-18-1993 00:00:00 145010 ngm139.exe
+10-28-1993 00:00:00 781292 ngm170.exe
+12-03-1993 00:00:00 202093 ngm175.exe
+12-06-1993 00:00:00 201248 ngm176.exe
+05-26-1994 00:00:00 279862 ngm192.exe
+12-20-1996 00:00:00 945888 ngm211.exe
+08-05-1995 00:00:00 1403369 ngwadm.exe
+08-13-1996 00:00:00 1810533 ngwaup.exe
+10-31-1995 00:00:00 106862 ngwmfc.exe
+03-15-1995 00:00:00 662953 ngwsnc.exe
+09-18-1993 00:00:00 27283 ni5010.exe
+07-10-2001 00:00:00 786223 nicid157.exe
+07-10-2001 00:00:00 761229 nicie157.exe
+01-15-2002 11:38:00 59300 nicimig.tgz
+01-17-2002 09:35:00 6802379 nims265.tgz
+01-31-2002 10:19:00 2526415 nims265.zip
+01-31-2002 11:14:00 121765 nims265a.zip
+01-14-2002 16:09:00 4894646 nims26l.tgz
+01-14-2002 16:05:00 2492982 nims26n.zip
+02-27-2002 16:49:00 5385381 nims26sb.z
+11-08-1999 00:00:00 343052 nims2_1a.exe
+04-15-2002 15:54:00 12495019 nims303.tar.z
+04-15-2002 15:53:00 7563916 nims303.tgz
+04-15-2002 15:54:00 7696073 nims303.zip
+05-23-1997 00:00:00 1252720 nip202.exe
+10-23-1998 00:00:00 1180104 nip203.exe
+04-19-1996 00:00:00 6212358 nip22b.exe
+07-09-1996 00:00:00 555990 nip318.exe
+07-09-1996 00:00:00 69020 nip319.exe
+11-02-2001 00:00:00 2489203 nipp10a.exe
+09-19-2000 00:00:00 3753117 nipt1.exe
+03-05-1996 00:00:00 6369467 nipw22.exe
+05-29-1996 00:00:00 42316 nipw2x.exe
+06-07-2000 00:00:00 4286209 njcl5a.exe
+06-10-1998 00:00:00 102325 nls212.exe
+07-24-2000 00:00:00 155414 nlsdll.exe
+05-25-2001 00:00:00 16952278 nlslsp6.exe
+01-04-2000 00:00:00 215729 nlsty2k.exe
+11-28-1995 00:00:00 1462112 nms002.exe
+03-08-1994 00:00:00 48618 nmsddf.exe
+11-15-1994 00:00:00 434841 nmsxp1.exe
+09-18-1993 00:00:00 28499 nnscpt.exe
+09-18-1993 00:00:00 88964 nnst40.exe
+09-18-1993 00:00:00 89092 nnstll.exe
+07-25-1995 00:00:00 32483 not131.exe
+10-12-1998 00:00:00 6707094 notes41.exe
+06-08-2000 00:00:00 4556171 notes51.exe
+09-18-1993 00:00:00 24900 novadf.exe
+09-18-1993 00:00:00 30691 np600a.exe
+08-02-1999 00:00:00 17097790 nppb_1.exe
+08-02-1999 00:00:00 17097771 nppb_2.exe
+03-15-2002 16:33:00 633317 nps15dpa.exe
+06-28-2002 09:08:00 31183790 nps15sp1.exe
+09-10-2001 00:00:00 8200267 npsgad01.exe
+04-12-2001 00:00:00 114902 npsnsapi.exe
+02-11-2002 15:57:00 615863 nptr95b.exe
+06-30-1997 00:00:00 22915 nrsbuild.exe
+06-23-1997 00:00:00 793469 nrsnt.exe
+06-18-1998 00:00:00 53586091 nsbgwsp3.exe
+03-22-1994 00:00:00 455182 nsd004.exe
+08-21-1998 00:00:00 1964329 nsr511n.zip
+06-24-1996 00:00:00 1935353 nsrtr51n.zip
+02-13-2002 13:36:00 93785 nsschk1a.exe
+07-30-1997 00:00:00 116845 nsync1.exe
+09-16-1999 00:00:00 10933860 nt411b.exe
+10-26-1998 00:00:00 1084753 nt411p1.exe
+04-06-1999 00:00:00 744586 nt430p2.exe
+06-22-1999 00:00:00 834534 nt451p1.exe
+03-23-2000 00:00:00 920405 nt46pt1.exe
+10-03-1999 00:00:00 4017917 nt46sp2.exe
+08-11-2000 00:00:00 765887 nt471pt2.exe
+06-13-2000 00:00:00 448015 nt47pt3.exe
+05-09-2002 07:26:00 1081217 nt480pt5.exe
+11-06-2001 00:00:00 898400 nt481pt1.exe
+06-14-2002 14:07:00 291026 nt483pt2.exe
+11-19-1998 00:00:00 150230 ntprint.exe
+07-18-1995 00:00:00 114593 ntwin.exe
+09-18-1993 00:00:00 28138 nver30.exe
+01-07-1997 00:00:00 28798 nw3dfs.exe
+01-07-1997 00:00:00 24257 nw4dfs.exe
+11-13-2000 00:00:00 80377034 nw4sp9.exe
+08-27-2001 00:00:00 866224 nw4wsock.exe
+12-20-2000 00:00:00 158210659 nw50sp6a.exe
+07-23-2001 00:00:00 490599 nw51fs1.exe
+02-17-2000 00:00:00 684981 nw51inst.exe
+08-21-2001 00:00:00 513640 nw51nrm1.exe
+07-24-2001 00:00:00 294037836 nw51sp3.exe
+02-25-2002 11:26:00 300006242 nw51sp4.exe
+04-27-2000 00:00:00 332610 nw51upd1.exe
+06-24-2002 14:54:00 3250529 nw56up1.exe
+09-09-1999 00:00:00 38435 nw5nss.exe
+02-17-2000 00:00:00 251511 nw5nwip.exe
+05-16-2000 00:00:00 169896 nw5psrv2.exe
+06-20-2000 00:00:00 378831 nw5tcp.exe
+03-22-2002 16:33:00 1533365 nw6nss1a.exe
+10-22-2001 00:00:00 633339 nw6sms1a.exe
+03-06-2002 17:15:00 237785656 nw6sp1.exe
+03-11-2002 12:19:00 568253661 nw6sp1ef.exe
+03-11-2002 12:59:00 567439767 nw6sp1ef56.exe
+03-22-2002 12:23:00 566604272 nw6sp1eg.exe
+03-22-2002 13:02:00 566458792 nw6sp1ei.exe
+03-11-2002 10:57:00 566528467 nw6sp1ep.exe
+03-22-2002 16:05:00 567333526 nw6sp1er.exe
+03-13-2002 16:55:00 565955338 nw6sp1es.exe
+03-23-1999 00:00:00 1344552 nwadmnp1.exe
+05-10-1995 00:00:00 478371 nwc001.exe
+02-24-1995 00:00:00 251557 nwc002.exe
+01-31-1996 00:00:00 1598884 nwc201.exe
+11-22-1995 00:00:00 1524102 nwc202.exe
+01-22-1998 00:00:00 1328082 nwc206.exe
+11-18-1996 00:00:00 1382188 nwc207.exe
+10-24-1996 00:00:00 547655 nwc208.exe
+08-12-1997 00:00:00 29587 nwc2tp.txt
+09-18-1993 00:00:00 21386 nwc386.exe
+10-05-1994 00:00:00 102446 nwcdll.exe
+02-27-1996 00:00:00 33443 nwcinf.exe
+09-11-1998 00:00:00 22331 nwcmod.exe
+09-20-1995 00:00:00 71514 nwcncs.exe
+07-11-1997 00:00:00 37424 nwcppp.exe
+03-13-2000 00:00:00 36603464 nwcssp1.exe
+03-23-2000 00:00:00 170397 nwcsupd1.exe
+02-14-1995 00:00:00 367734 nwcwin.exe
+05-22-1998 00:00:00 367002 nwcwrt.exe
+12-16-1996 00:00:00 77941 nwda01.exe
+05-08-2002 12:21:00 147838 nwftpd6.exe
+01-29-2002 15:30:00 128211 nwipadm4.exe
+11-01-1995 00:00:00 571623 nwl11e.exe
+11-01-1995 00:00:00 579046 nwl11f.exe
+11-01-1995 00:00:00 585907 nwl11g.exe
+11-01-1995 00:00:00 576676 nwl11i.exe
+11-01-1995 00:00:00 579137 nwl11s.exe
+12-13-1995 00:00:00 184137 nwltid.exe
+04-09-1999 00:00:00 13639159 nwmac.exe
+03-23-1999 00:00:00 2264037 nwmp2.exe
+06-15-2001 00:00:00 16902888 nwovly2.exe
+04-02-1997 00:00:00 2536221 nwpa300.exe
+05-07-2001 00:00:00 155727 nwpa5.exe
+12-07-2001 00:00:00 254835 nwpapt2a.exe
+11-15-1993 00:00:00 26440 nwparr.exe
+06-09-1999 00:00:00 1341903 nwpaup1a.exe
+02-27-1997 00:00:00 7763041 nws25b.exe
+03-11-1999 00:00:00 73306 nwsaahpr.exe
+02-29-2000 00:00:00 12091868 nwsb41wa.exe
+08-11-1999 00:00:00 38819 nwsp2aai.exe
+02-04-2000 00:00:00 287681 nwsso.exe
+02-24-2000 00:00:00 112641 nwtape1.exe
+11-15-1993 00:00:00 66111 nwtlg.exe
+06-16-1995 00:00:00 126883 o31mci.exe
+07-25-1995 00:00:00 73911 o4crtl.exe
+04-27-1999 00:00:00 4957532 odbcbeta.exe
+11-08-2001 00:00:00 6488080 odemandb.exe
+07-28-1999 00:00:00 671132 odi33g.exe
+07-30-1997 00:00:00 409920 odiwan1.exe
+07-18-1995 00:00:00 48296 of31pt.exe
+09-18-1993 00:00:00 111219 os2p1.exe
+12-18-1998 00:00:00 610164 os2pt2.exe
+11-26-1996 00:00:00 896477 os2u1.exe
+05-30-2000 00:00:00 4229434 os4pt1.exe
+03-02-2001 00:00:00 4227811 os5pt2a.exe
+09-18-1993 00:00:00 216072 othdrv.exe
+12-04-1996 00:00:00 66394 ovvmdl.exe
+10-25-1996 00:00:00 26153 p1000.exe
+01-03-1995 00:00:00 242785 p10g05.exe
+01-03-1995 00:00:00 242514 p10i05.exe
+01-03-1995 00:00:00 242647 p10s05.exe
+05-12-1995 00:00:00 33893 p2scsi.exe
+05-11-1999 00:00:00 659891 pager1.exe
+09-18-1993 00:00:00 50002 pat301.exe
+09-18-1993 00:00:00 50015 pat303.exe
+09-18-1993 00:00:00 49975 pat304.exe
+09-18-1993 00:00:00 49878 pat306.exe
+09-18-1993 00:00:00 29863 pat311.exe
+09-18-1993 00:00:00 49888 pat312.exe
+09-18-1993 00:00:00 49823 pat314.exe
+09-18-1993 00:00:00 33034 pat315.exe
+09-18-1993 00:00:00 49900 pat317.exe
+09-18-1993 00:00:00 49858 pat321.exe
+09-18-1993 00:00:00 49901 pat323.exe
+09-18-1993 00:00:00 50232 pat326.exe
+09-18-1993 00:00:00 49842 pat334.exe
+09-18-1993 00:00:00 49930 pat354.exe
+09-18-1993 00:00:00 30322 patman.exe
+09-18-1993 00:00:00 35789 paudfx.exe
+07-20-1998 00:00:00 565974 pbm21y2k.exe
+06-15-1994 00:00:00 63237 pburst.exe
+09-18-1993 00:00:00 35522 pcn22x.exe
+09-18-1993 00:00:00 25266 pcn23x.exe
+09-18-1993 00:00:00 46823 pcn2pa.exe
+09-18-1993 00:00:00 30816 pcnscs.exe
+09-18-1993 00:00:00 22801 pdf.exe
+09-18-1993 00:00:00 38018 pdf311.exe
+08-27-2001 00:00:00 8552350 pdlckcmp.exe
+01-22-1997 00:00:00 699150 perfectf.exe
+06-07-2000 00:00:00 214597 pfar6.exe
+09-18-1993 00:00:00 26618 pfix1.exe
+09-18-1993 00:00:00 26734 pfix3.exe
+07-18-1995 00:00:00 23435 pifoff.exe
+08-26-1999 00:00:00 70799 pkernel.exe
+08-12-1999 00:00:00 252312 pkis.pdf
+02-08-2000 00:00:00 772450 pkisnmas.exe
+09-11-1995 00:00:00 63891 plpd8.exe
+03-28-2002 12:17:00 212389 plpdsoc2.exe
+12-13-1995 00:00:00 181777 pnwtid.exe
+11-17-1995 00:00:00 186437 pnxtfx.exe
+01-03-2001 00:00:00 4463887 preds8a.exe
+09-18-1993 00:00:00 75026 pro10.exe
+11-01-1995 00:00:00 28781 prog.exe
+09-18-1993 00:00:00 23802 propth.exe
+01-29-1996 00:00:00 25129 prt312.exe
+09-18-1993 00:00:00 31193 prtime.exe
+07-20-1995 00:00:00 130827 prupc.exe
+09-18-1993 00:00:00 34893 ps110.exe
+09-18-1993 00:00:00 25963 ps2286.exe
+09-18-1993 00:00:00 24576 ps22is.exe
+09-18-1993 00:00:00 42925 ps2311.exe
+09-18-1993 00:00:00 29867 ps2386.exe
+09-18-1993 00:00:00 28205 ps2cmb.exe
+09-18-1993 00:00:00 23372 ps2esd.exe
+09-18-1993 00:00:00 24336 ps2fx.exe
+09-18-1993 00:00:00 76207 ps2isa.exe
+09-18-1993 00:00:00 23955 ps2m57.exe
+12-14-1993 00:00:00 36351 ps2opt.exe
+06-30-1995 00:00:00 162367 ps3x02.exe
+01-23-1996 00:00:00 85566 ps4x03.exe
+10-24-2001 00:00:00 172677 psrvr112.exe
+09-18-1993 00:00:00 52324 ptf286.exe
+09-18-1993 00:00:00 112633 ptf350.exe
+09-18-1993 00:00:00 48074 ptf374.exe
+09-18-1993 00:00:00 39025 ptf378.exe
+09-18-1993 00:00:00 30600 ptf380.exe
+03-25-1994 00:00:00 525519 ptf410.exe
+09-29-1993 00:00:00 501986 ptf411.exe
+09-29-1993 00:00:00 350294 ptf412.exe
+09-29-1993 00:00:00 473981 ptf413.exe
+09-29-1993 00:00:00 480777 ptf414.exe
+09-29-1993 00:00:00 488223 ptf415.exe
+10-22-1993 00:00:00 147005 ptf424.exe
+08-14-1995 00:00:00 109320 ptf425.exe
+10-18-1993 00:00:00 109064 ptf426.exe
+10-07-1994 00:00:00 292097 ptf437.exe
+04-12-1994 00:00:00 464842 ptf569.exe
+11-16-1994 00:00:00 453054 pu3x01.exe
+05-02-1995 00:00:00 553603 pu4x03.exe
+05-29-2002 17:29:00 280505 pwdsnc1.exe
+05-31-2002 11:45:00 758411 pxy031.exe
+08-31-2000 00:00:00 785651 pxyauth.exe
+11-01-1995 00:00:00 25270 qa.exe
+07-25-1995 00:00:00 22674 qaos2.exe
+07-18-1995 00:00:00 21421 qkinst.exe
+12-04-1995 00:00:00 390862 qlfix.exe
+10-05-1995 00:00:00 21028 quest.exe
+11-23-1998 00:00:00 13847723 r16524br.exe
+11-23-1998 00:00:00 15367885 r16524ce.exe
+11-23-1998 00:00:00 13974046 r16524cf.exe
+11-23-1998 00:00:00 14636368 r16524cs.exe
+11-23-1998 00:00:00 14606815 r16524ct.exe
+11-23-1998 00:00:00 14396472 r16524cz.exe
+11-23-1998 00:00:00 14526983 r16524de.exe
+11-23-1998 00:00:00 13996151 r16524dk.exe
+11-24-1998 00:00:00 14083999 r16524es.exe
+11-24-1998 00:00:00 14036283 r16524fr.exe
+11-24-1998 00:00:00 14168496 r16524it.exe
+11-30-1998 00:00:00 15194180 r16524jp.exe
+11-24-1998 00:00:00 14717983 r16524kr.exe
+11-24-1998 00:00:00 13957605 r16524ma.exe
+11-24-1998 00:00:00 13018097 r16524nl.exe
+11-24-1998 00:00:00 14282386 r16524no.exe
+11-24-1998 00:00:00 14592999 r16524oz.exe
+11-24-1998 00:00:00 14683051 r16524pl.exe
+11-24-1998 00:00:00 13868667 r16524ru.exe
+11-24-1998 00:00:00 14075816 r16524su.exe
+11-24-1998 00:00:00 14090792 r16524sv.exe
+11-24-1998 00:00:00 14598010 r16524uk.exe
+11-23-1998 00:00:00 14633171 r16524us.exe
+10-08-1999 00:00:00 13892746 r16525br.exe
+10-08-1999 00:00:00 15412960 r16525ce.exe
+10-08-1999 00:00:00 14019075 r16525cf.exe
+10-08-1999 00:00:00 14681320 r16525cs.exe
+10-08-1999 00:00:00 14651741 r16525ct.exe
+10-08-1999 00:00:00 14443408 r16525cz.exe
+08-24-1999 00:00:00 14572069 r16525de.exe
+10-08-1999 00:00:00 14041141 r16525dk.exe
+10-08-1999 00:00:00 14129003 r16525es.exe
+10-08-1999 00:00:00 14081224 r16525fr.exe
+10-08-1999 00:00:00 14213472 r16525it.exe
+10-08-1999 00:00:00 15239217 r16525jp.exe
+10-08-1999 00:00:00 14762877 r16525kr.exe
+10-08-1999 00:00:00 14004580 r16525ma.exe
+10-08-1999 00:00:00 14520305 r16525nl.exe
+10-08-1999 00:00:00 14327373 r16525no.exe
+10-08-1999 00:00:00 14638087 r16525oz.exe
+10-08-1999 00:00:00 14730025 r16525pl.exe
+10-08-1999 00:00:00 13915653 r16525ru.exe
+10-08-1999 00:00:00 14120807 r16525su.exe
+10-08-1999 00:00:00 14135776 r16525sv.exe
+10-08-1999 00:00:00 14643087 r16525uk.exe
+08-24-1999 00:00:00 14678258 r16525us.exe
+11-20-1998 00:00:00 34195974 r524east.exe
+11-21-1998 00:00:00 29934184 r524kcc.exe
+11-21-1998 00:00:00 40546603 r524mult.exe
+11-21-1998 00:00:00 33377124 r524scan.exe
+11-21-1998 00:00:00 17133749 r524us.exe
+11-21-1998 00:00:00 20919099 r524usde.exe
+12-21-1998 00:00:00 21776910 r524usjp.exe
+10-11-1999 00:00:00 33695357 r525east.exe
+10-11-1999 00:00:00 29387389 r525kcc.exe
+10-11-1999 00:00:00 39896412 r525mult.exe
+10-11-1999 00:00:00 32843372 r525scan.exe
+10-11-1999 00:00:00 18205814 r525us.exe
+10-11-1999 00:00:00 20711743 r525usde.exe
+10-14-1999 00:00:00 19744627 r525usjp.exe
+02-25-1999 00:00:00 21883384 r551en.exe
+02-26-1999 00:00:00 38653860 r551mult.exe
+02-25-1999 00:00:00 38136537 r551scan.exe
+10-19-1999 00:00:00 37890181 r552mult.exe
+10-19-1999 00:00:00 37404535 r552scan.exe
+02-11-2000 00:00:00 22183381 r553aen.exe
+02-11-2000 00:00:00 32216751 r553aest.exe
+02-11-2000 00:00:00 24925427 r553ajp.exe
+02-11-2000 00:00:00 27749699 r553akcc.exe
+02-11-2000 00:00:00 38006860 r553amlt.exe
+02-11-2000 00:00:00 37454601 r553ascn.exe
+04-06-1998 00:00:00 87518 rad102.exe
+11-23-1998 00:00:00 260651 rad104.exe
+03-04-2002 16:09:00 118100 radatr4.exe
+02-28-1997 00:00:00 575303 ramac.exe
+06-07-1995 00:00:00 66500 rbuild.exe
+02-27-1998 00:00:00 23539 rdmsg0.exe
+04-03-2000 00:00:00 91403 revfhrft.exe
+07-27-2001 00:00:00 138359 rinstall.exe
+11-04-1993 00:00:00 211615 ripsap.exe
+09-18-1993 00:00:00 192641 rlit92.exe
+09-18-1993 00:00:00 155252 rlit93.exe
+07-18-1995 00:00:00 780218 rofwin.exe
+09-18-1993 00:00:00 36120 routez.exe
+09-18-1993 00:00:00 96684 routfx.exe
+10-05-1998 00:00:00 141605 rplkt5.exe
+05-11-1994 00:00:00 72596 rsync1.exe
+09-18-1993 00:00:00 23294 rxnet.exe
+03-28-2001 00:00:00 48534006 rzfd3sp1.exe
+06-16-1994 00:00:00 39313 saa005.exe
+08-16-2001 00:00:00 534003 saa008.exe
+08-11-1994 00:00:00 80219 saa010.exe
+09-01-1994 00:00:00 142620 saa012.exe
+08-16-2001 00:00:00 943059 saa023.exe
+12-24-1996 00:00:00 154805 saa031.exe
+08-16-2001 00:00:00 4659719 saa20040.exe
+08-16-2001 00:00:00 2550556 saa21030.exe
+08-15-2001 00:00:00 63688405 saa22010.exe
+08-15-2001 00:00:00 9396094 saa2210e.exe
+08-15-2001 00:00:00 10338946 saa30020.exe
+08-15-2001 00:00:00 9315931 saa40020.exe
+06-29-2000 00:00:00 169062 saa4pt1.exe
+06-27-1995 00:00:00 20269 saamic.exe
+06-03-1998 00:00:00 1646 saapatch.txt
+11-30-1995 00:00:00 32233 saarot.exe
+08-03-1995 00:00:00 29347 saaupg.exe
+06-26-1996 00:00:00 282396 sback6.exe
+06-07-2001 00:00:00 376293 sbcon1.exe
+07-20-1998 00:00:00 564045 sbm21y2k.exe
+03-02-2001 00:00:00 8341393 sbs5pt2a.exe
+12-05-1997 00:00:00 244604 schcmp2.exe
+08-12-1999 00:00:00 151673 scmddoc.exe
+05-04-2001 00:00:00 167290 scmdflt.exe
+09-18-1993 00:00:00 40595 sec286.exe
+05-25-1994 00:00:00 29661 secdoc.exe
+09-18-1993 00:00:00 310609 secdos.exe
+05-26-2000 00:00:00 7039614 secexp64.01
+08-10-2000 00:00:00 5016153 secexp64.z
+11-15-1993 00:00:00 167904 seclog.exe
+09-18-1993 00:00:00 632669 secnns.exe
+09-18-1993 00:00:00 449590 secprn.exe
+09-18-1993 00:00:00 322007 secsys.exe
+05-26-2000 00:00:00 7351040 secus64.01
+08-10-2000 00:00:00 5457221 secus64.z
+09-18-1993 00:00:00 510733 secut1.exe
+09-18-1993 00:00:00 556873 secut2.exe
+09-18-1993 00:00:00 431691 secut3.exe
+08-29-2001 00:00:00 134525 servinst.exe
+05-25-1996 00:00:00 367009 setdoc.exe
+09-18-1993 00:00:00 24541 setfx.exe
+09-18-1993 00:00:00 56904 setupc.exe
+07-21-1997 00:00:00 75076 sftpt2.exe
+09-18-1993 00:00:00 67817 sftutl.exe
+09-02-1999 00:00:00 49345 showenv1.exe
+11-06-1997 00:00:00 25774 silent.exe
+04-23-2002 09:38:00 181878 slp107g.exe
+08-28-2001 00:00:00 184680 slpinsp3.exe
+09-18-1993 00:00:00 59254 smfsel.exe
+01-31-1997 00:00:00 2747104 smsup6.exe
+09-25-1996 00:00:00 131884 smt121.exe
+11-10-1993 00:00:00 105031 smt171.exe
+05-24-1996 00:00:00 1764462 smtos2.exe
+10-11-1996 00:00:00 322954 smtp1.exe
+05-19-1997 00:00:00 441783 smtp2.exe
+09-19-1997 00:00:00 2862615 smtp3.exe
+07-14-1998 00:00:00 2882529 smtp4.exe
+08-01-1995 00:00:00 53164 smtslp.exe
+06-04-1996 00:00:00 298148 sna31a.exe
+03-07-2002 10:28:00 148302 snmpfix.exe
+07-20-1995 00:00:00 147265 snupd2.exe
+07-20-1995 00:00:00 163195 snupdu.exe
+07-14-2000 00:00:00 12675957 sp1_ce.02
+04-25-2000 00:00:00 9547499 sp1_edir.02
+09-20-1999 00:00:00 230035 sp3to3a.exe
+02-25-1998 00:00:00 74372 spanish.exe
+05-23-1996 00:00:00 5323860 srapi.exe
+11-08-1996 00:00:00 38967 srout4.exe
+07-24-2000 00:00:00 812158 srvinst.exe
+08-23-1996 00:00:00 54912 srvmn1.exe
+06-30-1995 00:00:00 38078 stampd.exe
+02-21-2002 11:15:00 126042 strmft1.exe
+08-23-2000 00:00:00 250945 strtl8a.exe
+03-08-1999 00:00:00 36114 stufkey5.exe
+02-11-1998 00:00:00 52344 stylbr.exe
+04-14-1997 00:00:00 50098 stylct.exe
+02-11-1998 00:00:00 35645 styles.exe
+02-11-1998 00:00:00 37446 stylfr.exe
+04-14-1997 00:00:00 77671 stylha.exe
+02-12-1998 00:00:00 36626 stylit.exe
+04-16-1997 00:00:00 55674 stylsc.exe
+12-12-1995 00:00:00 37297 supwin.exe
+09-18-1993 00:00:00 186171 sys233.exe
+09-18-1993 00:00:00 159178 sys368.exe
+05-10-1995 00:00:00 161608 sys376.exe
+09-18-1993 00:00:00 61914 syschk.exe
+09-18-1993 00:00:00 81300 syslod.exe
+07-14-1997 00:00:00 418576 tabnd2a.exe
+01-13-1997 00:00:00 9831871 tambt1.exe
+05-04-1998 00:00:00 172843 tback3.exe
+09-01-1998 00:00:00 58235 tbox7.exe
+05-04-1998 00:00:00 174964 tcopy2.exe
+05-18-1998 00:00:00 781603 tcp312.exe
+08-01-2001 00:00:00 1223056 tcp542u.exe
+04-10-2002 08:49:00 1124221 tcp553v.exe
+05-24-2002 16:03:00 1221375 tcp590s.exe
+04-15-2002 10:50:00 315088 tcp604m.exe
+05-07-2002 11:27:00 1118361 tcp604s.exe
+02-09-1998 00:00:00 6902826 tel01a.exe
+01-21-1998 00:00:00 97084 tel40d.exe
+09-18-1993 00:00:00 33888 tim286.exe
+09-18-1993 00:00:00 20453 tim386.exe
+11-02-1998 00:00:00 105037 timefx.exe
+03-29-1994 00:00:00 31521 tokws.exe
+01-25-2001 00:00:00 123321 trpmon.exe
+09-18-1993 00:00:00 26100 trxnet.exe
+08-21-1996 00:00:00 88579 tsa410.exe
+06-25-2002 10:29:00 724997 tsa5up9.exe
+01-12-2000 00:00:00 127632 tsands.exe
+11-02-1999 00:00:00 20771 ttsy2k.exe
+09-22-1995 00:00:00 19933 ttyncs.exe
+08-03-1994 00:00:00 834048 tux001.tar
+08-03-1994 00:00:00 845824 tux002.tar
+08-03-1994 00:00:00 1034240 tux003.tar
+08-03-1994 00:00:00 2764800 tux004.tar
+08-03-1994 00:00:00 2836480 tux005.tar
+08-03-1994 00:00:00 1252352 tux006.tar
+08-03-1994 00:00:00 1355264 tux007.tar
+08-03-1994 00:00:00 1710080 tux008.tar
+08-03-1994 00:00:00 3502080 tux009.tar
+08-03-1994 00:00:00 3573760 tux010.tar
+08-04-1994 00:00:00 258236 tux011.exe
+08-04-1994 00:00:00 258250 tux012.exe
+08-04-1994 00:00:00 304451 tux013.exe
+08-04-1994 00:00:00 602748 tux014.exe
+08-04-1994 00:00:00 351070 tux015.exe
+08-04-1994 00:00:00 460026 tux016.exe
+09-18-1993 00:00:00 34077 ucpy.exe
+09-18-1993 00:00:00 103354 udf355.exe
+02-22-2002 12:49:00 6994 unixinf1.tgz
+05-08-2002 14:56:00 107260 unixins2.tgz
+09-18-1993 00:00:00 25508 unld.exe
+09-18-1993 00:00:00 30830 unps2.exe
+09-18-1993 00:00:00 34857 up215c.exe
+01-06-1994 00:00:00 509794 upd311.exe
+09-18-1993 00:00:00 24330 ups.exe
+09-18-1993 00:00:00 20837 ups22.exe
+09-18-1993 00:00:00 45537 upsm70.exe
+07-22-1997 00:00:00 43743 upsnlm.exe
+10-13-1998 00:00:00 28519 userhlp.exe
+09-18-1993 00:00:00 134247 ut1192.exe
+01-14-1998 00:00:00 1632088 uxp203.exe
+11-19-1998 00:00:00 1389072 uxp205.exe
+06-06-2001 00:00:00 1341347 uxp23j.exe
+11-02-1995 00:00:00 1098502 uxpsft.exe
+11-01-1995 00:00:00 58761 v2014.exe
+09-18-1993 00:00:00 40944 v286ol.exe
+06-08-2001 00:00:00 95133 v_nfs914.exe
+09-18-1993 00:00:00 34278 vapvol.exe
+09-18-1993 00:00:00 38035 vgetsc.exe
+09-18-1993 00:00:00 52323 volinf.exe
+05-03-2001 00:00:00 2444080 vpn35e.exe
+02-13-2002 14:50:00 3838701 vpn36d.exe
+02-13-2002 14:48:00 3837285 vpn36e.exe
+08-31-1999 00:00:00 96158 vpnbs01.exe
+09-18-1993 00:00:00 49835 vrepfa.exe
+09-18-1993 00:00:00 86326 vrp215.exe
+07-31-1997 00:00:00 59336 vrp386.exe
+07-31-1997 00:00:00 43438 vrpa286.exe
+09-18-1993 00:00:00 486274 vrpels.exe
+09-18-1993 00:00:00 103958 vrpps2.exe
+09-09-1999 00:00:00 699367 w2n213.exe
+09-25-2000 00:00:00 771404 w2ny2k.exe
+06-04-1996 00:00:00 240179 wan31a.exe
+06-16-1995 00:00:00 135592 wanx02.exe
+07-20-2001 00:00:00 79254744 was351.exe
+09-18-1993 00:00:00 28100 watch.exe
+04-23-2002 10:17:00 7884641 waview71.exe
+07-09-2001 00:00:00 182961 wbdav51.exe
+08-21-1996 00:00:00 53418 web002.exe
+03-01-2001 00:00:00 182219 webdv51.exe
+10-05-1999 00:00:00 2574191 weblsp1.exe
+06-22-1998 00:00:00 3589741 webser31.exe
+11-01-1995 00:00:00 27063 welcom.exe
+04-23-2001 00:00:00 1887450 winntwms.exe
+07-17-1997 00:00:00 28104 wkstrk.exe
+07-20-1995 00:00:00 52547 wpca31.exe
+07-20-1995 00:00:00 89314 wpfm31.exe
+09-11-1995 00:00:00 41008 wpmdm2.exe
+07-18-1995 00:00:00 36824 wpofus.exe
+07-20-1995 00:00:00 51398 wprp31.exe
+07-18-1995 00:00:00 21092 wpvwin.exe
+03-15-1994 00:00:00 52738 wrkdoc.exe
+05-29-1997 00:00:00 683395 ws250c.exe
+05-29-1997 00:00:00 765234 ws251c.exe
+05-09-1997 00:00:00 212586 ws3tk2b.exe
+03-07-2002 12:40:00 371 ws_ftp.log
+06-29-1999 00:00:00 1507075 wtmc1.exe
+06-16-1995 00:00:00 32697 x400fx.exe
+06-09-1999 00:00:00 660700 x400nlm1.exe
+08-05-1999 00:00:00 1134477 x400os21.exe
+01-30-2002 17:09:00 156483 xconss9f.exe
+09-18-1993 00:00:00 49801 xld386.exe
+07-27-2001 00:00:00 1043685 zd2dmi1.exe
+12-19-2001 00:00:00 365016 zd2dstfx.exe
+07-20-2001 00:00:00 338947 zd2scan.exe
+03-01-2002 09:27:00 24341430 zd322k1.exe
+03-01-2002 09:35:00 24350056 zd322k2.exe
+03-14-2002 18:03:00 95330 zd32dupw.exe
+03-01-2002 09:16:00 26945103 zd32nw.exe
+03-01-2002 09:21:00 26937023 zd32nw4.exe
+02-05-2002 12:18:00 26944994 zd32nw5.exe
+07-29-2001 00:00:00 24349972 zd32p2k1.exe
+07-29-2001 00:00:00 24349896 zd32p2k2.exe
+07-29-2001 00:00:00 26936865 zd32pnw4.exe
+07-29-2001 00:00:00 26944944 zd32pnw5.exe
+01-29-2002 14:50:00 128456 zd3awsr1.exe
+12-19-2001 00:00:00 1242985 zd3ccpp.exe
+12-19-2001 00:00:00 304923 zd3dac.exe
+02-22-2002 15:31:00 537680 zd3dupws.exe
+11-08-2001 00:00:00 142260 zd3dxsnp.exe
+12-04-2001 00:00:00 94250 zd3idnt1.exe
+01-02-2002 00:00:00 1443132 zd3ntmsi.exe
+12-20-2001 00:00:00 15512136 zd3o8i1.exe
+12-19-2001 00:00:00 15575094 zd3o8i2.exe
+05-24-2002 10:03:00 204858 zd3rosnp.exe
+01-09-2002 00:00:00 298505 zd3scn.exe
+12-18-2001 00:00:00 149726 zd3wm95.exe
+01-09-2002 00:00:00 180523 zd3wminv.exe
+12-18-2001 00:00:00 166988 zd3wmsch.exe
+12-19-2001 00:00:00 195447 zd3wsmg.exe
+03-19-2002 14:21:00 381437 zd3xwsrg.exe
+04-18-2002 15:53:00 125036 zd3xwuol.exe
+04-18-2002 16:30:00 180429 zd3xzisw.exe
+03-14-2002 10:01:00 226961 zd3zpol.exe
+05-01-2002 17:41:00 1274878 zenintg.exe
+05-09-2001 00:00:00 1865 zenstart.txt
+02-23-2001 00:00:00 2408726 zfd2pt3b.exe
+03-21-2000 00:00:00 62396652 zfd2sp1.exe
+05-22-2001 00:00:00 293221 zfd2tsfx.exe
+10-20-2000 00:00:00 97309 zfd3site.exe
+11-23-2001 00:00:00 46890033 zfd3sp1a.exe
+08-10-2000 00:00:00 31452136 zfn101.exe
+11-23-2001 00:00:00 1647316 zfs2jvm.exe
+12-18-2001 00:00:00 215046 zfs2pt1.exe
+12-20-2001 00:00:00 262640 zfs2sel.exe
+01-18-2002 14:59:00 54876346 zfs2sp1a.exe
+02-05-2001 00:00:00 1903657 zfsdbpb.exe
+10-11-2000 00:00:00 96356 zisclr.exe
+12-19-2001 00:00:00 94293 zs2dbbk.exe
+12-20-2001 00:00:00 141307 zs2ipg.exe
+12-19-2001 00:00:00 1173802 zs2mibs.exe
+01-23-2002 08:51:00 943931 zs2ndst1.exe
+06-04-2002 13:23:00 516401 zs2rbs.exe
+03-13-2002 11:41:00 1095584 zs2smtp.exe
+12-05-2001 00:00:00 981014 zs2util2.exe
+06-06-2002 11:14:00 92151 zs3sch.exe
+10-13-1998 00:00:00 1039314 zw100p1.exe
+11-06-1998 00:00:00 194420 zw101p1.exe
+11-22-1999 00:00:00 1065727 zw110p3.exe
diff --git a/netwerk/test/gtest/parse-ftp/U-no_ug.in b/netwerk/test/gtest/parse-ftp/U-no_ug.in
new file mode 100644
index 0000000000..aedd9cff46
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-no_ug.in
@@ -0,0 +1,84 @@
+ftp> open casper.cs.uct.ac.za
+Connected to casper.cs.uct.ac.za.
+220 casper.cs.uct.ac.za FTP server (Version 6.00LS) ready.
+Name (ftp.cs.uct.ac.za:cyp):
+331 Guest login ok, send your email address as password.
+230- Welcome to
+230- __ _ _
+230- / _| |_ _ __ ___ ___ _ _ ___| |_ __ _ ___ ______ _
+230- | |_| __| '_ \ / __/ __|| | | |/ __| __| / _` |/ __| |_ / _` |
+230- | _| |_| |_) | (__\__ \| |_| | (__| |_ | (_| | (__ _ / / (_| |
+230- |_| \__| .__(_)___|___(_)__,_|\___|\__(_)__,_|\___(_)___\__,_|
+230- |_|
+230-
+230- Publicly available downloads can be found in /pub.
+230-
+230- Do not put anything in /incoming unless it is by prior arrangement.
+230- If we do not know what it is, it will be deleted.
+230-
+230- A complete listing of available files can be found in /pub/ls-lR.gz
+230- and /pub/ls-lR. This list is updated daily at 02:00 SAST.
+230-
+230- Contents of /pub:
+230- CVC/ - Collaborative Visual Computing Laboratory
+230- DB/ - Database Laboratory
+230- DNA/ - The Data Network Architectures Laboratory
+230- FreeBSD/ - The FreeBSD Operating System
+230- distfiles/ - ports source tarball's
+230- releases/ - self-made releases
+230- OpenBSD/ - The OpenBSD Operating System
+230- windows/ - Software for Microsoft Windows
+230- ssh/ - secure shell clients
+230-
+230- This FTP site is also searchable via archie,
+230- and the web: http://ftp.cs.uct.ac.za:8000/ftpsearch
+230-
+230- UCT/UNINET users should also peruse the following
+230- anonymous FTP servers for additional resources:
+230-
+230- ftp://ftp.leg.uct.ac.za/ [ http://www.leg.uct.ac.za/ ]
+230- ftp://ftp.adamastor.ac.za/ [ http://ftp.adamastor.ac.za/ ]
+230- ftp://cabbagewrath.its.uct.ac.za/ [ don't ask about the name ]
+230-
+230- --
+230- ftp-admin@cs.uct.ac.za
+230 Guest login ok, access restrictions apply.
+Remote system type is UNIX.
+Using binary mode to transfer files.
+ftp> syst
+215 UNIX Type: L8 Version: BSD-199506
+ftp> ls
+200 PORT command successful.
+150 Opening ASCII mode data connection for '/bin/ls'.
+total 1092
+drwxr-xr-x 2 0 0 512 May 28 22:17 etc
+drwx-wx-wt 2 0 0 512 Jul 1 02:15 incoming
+drwx--x--x 2 0 0 512 Jul 3 12:19 private
+drwxr-xr-x 9 0 0 512 Jul 11 01:02 pub
+drwxr-xr-x 2 0 5 512 May 28 22:24 CVC
+drwxr-xr-x 2 0 5 512 Jul 31 2000 DB
+drwxr-xr-x 5 0 5 512 May 28 22:24 DNA
+drwxr-xr-x 4 0 0 512 Jun 12 22:13 FreeBSD
+drwxr-xr-x 3 0 5 512 May 28 22:24 OpenBSD
+drwxr-xr-x 2 0 5 512 Jun 3 15:51 Solaris
+-rw-r--r-- 1 0 0 158918 Jul 11 01:02 ls-lR
+-rw-r--r-- 1 0 0 26569 Jul 11 01:02 ls-lR.gz
+drwxr-xr-x 3 0 5 512 May 28 22:24 windows
+lrwx------ 1 0 0 25 Jun 12 22:10 4.6-RELEASE -> releases/i386/4.6-RELEASE
+lrwx------ 1 0 0 24 Jun 12 22:13 ISO-IMAGES -> releases/i386/ISO-IMAGES
+-rw-r--r-- 1 0 5 157 May 28 22:27 README.TXT
+-rw-r--r-- 1 0 5 872048 Sep 23 2001 cvsup-16.1e.tgz
+lrwxr-xr-x 1 0 0 15 May 28 22:26 distfiles -> ports/distfiles
+lrwxr-xr-x 1 0 0 14 Jun 6 17:12 packages -> ports/packages
+drwxr-xr-x 5 0 0 512 Jun 23 18:47 ports
+drwxr-xr-x 3 0 0 512 May 28 22:27 releases
+-rw-r--r-- 1 0 0 1538 Mar 15 2001 ftpmotd
+ftp> cd incoming
+250 CWD command successful.
+ftp> ls
+200 PORT command successful.
+150 Opening ASCII mode data connection for '/bin/ls'.
+ftpd: .: Permission denied
+226 Transfer complete.
+ftp> close
+221 Goodbye.
diff --git a/netwerk/test/gtest/parse-ftp/U-no_ug.out b/netwerk/test/gtest/parse-ftp/U-no_ug.out
new file mode 100644
index 0000000000..5200a9b0e1
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-no_ug.out
@@ -0,0 +1,26 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+05-28-2002 22:17:00 <DIR> etc
+07-01-2002 02:15:00 <DIR> incoming
+07-03-2002 12:19:00 <DIR> private
+07-11-2002 01:02:00 <DIR> pub
+05-28-2002 22:24:00 <DIR> CVC
+07-31-2000 00:00:00 <DIR> DB
+05-28-2002 22:24:00 <DIR> DNA
+06-12-2002 22:13:00 <DIR> FreeBSD
+05-28-2002 22:24:00 <DIR> OpenBSD
+06-03-2002 15:51:00 <DIR> Solaris
+07-11-2002 01:02:00 158918 ls-lR
+07-11-2002 01:02:00 26569 ls-lR.gz
+05-28-2002 22:24:00 <DIR> windows
+06-12-2002 22:10:00 <JUNCTION> 4.6-RELEASE -> releases/i386/4.6-RELEASE
+06-12-2002 22:13:00 <JUNCTION> ISO-IMAGES -> releases/i386/ISO-IMAGES
+05-28-2002 22:27:00 157 README.TXT
+09-23-2001 00:00:00 872048 cvsup-16.1e.tgz
+05-28-2002 22:26:00 <JUNCTION> distfiles -> ports/distfiles
+06-06-2002 17:12:00 <JUNCTION> packages -> ports/packages
+06-23-2002 18:47:00 <DIR> ports
+05-28-2002 22:27:00 <DIR> releases
+03-15-2001 00:00:00 1538 ftpmotd
diff --git a/netwerk/test/gtest/parse-ftp/U-nogid.in b/netwerk/test/gtest/parse-ftp/U-nogid.in
new file mode 100644
index 0000000000..15f77d1ee3
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-nogid.in
@@ -0,0 +1,82 @@
+ftp> open export.lcs.mit.edu
+Connected to ftp.x.org.
+220-Welcome to the ftp.x.org server.
+220-
+220-This server is provided and supported by The Open Group.
+220-
+220 ftp.x.org FTP server (Version wu-2.6.2(2) Mon Dec 17 13:03:41 GMT 2001) ready.
+Name (export.lcs.mit.edu:cyp):
+331 Guest login ok, send your complete e-mail address as password.
+230-
+230-Welcome to ftp.x.org, the X11 anonymous FTP archive of X.Org.
+230-
+230-PLEASE NOTE THE INCOMING FACILITY IS PRESENTLY UNAVAILABLE
+230-
+230-All activity is logged with your host name and email address.
+230-
+230-REPORTING PROBLEMS - Please report all problems to xorg_info@x.org.
+230-
+230-LOGIN PROBLEMS - If your FTP client crashes or hangs shortly after
+230-login, try using a dash (-) as the first character of your password.
+230-This turns off the informational messages that may be confusing
+230-your ftp client.
+230-
+230-
+230-Please read the file README
+230- it was last modified on Tue Apr 24 15:33:13 2001 - 444 days ago
+230 User ftp logged in. Access restrictions apply.
+Remote system type is UNIX.
+Using binary mode to transfer files.
+ftp> ls
+200 PORT command successful.
+150 Opening ASCII mode data connection for /bin/ls.
+total 5106
+-rw-r--r-- 1 X.Org 91 May 7 2001 .banner
+---------- 1 X.Org 0 May 30 2000 .notar
+-rw-r--r-- 1 X.Org 885 May 27 1999 .welcome
+-rw-r--r-- 1 X.Org 61 Jul 20 2001 30_days_changes
+-rw-r--r-- 1 X.Org 61 Jul 20 2001 7_days_changes
+-rw-r--r-- 1 X.Org 1456 Jul 20 2001 90_days_changes
+-rw-r--r-- 1 X.Org 3075 Oct 7 1998 GettingBroadway
+-rw-r--r-- 1 X.Org 3075 Oct 7 1998 GettingR6.3
+-rw-r--r-- 1 X.Org 1847 Feb 1 1999 GettingR6.4
+-rw-r--r-- 1 X.Org 1378 Aug 24 2000 GettingR6.5.1
+-rw-r--r-- 1 X.Org 1229 Apr 24 2001 GettingR6.6
+-rw-r--r-- 1 X.Org 194 Mar 11 1997 MIRROR.README
+drwxr-xr-x 56 X.Org 22016 May 4 2001 R5contrib
+-r--r--r-- 1 X.Org 1314 Apr 24 2001 README
+-rw-r--r-- 2 X.Org 479 Nov 6 2001 banner2
+lrwxrwxrwx 1 X.Org 7 Aug 8 2001 bin -> usr/bin
+drwxr-xr-x 34 X.Org 1024 Nov 6 2001 contrib
+d--x--x--x 2 X.Org 512 Aug 8 2001 dev
+drwxr-xr-x 2 X.Org 512 Oct 12 1998 digest
+d--x--x--x 3 X.Org 512 Aug 8 2001 etc
+-rw-r--r-- 1 X.Org 2135632 Jul 20 2001 ls-lR
+-rw-r--r-- 1 X.Org 376221 May 8 2001 ls-lR.Z
+drwxr-xr-x 2 X.Org 512 Sep 29 1998 private
+drwxr-xr-x 17 X.Org 512 May 4 2001 pub
+drwxr-xr-x 2 X.Org 512 May 4 2001 rcsfaq
+d--x--x--x 5 X.Org 512 Aug 8 2001 usr
+-rw-r--r-- 2 X.Org 479 Nov 6 2001 welcome.msg
+drwxr-xr-x 2 X.Org 512 May 4 2001 10R3
+drwxr-xr-x 2 X.Org 512 May 4 2001 10R4
+-rw-r--r-- 1 X.Org 9740 Mar 2 1999 Contrib.howto
+drwxr-xr-x 9 X.Org 512 Jun 2 2001 DOCS
+drwxr-xr-x 2 X.Org 512 May 4 2001 R1
+drwxr-xr-x 2 X.Org 512 May 4 2001 R2
+drwxr-xr-x 2 X.Org 512 May 4 2001 R3
+drwxr-xr-x 7 X.Org 512 May 4 2001 R4
+drwxr-xr-x 4 X.Org 512 May 4 2001 R5
+drwxr-xr-x 4 X.Org 512 May 4 2001 R6
+drwxr-xr-x 5 X.Org 512 May 4 2001 R6.1
+drwxr-xr-x 6 X.Org 512 May 4 2001 R6.3
+drwxr-xr-x 8 X.Org 512 Aug 10 2001 R6.4
+drwxr-xr-x 7 X.Org 512 Aug 9 2001 R6.5.1
+drwxr-xr-x 8 X.Org 512 May 2 2001 R6.6
+drwxr-xr-x 10 X.Org 512 May 4 2001 unsupported
+226 Transfer complete.
+ftp> close
+221-You have transferred 0 bytes in 0 files.
+221-Total traffic for this session was 2686 bytes in 1 transfers.
+221-Thank you for using the FTP service on ftp.x.org.
+221 Goodbye.
diff --git a/netwerk/test/gtest/parse-ftp/U-nogid.out b/netwerk/test/gtest/parse-ftp/U-nogid.out
new file mode 100644
index 0000000000..9a31407c8f
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-nogid.out
@@ -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/. -->
+
+05-07-2001 00:00:00 91 .banner
+05-30-2000 00:00:00 0 .notar
+05-27-1999 00:00:00 885 .welcome
+07-20-2001 00:00:00 61 30_days_changes
+07-20-2001 00:00:00 61 7_days_changes
+07-20-2001 00:00:00 1456 90_days_changes
+10-07-1998 00:00:00 3075 GettingBroadway
+10-07-1998 00:00:00 3075 GettingR6.3
+02-01-1999 00:00:00 1847 GettingR6.4
+08-24-2000 00:00:00 1378 GettingR6.5.1
+04-24-2001 00:00:00 1229 GettingR6.6
+03-11-1997 00:00:00 194 MIRROR.README
+05-04-2001 00:00:00 <DIR> R5contrib
+04-24-2001 00:00:00 1314 README
+11-06-2001 00:00:00 479 banner2
+08-08-2001 00:00:00 <JUNCTION> bin -> usr/bin
+11-06-2001 00:00:00 <DIR> contrib
+08-08-2001 00:00:00 <DIR> dev
+10-12-1998 00:00:00 <DIR> digest
+08-08-2001 00:00:00 <DIR> etc
+07-20-2001 00:00:00 2135632 ls-lR
+05-08-2001 00:00:00 376221 ls-lR.Z
+09-29-1998 00:00:00 <DIR> private
+05-04-2001 00:00:00 <DIR> pub
+05-04-2001 00:00:00 <DIR> rcsfaq
+08-08-2001 00:00:00 <DIR> usr
+11-06-2001 00:00:00 479 welcome.msg
+05-04-2001 00:00:00 <DIR> 10R3
+05-04-2001 00:00:00 <DIR> 10R4
+03-02-1999 00:00:00 9740 Contrib.howto
+06-02-2001 00:00:00 <DIR> DOCS
+05-04-2001 00:00:00 <DIR> R1
+05-04-2001 00:00:00 <DIR> R2
+05-04-2001 00:00:00 <DIR> R3
+05-04-2001 00:00:00 <DIR> R4
+05-04-2001 00:00:00 <DIR> R5
+05-04-2001 00:00:00 <DIR> R6
+05-04-2001 00:00:00 <DIR> R6.1
+05-04-2001 00:00:00 <DIR> R6.3
+08-10-2001 00:00:00 <DIR> R6.4
+08-09-2001 00:00:00 <DIR> R6.5.1
+05-02-2001 00:00:00 <DIR> R6.6
+05-04-2001 00:00:00 <DIR> unsupported
diff --git a/netwerk/test/gtest/parse-ftp/U-proftpd.in b/netwerk/test/gtest/parse-ftp/U-proftpd.in
new file mode 100644
index 0000000000..0f8aa138da
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-proftpd.in
@@ -0,0 +1,21 @@
+ftp> open ftp.netmanage.com
+Connected to ftp.netmanage.com.
+220 ProFTPD 1.2.4 Server (Netmanage FTP Server) [156.27.8.3]
+Name (ftp.netmanage.com:cyp):
+331 Anonymous login ok, send your complete email address as your password.
+230 Anonymous access granted, restrictions apply.
+Remote system type is UNIX.
+Using binary mode to transfer files.
+ftp> ls
+200 PORT command successful.
+150 Opening ASCII mode data connection for file list.
+drwxr-xr-x 13 ftp ftp 344 Oct 17 2001 pub
+drwxrwxr-x 7 ftp support 304 May 14 16:56 support
+-rw-r--r-- 1 ftp ftp 508 Aug 19 1998 welcome.msg
+226-Transfer complete.
+226 Quotas off
+ftp> syst
+215 UNIX Type: L8
+ftp> close
+221 Goodbye.
+ftp> \ No newline at end of file
diff --git a/netwerk/test/gtest/parse-ftp/U-proftpd.out b/netwerk/test/gtest/parse-ftp/U-proftpd.out
new file mode 100644
index 0000000000..02d7a18432
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-proftpd.out
@@ -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/. -->
+
+10-17-2001 00:00:00 <DIR> pub
+05-14-2002 16:56:00 <DIR> support
+08-19-1998 00:00:00 508 welcome.msg
diff --git a/netwerk/test/gtest/parse-ftp/U-wu.in b/netwerk/test/gtest/parse-ftp/U-wu.in
new file mode 100644
index 0000000000..e4a822a093
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-wu.in
@@ -0,0 +1,71 @@
+ftp> open sunsite.unc.edu
+Connected to sunsite.unc.edu.
+220 divahouse.metalab.unc.edu FTP server (Version wu-2.6.1(4) Thu Nov 29 23:05:07 EST 2001) ready.
+Name (sunsite.unc.edu:cyp):
+331 Guest login ok, send your complete e-mail address as password.
+230-
+230- Welcome to ibiblio.org's FTP archives!
+230- formerly known as MetaLab.unc.edu
+230-
+230-
+230-For more information about services offered by ibiblio.org,
+230-browse to http://ibiblio.org/faq
+230-
+230-You can access this archive via HTTP with the same URL.
+230-
+230-example: ftp://ibiblio.org/pub/Linux/ becomes
+230- http://ibiblio.org/pub/Linux/, but we prefer you use FTP.
+230-
+230-You can get tarred directories if you issue a "get dirname.tar"
+230-You can also get gzipped or compressed tarred directories by following
+230-the .tar with .gz or .Z, respectively. Please don't issue either of
+230-these commands to get Linux distributions. They are already compressed,
+230-so this only generates unnecessary CPU overhead for us.
+230-
+230-************************************************************************
+230-
+230-Please use LSM documentation when submitting to the linux archive.
+230-Anything submitted without an LSM will be rejected! You'll get an
+230-email form letter about it if we can figure out who you are.
+230-
+230-To learn more about submitting an LSM,
+230-see: http://www.ibiblio.org/pub/Linux/howtosubmit.html
+230-or ftp://www.ibiblio.org/pub/Linux/HOW.TO.SUBMIT
+230-
+230-***********************************************************************
+230-
+230-If you mirror a site on ibiblio, please subscribe to our mirror list:
+230-http://lists.ibiblio.org/mailman/listinfo/ibiblio-mirrors
+230-
+230-Have suggestions or questions? Please mail ftpkeeper@ibiblio.org.
+230-
+230-
+230-Please read the file README
+230- it was last modified on Tue Feb 19 09:46:53 2002 - 140 days ago
+230 Guest login ok, access restrictions apply.
+Remote system type is UNIX.
+Using binary mode to transfer files.
+ftp> syst
+215 UNIX Type: L8
+ftp> ls
+200 PORT command successful.
+150 Opening ASCII mode data connection for /bin/ls.
+total 78744
+-r--r--r-- 1 root other 80507411 Jul 9 06:09 IAFA-LISTINGS
+-rw-r--r-- 1 root root 1393 Feb 19 14:46 README
+dr-xr-xr-x 2 root other 4096 Mar 27 2001 bin
+dr-xr-xr-x 2 root other 4096 Jul 16 1997 dev
+dr-xr-xr-x 2 root other 4096 May 30 14:35 etc
+drwxrwxrwx 18 ftp 20 4096 Jun 10 13:05 incoming
+drwxr-xr-x 2 root root 4096 Mar 27 2001 lib
+lrwxrwxrwx 1 root other 13 Mar 16 2001 ls-lR -> IAFA-LISTINGS
+dr-xr-xr-x 18 root root 4096 May 31 19:46 pub
+dr-xr-xr-x 3 root other 4096 Jun 26 2000 unc
+dr-xr-xr-x 5 root other 4096 Jul 16 1997 usr
+226 Transfer complete.
+ftp> close
+221-You have transferred 0 bytes in 0 files.
+221-Total traffic for this session was 2777 bytes in 1 transfers.
+221-Thank you for using the FTP service on divahouse.metalab.unc.edu.
+221 Goodbye.
+ftp> \ No newline at end of file
diff --git a/netwerk/test/gtest/parse-ftp/U-wu.out b/netwerk/test/gtest/parse-ftp/U-wu.out
new file mode 100644
index 0000000000..1ffbf23eb2
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/U-wu.out
@@ -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/. -->
+
+07-09-2002 06:09:00 80507411 IAFA-LISTINGS
+02-19-2002 14:46:00 1393 README
+03-27-2001 00:00:00 <DIR> bin
+07-16-1997 00:00:00 <DIR> dev
+05-30-2002 14:35:00 <DIR> etc
+06-10-2002 13:05:00 <DIR> incoming
+03-27-2001 00:00:00 <DIR> lib
+03-16-2001 00:00:00 <JUNCTION> ls-lR -> IAFA-LISTINGS
+05-31-2002 19:46:00 <DIR> pub
+06-26-2000 00:00:00 <DIR> unc
+07-16-1997 00:00:00 <DIR> usr
diff --git a/netwerk/test/gtest/parse-ftp/V-MultiNet.in b/netwerk/test/gtest/parse-ftp/V-MultiNet.in
new file mode 100644
index 0000000000..04e66774e6
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/V-MultiNet.in
@@ -0,0 +1,34 @@
+Connected to acavax.lynchburg.edu.
+220 acavax.lynchburg.edu MultiNet FTP Server Process V4.0(15) at Mon 1-Jul-02 10:51AM-EDT
+Name (acavax.lynchburg.edu:cyp): 331 anonymous user ok. Send real ident as password.
+230-Guest User FOO@BAR.COM logged into ACA:[ANONYMOUS] at Mon 1-Jul-02 10:51AM-EDT, job 2b69.
+230 Access restrictions apply
+Remote system type is VMS.
+ftp> passive
+Passive mode on.
+ftp> pwd
+257 "ACA:[ANONYMOUS]" is current directory.
+ftp> ls
+227 Entering passive mode; use PORT 161,115,147,1,10,31
+150 List started.
+
+ACA:[ANONYMOUS]
+
+AUTOEXEC.BAT;1 %RMS-E-PRV, insufficient privilege or file protection violation
+BOURNE.DIR;1 1 28-OCT-1994 10:05 [ANONYMOUS] (RWE,RWE,RE,RWE)
+FTP_SERVER.LOG;8171 0 1-JUL-2002 10:51 [ANONYMOUS] (RWED,RWED,,)
+LOGIN.COM;2 1 4-NOV-1994 14:09 [ANONYMOUS] (RWE,RWE,,)
+NICELY.DIR;1 1 14-NOV-1994 14:17 [501,120] (RWED,RWED,RE,RE)
+NWLINK.386;1 66 7-AUG-1995 14:32 [ANONYMOUS] (RWED,RWED,,RE)
+PUB.DIR;1 1 27-JAN-1994 14:46 [ANONYMOUS] (RWE,RWE,RE,RWE)
+README.FTP;1 %RMS-E-PRV, insufficient privilege or file protection violation
+ROUSSOS.DIR;1 1 27-JAN-1994 14:48 [CS,ROUSSOS] (RWE,RWE,RE,R)
+S67-50903.JPG;1 328 22-SEP-1998 16:19 [ANONYMOUS] (RWED,RWED,,)
+S71-43543.JPG;1 310 22-SEP-1998 16:20 [ANONYMOUS] (RWED,RWED,,)
+TEAGUE.DIR;1 1 1-JUL-1995 12:23 [ANONYMOUS] (RWE,RWE,RE,RE)
+
+
+Total of 710 blocks in 12 files.
+226 Transfer completed.
+ftp> quit
+221 QUIT command received. Goodbye.
diff --git a/netwerk/test/gtest/parse-ftp/V-MultiNet.out b/netwerk/test/gtest/parse-ftp/V-MultiNet.out
new file mode 100644
index 0000000000..a6cddd6191
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/V-MultiNet.out
@@ -0,0 +1,14 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+10-28-1994 10:05:00 <DIR> BOURNE
+07-01-2002 10:51:00 0 FTP_SERVER.LOG
+11-04-1994 14:09:00 512 LOGIN.COM
+11-14-1994 14:17:00 <DIR> NICELY
+08-07-1995 14:32:00 33792 NWLINK.386
+01-27-1994 14:46:00 <DIR> PUB
+01-27-1994 14:48:00 <DIR> ROUSSOS
+09-22-1998 16:19:00 167936 S67-50903.JPG
+09-22-1998 16:20:00 158720 S71-43543.JPG
+07-01-1995 12:23:00 <DIR> TEAGUE
diff --git a/netwerk/test/gtest/parse-ftp/V-VMS-mix.in b/netwerk/test/gtest/parse-ftp/V-VMS-mix.in
new file mode 100644
index 0000000000..07ebd7bbac
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/V-VMS-mix.in
@@ -0,0 +1,22 @@
+This is a composite of 'difficult' VMS (MultiNet/UCX/CMU) LIST lines.
+CAUTION WHILE EDITING - lines may have dangling CRs! (intentional)
+
+
+Directory DISK$VOL:[DIR1.DIR2]
+DISK$VOL:
+ACA:[ANONYMOUS]
+
+AUTOEXEC.BAT;1 %RMS-E-PRV, insufficient privilege or file protection violation
+README.FTP;1 insufficient privilege or file protection violation
+LOGIN.COM;2 1 4-NOV-1994 14:09 [ANONYMOUS] (RWE,RWE,,)
+CII-MANUAL.TEX;1 213/216 29-JAN-1996 03:33:12 [ANONYMOU,ANONYMOUS] (RWED,RWED,,)
+THIS-IS-A-LONG-VMS-FILENAME.WITH-CR-TO-NEXT-LINE 213/216 29-JAN-1996 03:33:12 [ANONYMOU,ANONYMOUS] (RWED,RWED,,)
+ANOTHER-LONG-VMS-FILENAME.WITH-LF-TO-NEXT-LINE
+ 213 29-JAN-1996 03:33 [ANONYMOU,ANONYMOUS] (RWED,RWED,,)
+[A.DIR]CMU-VMS-IP-FTP-FILE;1 1/3 5-MAR-1993 18:09
+MAX_FILESIZE.FILE;1 2199023255040/4294967295 5-MAR-1993 18:09
+
+Total of 710 blocks in 12 files.
+226 Transfer completed.
+ftp> quit
+221 QUIT command received. Goodbye.
diff --git a/netwerk/test/gtest/parse-ftp/V-VMS-mix.out b/netwerk/test/gtest/parse-ftp/V-VMS-mix.out
new file mode 100644
index 0000000000..b8192f5ef9
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/V-VMS-mix.out
@@ -0,0 +1,10 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+11-04-1994 14:09:00 512 LOGIN.COM
+01-29-1996 03:33:12 109056 CII-MANUAL.TEX
+01-29-1996 03:33:12 109056 THIS-IS-A-LONG-VMS-FILENAME.WITH-CR-TO-NEXT-LINE
+01-29-1996 03:33:00 109056 ANOTHER-LONG-VMS-FILENAME.WITH-LF-TO-NEXT-LINE
+03-05-1993 18:09:00 512 CMU-VMS-IP-FTP-FILE
+03-05-1993 18:09:00 2199023255040 MAX_FILESIZE.FILE
diff --git a/netwerk/test/gtest/parse-ftp/moz.build b/netwerk/test/gtest/parse-ftp/moz.build
new file mode 100644
index 0000000000..04961f33f5
--- /dev/null
+++ b/netwerk/test/gtest/parse-ftp/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/.
+
+UNIFIED_SOURCES += [
+ "TestParseFTPList.cpp",
+]
+
+TEST_HARNESS_FILES.gtest += [
+ "3-guess.in",
+ "3-guess.out",
+ "C-VMold.in",
+ "C-VMold.out",
+ "C-zVM.in",
+ "C-zVM.out",
+ "D-WinNT.in",
+ "D-WinNT.out",
+ "E-EPLF.in",
+ "E-EPLF.out",
+ "O-guess.in",
+ "O-guess.out",
+ "R-dls.in",
+ "R-dls.out",
+ "U-HellSoft.in",
+ "U-HellSoft.out",
+ "U-hethmon.in",
+ "U-hethmon.out",
+ "U-murksw.in",
+ "U-murksw.out",
+ "U-ncFTPd.in",
+ "U-ncFTPd.out",
+ "U-NetPresenz.in",
+ "U-NetPresenz.out",
+ "U-NetWare.in",
+ "U-NetWare.out",
+ "U-no_ug.in",
+ "U-no_ug.out",
+ "U-nogid.in",
+ "U-nogid.out",
+ "U-Novonyx.in",
+ "U-Novonyx.out",
+ "U-proftpd.in",
+ "U-proftpd.out",
+ "U-Surge.in",
+ "U-Surge.out",
+ "U-WarFTPd.in",
+ "U-WarFTPd.out",
+ "U-WebStar.in",
+ "U-WebStar.out",
+ "U-WinNT.in",
+ "U-WinNT.out",
+ "U-wu.in",
+ "U-wu.out",
+ "V-MultiNet.in",
+ "V-MultiNet.out",
+ "V-VMS-mix.in",
+ "V-VMS-mix.out",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/netwerk/streamconv/converters",
+]
+
+FINAL_LIBRARY = "xul-gtest"
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..b093656ff4
--- /dev/null
+++ b/netwerk/test/gtest/urltestdata.json
@@ -0,0 +1,6480 @@
+[
+ "# 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": "null",
+ "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": ""
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "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": "",
+ "skip": true
+ },
+ {
+ "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": "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": ""
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "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": "",
+ "skip": true
+ },
+ {
+ "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"
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "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",
+ "skip": true
+ },
+ {
+ "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": "null",
+ "protocol": "gopher:",
+ "username": "",
+ "password": "",
+ "host": "foo",
+ "hostname": "foo",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "gopher://foo:443/",
+ "base": "about:blank",
+ "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": "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": "null",
+ "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": ""
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "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": "",
+ "skip": true
+ },
+ {
+ "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": "null",
+ "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": ""
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "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": "",
+ "skip": true
+ },
+ {
+ "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": ""
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "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": "",
+ "skip": true
+ },
+ {
+ "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": ""
+ },
+ "Origin computed by MozURL is wrong",
+ {
+ "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": "",
+ "skip": true
+ },
+ {
+ "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
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "input": "i",
+ "base": "sc:/pa/pa",
+ "href": "sc:/pa/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pa/i",
+ "search": "",
+ "hash": "",
+ "skip": true
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "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": "",
+ "skip": true
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "input": "i",
+ "base": "sc:///pa/pa",
+ "href": "sc:///pa/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/pa/i",
+ "search": "",
+ "hash": "",
+ "skip": true
+ },
+ {
+ "input": "../i",
+ "base": "sc:sd",
+ "failure": true
+ },
+ {
+ "input": "../i",
+ "base": "sc:sd/sd",
+ "failure": true
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "input": "../i",
+ "base": "sc:/pa/pa",
+ "href": "sc:/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": "",
+ "skip": true
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "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": "",
+ "skip": true
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "input": "../i",
+ "base": "sc:///pa/pa",
+ "href": "sc:///i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": "",
+ "skip": true
+ },
+ {
+ "input": "/i",
+ "base": "sc:sd",
+ "failure": true
+ },
+ {
+ "input": "/i",
+ "base": "sc:sd/sd",
+ "failure": true
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "input": "/i",
+ "base": "sc:/pa/pa",
+ "href": "sc:/i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": "",
+ "skip": true
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "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": "",
+ "skip": true
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "input": "/i",
+ "base": "sc:///pa/pa",
+ "href": "sc:///i",
+ "origin": "null",
+ "protocol": "sc:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/i",
+ "search": "",
+ "hash": "",
+ "skip": true
+ },
+ {
+ "input": "?i",
+ "base": "sc:sd",
+ "failure": true
+ },
+ {
+ "input": "?i",
+ "base": "sc:sd/sd",
+ "failure": true
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "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": "",
+ "skip": true
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "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": "",
+ "skip": true
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "skip": 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: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",
+ "Origin computed by MozURL is wrong",
+ {
+ "input": "about:/../",
+ "base": "about:blank",
+ "href": "about:/",
+ "origin": "about:/../",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": "",
+ "skip": true
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "input": "data:/../",
+ "base": "about:blank",
+ "href": "data:/",
+ "origin": "null",
+ "protocol": "data:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": "",
+ "skip": true
+ },
+ {
+ "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
+ },
+ "NS_NewURI for given input and base fails",
+ {
+ "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": "",
+ "skip": true
+ },
+ "# 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": ""
+ },
+ "Origin computed by MozURL is wrong",
+ {
+ "input": "https://0x.0x.0",
+ "base": "about:blank",
+ "href": "https://0.0.0.0/",
+ "origin": "https://0x.0x.0",
+ "protocol": "https:",
+ "username": "",
+ "password": "",
+ "host": "0.0.0.0",
+ "hostname": "0.0.0.0",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": "",
+ "skip": true
+ },
+ "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"
+ },
+ "New tests",
+ {
+ "input": "file:///foo/bar.html",
+ "base": "about:blank",
+ "href": "file:///foo/bar.html",
+ "origin": "file:///foo/bar.html",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///foo/bar.html?x=y",
+ "base": "about:blank",
+ "href": "file:///foo/bar.html?x=y",
+ "origin": "file:///foo/bar.html",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "file:///foo/bar.html#bla",
+ "base": "about:blank",
+ "href": "file:///foo/bar.html#bla",
+ "origin": "file:///foo/bar.html",
+ "protocol": "file:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "/",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "about:blank",
+ "base": "about:blank",
+ "href": "about:blank",
+ "origin": "null",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "about:home",
+ "base": "about:blank",
+ "href": "about:home",
+ "origin": "about:home",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "about:home#x",
+ "base": "about:blank",
+ "href": "about:home#x",
+ "origin": "about:home",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "about:home?x=y",
+ "base": "about:blank",
+ "href": "about:home?x=y",
+ "origin": "about:home",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "moz-safe-about:home",
+ "base": "about:blank",
+ "href": "moz-safe-about:home",
+ "origin": "moz-safe-about:home",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "moz-safe-about:home#x",
+ "base": "about:blank",
+ "href": "moz-safe-about:home#x",
+ "origin": "moz-safe-about:home",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "moz-safe-about:home?x=y",
+ "base": "about:blank",
+ "href": "moz-safe-about:home?x=y",
+ "origin": "moz-safe-about:home",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "indexeddb://fx-devtools",
+ "base": "about:blank",
+ "href": "indexeddb://fx-devtools/",
+ "origin": "indexeddb://fx-devtools",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "indexeddb://fx-devtools/",
+ "base": "about:blank",
+ "href": "indexeddb://fx-devtools/",
+ "origin": "indexeddb://fx-devtools",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "indexeddb://fx-devtools/foo",
+ "base": "about:blank",
+ "href": "indexeddb://fx-devtools/foo",
+ "origin": "indexeddb://fx-devtools",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "indexeddb://fx-devtools#x",
+ "base": "about:blank",
+ "href": "indexeddb://fx-devtools#x",
+ "origin": "indexeddb://fx-devtools",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "indexeddb://fx-devtools/#x",
+ "base": "about:blank",
+ "href": "indexeddb://fx-devtools/#x",
+ "origin": "indexeddb://fx-devtools",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "moz-extension://53711a8f-65ed-e742-9671-1f02e267c0bc/foo/bar.html",
+ "base": "about:blank",
+ "href": "moz-extension://53711a8f-65ed-e742-9671-1f02e267c0bc/foo/bar.html",
+ "origin": "moz-extension://53711a8f-65ed-e742-9671-1f02e267c0bc",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "moz-extension://53711a8f-65ed-e742-9671-1f02e267c0bc:123/foo/bar.html",
+ "base": "about:blank",
+ "href": "moz-extension://53711a8f-65ed-e742-9671-1f02e267c0bc:123/foo/bar.html",
+ "origin": "moz-extension://53711a8f-65ed-e742-9671-1f02e267c0bc:123",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "resource://foo/bar.html",
+ "base": "about:blank",
+ "href": "resource://foo/bar.html",
+ "origin": "resource://foo",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ },
+ {
+ "input": "resource://foo:123/bar.html",
+ "base": "about:blank",
+ "href": "resource://foo:123/bar.html",
+ "origin": "resource://foo:123",
+ "protocol": "about:",
+ "username": "",
+ "password": "",
+ "host": "",
+ "hostname": "",
+ "port": "",
+ "pathname": "blank",
+ "search": "",
+ "hash": ""
+ }
+]
diff --git a/netwerk/test/http3server/Cargo.toml b/netwerk/test/http3server/Cargo.toml
new file mode 100644
index 0000000000..da91ebdfdd
--- /dev/null
+++ b/netwerk/test/http3server/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "http3server"
+version = "0.1.1"
+authors = ["Dragana Damjanovic <dragana.damjano@gmail.com>"]
+edition = "2018"
+
+[dependencies]
+neqo-transport = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
+neqo-common = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
+neqo-http3 = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
+neqo-qpack = { tag = "v0.4.19", git = "https://github.com/mozilla/neqo" }
+mio = "0.6.17"
+mio-extras = "2.0.5"
+log = "0.4.0"
+
+[dependencies.neqo-crypto]
+tag = "v0.4.19"
+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.56", 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..89c4d66ce4
--- /dev/null
+++ b/netwerk/test/http3server/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["COMPILE_ENVIRONMENT"]
+ and not CONFIG["MOZ_TSAN"]
+ and not CONFIG["MOZ_ASAN"]
+ and not CONFIG["FUZZING"]
+ and not CONFIG["OS_TARGET"] == "WINNT"
+):
+ RUST_PROGRAMS += [
+ "http3server",
+ ]
diff --git a/netwerk/test/http3server/src/main.rs b/netwerk/test/http3server/src/main.rs
new file mode 100644
index 0000000000..ae953e6ec0
--- /dev/null
+++ b/netwerk/test/http3server/src/main.rs
@@ -0,0 +1,620 @@
+// 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 neqo_common::{event::Provider, qdebug, qinfo, qtrace, Datagram};
+use neqo_crypto::{init_db, AllowZeroRtt, AntiReplay};
+use neqo_http3::{Error, Http3Server, Http3ServerEvent};
+use neqo_qpack::QpackSettings;
+use neqo_transport::server::Server;
+use neqo_transport::{ConnectionEvent, ConnectionParameters, FixedConnectionIdManager, Output};
+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 core::fmt::Display;
+use mio::net::UdpSocket;
+use mio::{Events, Poll, PollOpt, Ready, Token};
+use mio_extras::timer::{Builder, Timeout, Timer};
+use std::collections::HashMap;
+use std::collections::HashSet;
+use std::mem;
+use std::net::SocketAddr;
+
+const MAX_TABLE_SIZE: u64 = 65536;
+const MAX_BLOCKED_STREAMS: u16 = 10;
+const PROTOCOLS: &[&str] = &["h3-27"];
+const TIMER_TOKEN: Token = Token(0xffff_ffff);
+
+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);
+}
+
+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<String, usize>,
+}
+
+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(),
+ }
+ }
+}
+
+impl HttpServer for Http3TestServer {
+ fn process(&mut self, dgram: Option<Datagram>) -> Output {
+ self.server.process(dgram, Instant::now())
+ }
+
+ fn process_events(&mut self) {
+ while let Some(event) = self.server.next_event() {
+ qtrace!("Event: {:?}", event);
+ match event {
+ Http3ServerEvent::Headers {
+ mut request,
+ headers,
+ fin,
+ } => {
+ qtrace!("Headers (request={} fin={}): {:?}", request, fin, headers);
+
+ let default_ret = b"Hello World".to_vec();
+ let default_headers = vec![
+ (String::from(":status"), String::from("200")),
+ (String::from("cache-control"), String::from("no-cache")),
+ (
+ String::from("content-length"),
+ default_ret.len().to_string(),
+ ),
+ ];
+
+ let path_hdr = headers.iter().find(|(k, _)| k == ":path");
+ match path_hdr {
+ Some((_, path)) if !path.is_empty() => {
+ qtrace!("Serve request {}", path);
+ if path == "/Response421" {
+ let response_body = b"0123456789".to_vec();
+ request
+ .set_response(
+ &[
+ (String::from(":status"), String::from("421")),
+ (
+ String::from("cache-control"),
+ String::from("no-cache"),
+ ),
+ (
+ String::from("content-length"),
+ response_body.len().to_string(),
+ ),
+ ],
+ &response_body,
+ )
+ .unwrap();
+ } else if path == "/RequestCancelled" {
+ request
+ .stream_reset(Error::HttpRequestCancelled.code())
+ .unwrap();
+ } else if path == "/VersionFallback" {
+ request
+ .stream_reset(Error::HttpVersionFallback.code())
+ .unwrap();
+ } else if path == "/EarlyResponse" {
+ request.stream_reset(Error::HttpNoError.code()).unwrap();
+ } else if path == "/RequestRejected" {
+ request
+ .stream_reset(Error::HttpRequestRejected.code())
+ .unwrap();
+ } else if path == "/.well-known/http-opportunistic" {
+ let host_hdr = headers.iter().find(|(k, _)| k == ":authority");
+ match host_hdr {
+ Some((_, host)) if !host.is_empty() => {
+ let mut content = b"[\"http://".to_vec();
+ content.extend(host.as_bytes());
+ content.extend(b"\"]".to_vec());
+ request
+ .set_response(
+ &[
+ (String::from(":status"), String::from("200")),
+ (
+ String::from("cache-control"),
+ String::from("no-cache"),
+ ),
+ (
+ String::from("content-type"),
+ String::from("application/json"),
+ ),
+ (
+ String::from("content-length"),
+ content.len().to_string(),
+ ),
+ ],
+ &content,
+ )
+ .unwrap();
+ }
+ _ => request
+ .set_response(&default_headers, &default_ret)
+ .unwrap(),
+ }
+ } else if path == "/no_body" {
+ request
+ .set_response(
+ &[
+ (String::from(":status"), String::from("200")),
+ (
+ String::from("cache-control"),
+ String::from("no-cache"),
+ ),
+ ],
+ &[],
+ )
+ .unwrap();
+ } else if path == "/no_content_length" {
+ request
+ .set_response(
+ &[
+ (String::from(":status"), String::from("200")),
+ (
+ String::from("cache-control"),
+ String::from("no-cache"),
+ ),
+ ],
+ &vec![b'a'; 4000],
+ )
+ .unwrap();
+ } else if path == "/content_length_smaller" {
+ request
+ .set_response(
+ &[
+ (String::from(":status"), String::from("200")),
+ (
+ String::from("cache-control"),
+ String::from("no-cache"),
+ ),
+ (String::from("content-length"), 4000.to_string()),
+ ],
+ &vec![b'a'; 8000],
+ )
+ .unwrap();
+ } else if path == "/post" {
+ // Read all data before responding.
+ self.posts.insert(format!("{}", request), 0);
+ } else {
+ match path.trim_matches(|p| p == '/').parse::<usize>() {
+ Ok(v) => request
+ .set_response(
+ &[
+ (String::from(":status"), String::from("200")),
+ (
+ String::from("cache-control"),
+ String::from("no-cache"),
+ ),
+ (String::from("content-length"), v.to_string()),
+ ],
+ &vec![b'a'; v],
+ )
+ .unwrap(),
+ Err(_) => request
+ .set_response(&default_headers, &default_ret)
+ .unwrap(),
+ }
+ }
+ }
+ _ => {
+ request
+ .set_response(&default_headers, &default_ret)
+ .unwrap();
+ }
+ }
+ }
+ Http3ServerEvent::Data {
+ mut request,
+ data,
+ fin,
+ } => {
+ if let Some(r) = self.posts.get_mut(&format!("{}", request)) {
+ *r += data.len();
+ }
+ if fin {
+ if let Some(r) = self.posts.remove(&format!("{}", request)) {
+ let default_ret = b"Hello World".to_vec();
+ request
+ .set_response(
+ &[
+ (String::from(":status"), String::from("200")),
+ (String::from("cache-control"), String::from("no-cache")),
+ (String::from("x-data-received-length"), r.to_string()),
+ (
+ String::from("content-length"),
+ default_ret.len().to_string(),
+ ),
+ ],
+ &default_ret,
+ )
+ .unwrap();
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+}
+
+impl HttpServer for Server {
+ fn process(&mut self, dgram: Option<Datagram>) -> Output {
+ self.process(dgram, 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 % 4 == 0 {
+ // We are only interesting in request streams
+ acr.borrow_mut()
+ .stream_send(stream_id, HTTP_RESPONSE_WITH_WRONG_FRAME)
+ .expect("Read should succeed");
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+ }
+}
+
+#[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 sent = socket
+ .send_to(&out_dgram, &out_dgram.destination())
+ .expect("Error sending datagram");
+ if sent != 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(new_timeout) => {
+ if let Some(svr_timeout) = svr_timeout {
+ timer.cancel_timeout(svr_timeout);
+ }
+
+ qinfo!("Setting timeout of {:?} for {}", new_timeout, server);
+ *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 (sz, remote_addr) = match socket.recv_from(&mut buf[..]) {
+ Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => return Ok(None),
+ Err(err) => {
+ eprintln!("UDP recv error: {:?}", err);
+ return Err(err);
+ }
+ Ok(res) => res,
+ };
+
+ 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, &buf[..sz])))
+ }
+}
+
+enum ServerType {
+ Http3,
+ Http3Fail,
+ Http3NoResponse,
+}
+
+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>,
+}
+
+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(),
+ })
+ }
+
+ pub fn init(&mut self) {
+ self.add_new_socket(0, ServerType::Http3);
+ self.add_new_socket(1, ServerType::Http3Fail);
+ self.add_new_socket(3, ServerType::Http3NoResponse);
+ println!(
+ "HTTP3 server listening on ports {}, {} and {}",
+ self.hosts[0].port(),
+ self.hosts[1].port(),
+ self.hosts[2].port()
+ );
+ self.poll
+ .register(&self.timer, TIMER_TOKEN, Ready::readable(), PollOpt::edge())
+ .unwrap();
+ }
+
+ fn add_new_socket(&mut self, count: usize, server_type: ServerType) -> u16 {
+ let addr = "127.0.0.1:0".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);
+ self.servers
+ .insert(local_addr, (self.create_server(server_type), None));
+ local_addr.port()
+ }
+
+ fn create_server(&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(FixedConnectionIdManager::new(10)));
+
+ match server_type {
+ ServerType::Http3 => Box::new(Http3TestServer::new(
+ Http3Server::new(
+ Instant::now(),
+ &[" HTTP2 Test Cert"],
+ PROTOCOLS,
+ anti_replay,
+ cid_mgr,
+ QpackSettings {
+ max_table_size_encoder: MAX_TABLE_SIZE,
+ max_table_size_decoder: MAX_TABLE_SIZE,
+ max_blocked_streams: MAX_BLOCKED_STREAMS,
+ },
+ )
+ .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()),
+ }
+ }
+
+ 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 {
+ if !event.readiness().is_readable() {
+ continue;
+ }
+ self.process_datagrams_and_events(event.token().0, true)?;
+ }
+ }
+ 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.js b/netwerk/test/httpserver/httpd.js
new file mode 100644
index 0000000000..63f6f2f531
--- /dev/null
+++ b/netwerk/test/httpserver/httpd.js
@@ -0,0 +1,5475 @@
+/* -*- 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.
+ */
+
+var EXPORTED_SYMBOLS = [
+ "HTTP_400",
+ "HTTP_401",
+ "HTTP_402",
+ "HTTP_403",
+ "HTTP_404",
+ "HTTP_405",
+ "HTTP_406",
+ "HTTP_407",
+ "HTTP_408",
+ "HTTP_409",
+ "HTTP_410",
+ "HTTP_411",
+ "HTTP_412",
+ "HTTP_413",
+ "HTTP_414",
+ "HTTP_415",
+ "HTTP_417",
+ "HTTP_500",
+ "HTTP_501",
+ "HTTP_502",
+ "HTTP_503",
+ "HTTP_504",
+ "HTTP_505",
+ "HttpError",
+ "HttpServer",
+ "NodeServer",
+];
+
+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
+
+var gGlobalObject = Cu.getGlobalForObject(this);
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * 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. */
+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.
+ */
+var HTTP_400 = new HttpError(400, "Bad Request");
+var HTTP_401 = new HttpError(401, "Unauthorized");
+var HTTP_402 = new HttpError(402, "Payment Required");
+var HTTP_403 = new HttpError(403, "Forbidden");
+var HTTP_404 = new HttpError(404, "Not Found");
+var HTTP_405 = new HttpError(405, "Method Not Allowed");
+var HTTP_406 = new HttpError(406, "Not Acceptable");
+var HTTP_407 = new HttpError(407, "Proxy Authentication Required");
+var HTTP_408 = new HttpError(408, "Request Timeout");
+var HTTP_409 = new HttpError(409, "Conflict");
+var HTTP_410 = new HttpError(410, "Gone");
+var HTTP_411 = new HttpError(411, "Length Required");
+var HTTP_412 = new HttpError(412, "Precondition Failed");
+var HTTP_413 = new HttpError(413, "Request Entity Too Large");
+var HTTP_414 = new HttpError(414, "Request-URI Too Long");
+var HTTP_415 = new HttpError(415, "Unsupported Media Type");
+var HTTP_417 = new HttpError(417, "Expectation Failed");
+
+var HTTP_500 = new HttpError(500, "Internal Server Error");
+var HTTP_501 = new HttpError(501, "Not Implemented");
+var HTTP_502 = new HttpError(502, "Bad Gateway");
+var HTTP_503 = new HttpError(503, "Service Unavailable");
+var HTTP_504 = new HttpError(504, "Gateway Timeout");
+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;
+
+/** 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. */
+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);
+}
+
+/** The XPCOM thread manager. */
+var gThreadManager = null;
+
+/**
+ * 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 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"
+);
+
+/**
+ * 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));
+}
+
+/**
+ * Prints out a human-readable representation of the object o and its fields,
+ * omitting those whose names begin with "_" if showMembers != true (to ignore
+ * "private" properties exposed via getters/setters).
+ */
+function printObj(o, showMembers) {
+ var s = "******************************\n";
+ s += "o = {\n";
+ for (var i in o) {
+ if (typeof i != "string" || showMembers || (i.length > 0 && i[0] != "_")) {
+ s += " " + i + ": " + o[i] + ",\n";
+ }
+ }
+ s += " };\n";
+ s += "******************************";
+ dumpn(s);
+}
+
+/**
+ * Instantiates a new HTTP server.
+ */
+function nsHttpServer() {
+ if (!gThreadManager) {
+ gThreadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+ }
+
+ /** 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, gThreadManager.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();
+ },
+ };
+ gThreadManager.currentThread.dispatch(
+ stopEvent,
+ Ci.nsIThread.DISPATCH_NORMAL
+ );
+ }
+ },
+
+ // NSIHTTPSERVER
+
+ //
+ // see nsIHttpServer.start
+ //
+ start(port) {
+ this._start(port, "localhost");
+ },
+
+ _start(port, host) {
+ 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 (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);
+ 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;
+ },
+};
+
+var HttpServer = nsHttpServer;
+
+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 env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ let h2Port = env.get("MOZNODE_EXEC_PORT");
+ if (!h2Port) {
+ throw new Error("Could not find MOZNODE_EXEC_PORT");
+ }
+
+ let req = new XMLHttpRequest();
+ req.open("POST", `http://127.0.0.1:${h2Port}${path}`);
+
+ // 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
+//
+
+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+" +
+ ")$",
+ "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) {
+ 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);
+ } 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) && host != "[::1]") {
+ 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 " +
+ gThreadManager.currentThread +
+ " (main is " +
+ gThreadManager.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, gThreadManager.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) && host != "[::1]") ||
+ !/^\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 = metadata._host = uri.asciiHost;
+ 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.
+ */
+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 > 0 && 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 maybeAddHeaders(file, metadata, response) {
+ var name = file.leafName;
+ if (name.charAt(name.length - 1) == HIDDEN_CHAR) {
+ name = name.substring(0, name.length - 1);
+ }
+
+ var headerFile = file.parent;
+ headerFile.append(name + HEADERS_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);
+ }
+
+ response.setStatusLine(
+ metadata.httpVersion,
+ parseInt(code, 10),
+ description
+ );
+
+ line.value = "";
+ more = lis.readLine(line);
+ }
+
+ // headers
+ while (more || line.value != "") {
+ var header = line.value;
+ var colon = header.indexOf(":");
+
+ response.setHeader(
+ 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();
+ }
+}
+
+/**
+ * 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 > 0) {
+ 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 == 0) {
+ 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 {
+ var s = Cu.Sandbox(gGlobalObject);
+ s.importFunction(dump, "dump");
+ s.importFunction(atob, "atob");
+ s.importFunction(btoa, "btoa");
+
+ // 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);
+ maybeAddHeaders(file, metadata, response);
+ 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() {
+ gThreadManager.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 != 0 &&
+ 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();
+
+ /**
+ * 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
+ //
+ setStatusLine(httpVersion, code, description) {
+ if (!this._headers || this._finished || this._powerSeized) {
+ 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
+ this._httpDescription = description;
+ this._httpCode = code;
+ this._httpVersion = httpVer;
+ },
+
+ //
+ // 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);
+ },
+
+ 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);
+ },
+
+ //
+ // 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.
+ gThreadManager.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._powerSeized);
+
+ // 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
+ var preambleData = [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
+ */
+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 > 0, "no pending data somehow?");
+ NS_ASSERT(
+ pendingData[pendingData.length - 1].length > 0,
+ "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 > 0, "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 > 0,
+ "stream-blocking exception with no data to write?"
+ );
+ NS_ASSERT(
+ pendingData[0].length > 0,
+ "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 > 0) {
+ 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
+ );
+ }
+ },
+ };
+
+ gThreadManager.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,
+ gThreadManager.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 > 0, "no pending data to write?");
+ NS_ASSERT(pendingData[0].length > 0, "buffered an empty write?");
+
+ this._sink.asyncWait(
+ this,
+ 0,
+ pendingData[0].length,
+ gThreadManager.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,
+ gThreadManager.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.
+ */
+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();
+ }
+ },
+};
+
+/**
+ * Creates a new HTTP server listening for loopback traffic on the given port,
+ * starts it, and runs the server until the server processes a shutdown request,
+ * spinning an event loop so that events posted by the server's socket are
+ * processed.
+ *
+ * This method is primarily intended for use in running this script from within
+ * xpcshell and running a functional HTTP server without having to deal with
+ * non-essential details.
+ *
+ * Note that running multiple servers using variants of this method probably
+ * doesn't work, simply due to how the internal event loop is spun and stopped.
+ *
+ * @note
+ * This method only works with Mozilla 1.9 (i.e., Firefox 3 or trunk code);
+ * you should use this server as a component in Mozilla 1.8.
+ * @param port
+ * the port on which the server will run, or -1 if there exists no preference
+ * for a specific port; note that attempting to use some values for this
+ * parameter (particularly those below 1024) may cause this method to throw or
+ * may result in the server being prematurely shut down
+ * @param basePath
+ * a local directory from which requests will be served (i.e., if this is
+ * "/home/jwalden/" then a request to /index.html will load
+ * /home/jwalden/index.html); if this is omitted, only the default URLs in
+ * this server implementation will be functional
+ */
+function server(port, basePath) {
+ if (basePath) {
+ var lp = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ lp.initWithPath(basePath);
+ }
+
+ // if you're running this, you probably want to see debugging info
+ DEBUG = true;
+
+ var srv = new nsHttpServer();
+ if (lp) {
+ srv.registerDirectory("/", lp);
+ }
+ srv.registerContentType("sjs", SJS_TYPE);
+ srv.identity.setPrimary("http", "localhost", port);
+ srv.start(port);
+
+ var thread = gThreadManager.currentThread;
+ while (!srv.isStopped()) {
+ thread.processNextEvent(true);
+ }
+
+ // get rid of any pending requests
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ DEBUG = false;
+}
diff --git a/netwerk/test/httpserver/moz.build b/netwerk/test/httpserver/moz.build
new file mode 100644
index 0000000000..4f5260ff79
--- /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.ini"]
+
+EXTRA_COMPONENTS += [
+ "httpd.js",
+]
+
+TESTING_JS_MODULES += [
+ "httpd.js",
+]
diff --git a/netwerk/test/httpserver/nsIHttpServer.idl b/netwerk/test/httpserver/nsIHttpServer.idl
new file mode 100644
index 0000000000..a2866485a5
--- /dev/null
+++ b/netwerk/test/httpserver/nsIHttpServer.idl
@@ -0,0 +1,624 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a 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);
+
+ /**
+ * 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/.eslintrc.js b/netwerk/test/httpserver/test/.eslintrc.js
new file mode 100644
index 0000000000..69e89d0054
--- /dev/null
+++ b/netwerk/test/httpserver/test/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/xpcshell-test"],
+};
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..b1554f2bc9
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/cgi.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response)
+{
+ if (request.queryString == "throw")
+ throw "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..1d9ea8b4e4
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/object-state.sjs
@@ -0,0 +1,87 @@
+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 "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: function(iid)
+ {
+ if (iid.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ end: function()
+ {
+ 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..36567ddc1a
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/qi.sjs
@@ -0,0 +1,46 @@
+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)
+ {
+ 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, "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..39fcc2b88e
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/range-checker.sjs
@@ -0,0 +1,3 @@
+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..da2862d1e9
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/state1.sjs
@@ -0,0 +1,42 @@
+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 "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..da2862d1e9
--- /dev/null
+++ b/netwerk/test/httpserver/test/data/sjs/state2.sjs
@@ -0,0 +1,42 @@
+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 "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..1aaf1639a3
--- /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..d4aeebb8f1
--- /dev/null
+++ b/netwerk/test/httpserver/test/head_utils.js
@@ -0,0 +1,567 @@
+/* -*- 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/. */
+/* global __LOCATION__ */
+/* import-globals-from ../httpd.js */
+
+var _HTTPD_JS_PATH = __LOCATION__.parent;
+_HTTPD_JS_PATH.append("httpd.js");
+load(_HTTPD_JS_PATH.path);
+
+// if these tests fail, we'll want the debug output
+var linDEBUG = true;
+
+var { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+/**
+ * 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 nsHttpServer();
+}
+
+/**
+ * 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);
+ } 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);
+
+ 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..a9fe74f26d
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_async_response_sending.js
@@ -0,0 +1,1670 @@
+/* -*- 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.
+ */
+
+gThreadManager = Cc["@mozilla.org/thread-manager;1"].createInstance();
+
+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 THREE_QUARTER_SEGMENT = [1, 2, 3];
+const EXTRA_HALF_SEGMENT = [5, 6];
+const MIDDLE_HALF_SEGMENT = [2, 3];
+const LAST_QUARTER_SEGMENT = [4];
+const FOURTH_HALF_SEGMENT = [7, 8];
+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.
+ */
+var BinaryInputStream = function BIS(stream) {
+ return stream;
+};
+var BinaryOutputStream = function BOS(stream) {
+ return stream;
+};
+Response.SEGMENT_SIZE = 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 > 0) {
+ 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);
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIAsyncInputStream",
+ "nsIInputStream",
+ ]),
+ });
+
+ /** 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);
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIAsyncOutputStream",
+ "nsIOutputStream",
+ ]),
+ });
+}
+
+/**
+ * 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,
+ gThreadManager.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);
+ }
+ },
+ };
+ gThreadManager.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..c2a7243d2d
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_basic_functionality.js
@@ -0,0 +1,181 @@
+/* -*- 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.
+ */
+
+XPCOMUtils.defineLazyGetter(this, "port", function() {
+ return srv.identity.primaryPort;
+});
+
+XPCOMUtils.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..2adbbeb272
--- /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 *
+ ***************/
+
+XPCOMUtils.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..7505140edd
--- /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;
+XPCOMUtils.defineLazyGetter(this, "PREFIX", function() {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+XPCOMUtils.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..3ff617289f
--- /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;
+
+XPCOMUtils.defineLazyGetter(this, "PREFIX", function() {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+XPCOMUtils.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..4f9bc8e2e1
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_default_index_handler.js
@@ -0,0 +1,247 @@
+/* -*- 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;
+
+XPCOMUtils.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");
+
+ 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 *
+ *********/
+
+XPCOMUtils.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..97bed9aec3
--- /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;
+
+XPCOMUtils.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..ad366d7dae
--- /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.
+
+XPCOMUtils.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..7377be857b
--- /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 *
+ ***************/
+
+XPCOMUtils.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..f3c2544d36
--- /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 != 0) {
+ 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_linedata.js b/netwerk/test/httpserver/test/test_linedata.js
new file mode 100644
index 0000000000..cdffb99956
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_linedata.js
@@ -0,0 +1,19 @@
+/* -*- 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
+
+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..53718055e1
--- /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.import(
+ "resource://testing-common/httpd.js"
+ );
+
+ 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..5014b1fa2a
--- /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;
+
+XPCOMUtils.defineLazyGetter(this, "PREFIX", function() {
+ return "http://localhost:" + srv.identity.primaryPort;
+});
+
+XPCOMUtils.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..33bc100698
--- /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.
+ */
+
+XPCOMUtils.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 *
+ ***************/
+
+XPCOMUtils.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
+ gThreadManager.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..38ece5e4d1
--- /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.
+ */
+
+XPCOMUtils.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..a5e530af09
--- /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
+
+XPCOMUtils.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)
+ );
+}
+
+XPCOMUtils.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..f93bb2348b
--- /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
+
+XPCOMUtils.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);
+}
+
+XPCOMUtils.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..bbe1f8cf46
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_registerprefix.js
@@ -0,0 +1,131 @@
+/* -*- 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
+
+XPCOMUtils.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);
+ };
+}
+
+XPCOMUtils.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;
+var serverBasePath;
+var testsDirectory;
+
+function run_test() {
+ testsDirectory = 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..65bb4ceda9
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_request_line_split_in_two_packets.js
@@ -0,0 +1,138 @@
+/* -*- 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 path = "/very-long-request-line?";
+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..fe0c4bd7ba
--- /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
+
+XPCOMUtils.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..0d85d83df1
--- /dev/null
+++ b/netwerk/test/httpserver/test/test_seizepower.js
@@ -0,0 +1,188 @@
+/* -*- 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.
+ */
+
+XPCOMUtils.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);
+}
+
+function callASAPLater(fun) {
+ gThreadManager.dispatchToMainThread({
+ run() {
+ fun();
+ },
+ });
+}
+
+/** ***************
+ * 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 *
+ ***************/
+
+XPCOMUtils.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..8fae4480e1
--- /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));
+}
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + srv.identity.primaryPort + "/";
+});
+
+XPCOMUtils.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..b296344338
--- /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
+
+XPCOMUtils.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 *
+ *********/
+
+XPCOMUtils.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..3b20d331a6
--- /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..c6dceceda4
--- /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.
+ */
+
+XPCOMUtils.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..b2e1a2b476
--- /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
+
+XPCOMUtils.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 *
+ ***************/
+
+XPCOMUtils.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..a4c6bb1616
--- /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).
+ */
+
+XPCOMUtils.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;
+
+XPCOMUtils.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..2d03f1ec2c
--- /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.
+ */
+
+XPCOMUtils.defineLazyGetter(this, "PORT", function() {
+ return srv.identity.primaryPort;
+});
+
+XPCOMUtils.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/xpcshell.ini b/netwerk/test/httpserver/test/xpcshell.ini
new file mode 100644
index 0000000000..28c33b025c
--- /dev/null
+++ b/netwerk/test/httpserver/test/xpcshell.ini
@@ -0,0 +1,35 @@
+[DEFAULT]
+head = head_utils.js
+support-files = data/** ../httpd.js
+
+[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_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]
+[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]
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..d9159003d1
--- /dev/null
+++ b/netwerk/test/mochitests/file_1331680.js
@@ -0,0 +1,26 @@
+/* eslint-env mozilla/frame-script */
+
+"use strict";
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+let cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+
+var observer = {
+ observe(subject, topic, data) {
+ if (topic == "cookie-changed") {
+ let cookie = subject.QueryInterface(Ci.nsICookie);
+ sendAsyncMessage("cookieName", cookie.name + "=" + cookie.value);
+ sendAsyncMessage("cookieOperation", data);
+ }
+ },
+};
+
+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..9fc8502bd4
--- /dev/null
+++ b/netwerk/test/mochitests/file_1502055.sjs
@@ -0,0 +1,18 @@
+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..ba7160c633
--- /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..f31dabb76d
--- /dev/null
+++ b/netwerk/test/mochitests/file_chromecommon.js
@@ -0,0 +1,14 @@
+/* eslint-env mozilla/frame-script */
+
+"use strict";
+
+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..2dfdfbe8f7
--- /dev/null
+++ b/netwerk/test/mochitests/file_documentcookie_maxage_chromescript.js
@@ -0,0 +1,44 @@
+/* eslint-env mozilla/frame-script */
+
+"use strict";
+
+function getCookieService() {
+ 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..e8702a8342
--- /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="http://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..ba0e93f82c
--- /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="http://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..908d370767
--- /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="http://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..40709a1d0f
--- /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="http://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..7d1702ea35
--- /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="http://example.org/tests/netwerk/test/mochitests/test1.css" />
+ <link rel="stylesheet" type="text/css" media="all" href="http://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="http://example.org/tests/netwerk/test/mochitests/image1.png" onload="runTest()" />
+<img src="http://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..fd46d6cd9d
--- /dev/null
+++ b/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs
@@ -0,0 +1,106 @@
+/*
+ * 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 newLoaction = "";
+ 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..f97b321552
--- /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="http://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..c010f84aeb
--- /dev/null
+++ b/netwerk/test/mochitests/file_testcommon.js
@@ -0,0 +1,84 @@
+/* eslint-env mozilla/frame-script */
+
+"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.
+ ["network.cookie.rejectForeignWithExceptions.enabled", 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();
+ 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..e3a24ae735
--- /dev/null
+++ b/netwerk/test/mochitests/file_testloadflags.js
@@ -0,0 +1,132 @@
+/* eslint-env mozilla/frame-script */
+
+"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(uri, domain, cookies, loads, headers) {
+ info(
+ "setupTest uri: " +
+ uri +
+ " 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(uri, "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..8bab7649b3
--- /dev/null
+++ b/netwerk/test/mochitests/file_testloadflags_chromescript.js
@@ -0,0 +1,143 @@
+/* eslint-env mozilla/frame-script */
+
+"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..386c15d794
--- /dev/null
+++ b/netwerk/test/mochitests/method.sjs
@@ -0,0 +1,12 @@
+
+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.ini b/netwerk/test/mochitests/mochitest.ini
new file mode 100644
index 0000000000..77a8e8501f
--- /dev/null
+++ b/netwerk/test/mochitests/mochitest.ini
@@ -0,0 +1,98 @@
+[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^
+
+[test_arraybufferinputstream.html]
+[test_documentcookies_maxage.html]
+[test_idn_redirect.html]
+[test_loadinfo_redirectchain.html]
+fail-if = xorigin
+[test_partially_cached_content.html]
+[test_rel_preconnect.html]
+[test_redirect_ref.html]
+[test_uri_scheme.html]
+skip-if = (verify && debug && os == 'mac')
+[test_viewsource_unlinkable.html]
+[test_xhr_method_case.html]
+[test_1331680.html]
+[test_1331680_iframe.html]
+[test_1331680_xhr.html]
+skip-if = verify
+[test_1396395.html]
+[test_1421324.html]
+[test_1425031.html]
+[test_1503201.html]
+[test_origin_header.html]
+[test_1502055.html]
+support-files = sw_1502055.js file_1502055.sjs iframe_1502055.html
+[test_accept_header.html]
+support-files = test_accept_header.sjs
+[test_different_domain_in_hierarchy.html]
+[test_differentdomain.html]
+[test_fetch_lnk.html]
+[test_image.html]
+[test_loadflags.html]
+[test_same_base_domain.html]
+[test_same_base_domain_2.html]
+[test_same_base_domain_3.html]
+[test_same_base_domain_4.html]
+[test_same_base_domain_5.html]
+[test_same_base_domain_6.html]
+[test_samedomain.html]
diff --git a/netwerk/test/mochitests/origin_header.sjs b/netwerk/test/mochitests/origin_header.sjs
new file mode 100644
index 0000000000..a3e1551b66
--- /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..5fd3443f10
--- /dev/null
+++ b/netwerk/test/mochitests/partial_content.sjs
@@ -0,0 +1,151 @@
+/* -*- 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 > 0) {
+ ERR(response, 400, "Bad Request",
+ "Should not receive \"Range: " + range + "\" for first, full request.");
+ return;
+ }
+ if (ifRange && ifRange.length > 0) {
+ 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.
+ 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..9b5f5a69ce
--- /dev/null
+++ b/netwerk/test/mochitests/rel_preconnect.sjs
@@ -0,0 +1,15 @@
+// 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..1fc70c5ac8
--- /dev/null
+++ b/netwerk/test/mochitests/reset_cookie_xhr.sjs
@@ -0,0 +1,13 @@
+
+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..8a32f95df7
--- /dev/null
+++ b/netwerk/test/mochitests/set_cookie_xhr.sjs
@@ -0,0 +1,13 @@
+
+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..96c4210a2b
--- /dev/null
+++ b/netwerk/test/mochitests/signed_web_packaged_app.sjs
@@ -0,0 +1,74 @@
+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..1857de3035
--- /dev/null
+++ b/netwerk/test/mochitests/subResources.sjs
@@ -0,0 +1,47 @@
+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..b166f36745
--- /dev/null
+++ b/netwerk/test/mochitests/test_1331680.html
@@ -0,0 +1,78 @@
+<!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'));
+
+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.");
+ 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..dc3397f60f
--- /dev/null
+++ b/netwerk/test/mochitests/test_1331680_iframe.html
@@ -0,0 +1,65 @@
+<!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");
+ SimpleTest.finish();
+}
+
+addEventListener("message", function(event) {
+ is(event.data, document.cookie, "Confirm the iframe 2 can communicate with iframe");
+});
+
+Promise.resolve()
+ .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..83f23831a7
--- /dev/null
+++ b/netwerk/test/mochitests/test_1331680_xhr.html
@@ -0,0 +1,87 @@
+<!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() {
+ 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.
+ */
+Promise.resolve()
+ .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..01bedb3d9e
--- /dev/null
+++ b/netwerk/test/mochitests/test_1396395.html
@@ -0,0 +1,50 @@
+<!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();
+
+/* eslint-env mozilla/frame-script */
+var script = SpecialPowers.loadChromeScript(() => {
+ const {Services} = ChromeUtils.import('resource://gre/modules/Services.jsm');
+
+ 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..0fd761ab0d
--- /dev/null
+++ b/netwerk/test/mochitests/test_1421324.html
@@ -0,0 +1,85 @@
+<!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() {
+ 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.
+ */
+Promise.resolve()
+ .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..3a1c16a638
--- /dev/null
+++ b/netwerk/test/mochitests/test_1425031.html
@@ -0,0 +1,67 @@
+<!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'));
+
+gScript.addMessageListener("cookieOperation", confirmCookieOperation);
+gScript.addMessageListener("createObserver:return", testSetCookie);
+gScript.sendAsyncMessage('createObserver');
+var testsNum = 0;
+var cookieString = "cookie0=test";
+
+// Confirm the notify which represents the cookie is updating.
+function confirmCookieOperation(op) {
+ testsNum++;
+ switch(testsNum) {
+ case 1:
+ is(op, "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, "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, "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');
+ 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.");
+}
+
+</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..26b251f891
--- /dev/null
+++ b/netwerk/test/mochitests/test_1502055.html
@@ -0,0 +1,36 @@
+<!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();
+SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true]
+]}).then(() => {
+ 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..62e6eccc54
--- /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/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/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 == 0) {
+ 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..7d00017903
--- /dev/null
+++ b/netwerk/test/mochitests/test_accept_header.sjs
@@ -0,0 +1,49 @@
+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", "");
+ return;
+ }
+}
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_different_domain_in_hierarchy.html b/netwerk/test/mochitests/test_different_domain_in_hierarchy.html
new file mode 100644
index 0000000000..ad7e9f0cda
--- /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('http://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..b5444c32c3
--- /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('http://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..ea330465cb
--- /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('http://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..568b01f38d
--- /dev/null
+++ b/netwerk/test/mochitests/test_loadinfo_redirectchain.html
@@ -0,0 +1,225 @@
+<!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.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+// *************** 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?redir-2",
+ "http://mochi.test:8888/tests/netwerk/test/mochitests/file_loadinfo_redirectchain.sjs?redir-1"
+ ];
+
+ // 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;
+ var redirectChain = loadinfo.redirectChain;
+ var redirectChainIncludingInternalRedirects = loadinfo.redirectChainIncludingInternalRedirects;
+
+ is(redirectChain.length,
+ EXPECTED_REDIRECT_CHAIN.length,
+ "two redirects, chain should have length 2");
+ is(redirectChainIncludingInternalRedirects.length,
+ EXPECTED_REDIRECT_CHAIN.length,
+ "two redirect, chainInternal should have length 2");
+
+ for (var i = 0; i < redirectChain.length; i++) {
+ is(redirectChain[i].principal.spec,
+ EXPECTED_REDIRECT_CHAIN[i],
+ "redirectChain at index [" + i + "] should match");
+ is(redirectChainIncludingInternalRedirects[i].principal.spec,
+ EXPECTED_REDIRECT_CHAIN[i],
+ "redirectChainIncludingInternalRedirects at index [" + i + "] should match");
+ is(redirectChain[i].referrerURI.spec,
+ EXPECTED_REFERRER,
+ "referrer should match");
+ is(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();
+}
+
+// ************** HELPERS ***************
+
+function compareChains(aLoadInfo, aExpectedRedirectChain, aExpectedRedirectChainIncludingInternalRedirects) {
+
+ var redirectChain = aLoadInfo.redirectChain;
+ var redirectChainIncludingInternalRedirects = aLoadInfo.redirectChainIncludingInternalRedirects;
+
+ is(redirectChain.length,
+ aExpectedRedirectChain.length,
+ "confirming length of redirectChain");
+
+ is(redirectChainIncludingInternalRedirects.length,
+ aExpectedRedirectChainIncludingInternalRedirects.length,
+ "confirming length of redirectChainIncludingInternalRedirects");
+
+ for (var i = 0; i < redirectChain.length; i++) {
+ is(redirectChain[i],
+ aExpectedRedirectChain[i],
+ "redirectChain at index [" + i + "] should match");
+ }
+
+ for (var i = 0; i < redirectChainIncludingInternalRedirects.length; i++) {
+ is(redirectChainIncludingInternalRedirects[i],
+ aExpectedRedirectChainIncludingInternalRedirects[i],
+ "redirectChainIncludingInternalRedirects at index [" + i + "] should match");
+ }
+}
+
+// *************** 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);
+ SimpleTest.finish();
+}
+
+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";
+}
+
+// *************** 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..5c878f1ed1
--- /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: "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",
+ },
+ },
+ {
+ 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..96775471c9
--- /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('http://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..2e1608ae9b
--- /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('http://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..ef9349a7c0
--- /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('http://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..ee754d6f8e
--- /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('http://sub1.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_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_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..2f7f65abd4
--- /dev/null
+++ b/netwerk/test/mochitests/web_packaged_app.sjs
@@ -0,0 +1,31 @@
+function handleRequest(request, response)
+{
+ response.setHeader("Content-Type", "application/package", false);
+ response.write(octetStreamData.getData());
+ return;
+}
+
+// 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: function() {
+ 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..0fd34192b3
--- /dev/null
+++ b/netwerk/test/moz.build
@@ -0,0 +1,20 @@
+# -*- 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", "unit", "http3server"]
+
+BROWSER_CHROME_MANIFESTS += ["browser/browser.ini"]
+MOCHITEST_MANIFESTS += ["mochitests/mochitest.ini"]
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "unit/xpcshell.ini",
+ "unit_ipc/xpcshell.ini",
+]
+
+PERFTESTS_MANIFESTS += ["perf/perftest.ini", "unit/perftest.ini"]
+
+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..3662662b42
--- /dev/null
+++ b/netwerk/test/perf/hooks_throttling.py
@@ -0,0 +1,203 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 time
+import http.client
+import os
+import json
+from urllib.parse import urlparse
+import sys
+
+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.enabled: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.ini b/netwerk/test/perf/perftest.ini
new file mode 100644
index 0000000000..aa96f97bf6
--- /dev/null
+++ b/netwerk/test/perf/perftest.ini
@@ -0,0 +1,8 @@
+[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..a67e412907
--- /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.enabled: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..b3ea4b881e
--- /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.enabled: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..ca62d8947f
--- /dev/null
+++ b/netwerk/test/perf/perftest_http3_facebook_scroll.js
@@ -0,0 +1,166 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.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.enabled: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 == 0) {
+ 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..5b8cb6842c
--- /dev/null
+++ b/netwerk/test/perf/perftest_http3_google_image.js
@@ -0,0 +1,191 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.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.enabled: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 == 0) {
+ 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..4831e1a037
--- /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.enabled: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..b20042bf45
--- /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.enabled: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..2063140fcd
--- /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.enabled: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..b3b2928108
--- /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.enabled: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/CA.key.pem b/netwerk/test/unit/CA.key.pem
new file mode 100644
index 0000000000..2153c9dd56
--- /dev/null
+++ b/netwerk/test/unit/CA.key.pem
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIoXjtIGzP1OkCAggA
+MBQGCCqGSIb3DQMHBAg7NkhJaDJEVwSCBMhYUI4JRAIdvJtCmP7lKl30QR+HG4JC
+9gUJflQrCsa1WhxKCHa7VXzqlItQTu0FTMwn9GRFUOtqpY8ZE6XJFuvzKPox3RMa
+1TMod6v3NS3KIs6/l2Pb2HcGtcMzOJVei1nwtjsT5fvq36x3eoKzgqd9l0fLcvlD
+wf+byzgY0Nsau+ER8DWy65jXF8bPsQQcKTc+U+p4moO3UuXG4+Pnd8ooSaM4X2on
+1jIYDU1aFoSDUvze8+MvQCD32QLuO63iK7ox4sFharG7KucYqeWCihDx5rlGaVGB
+5647v4oHRysEdLVTkU12mIC/Hx/yPXcLhHYmawmnYwEoh1S+wd7rOo9Wn/l16NTK
+8BcDuvfM8km4T5oO/UFaNDIBLBQsNM5sNHDYFDlhmR4x6d5nXeERJ6DQbvhQtgnV
+bTtT9h24rsC8Irflz/abcvTvqqp8I1+gYEzmhgDRUgp9zAPZUoH3E4DKk5rVgApR
+ARX9Y88S7k/OBnU8r+cT+0CjsusbbIv5W2nAFqEX9jMend0cHzYvq3m6v1Jqxjfn
+kQRP1n+SagmAPBIAzy1wSHGV43+COk6FB+blfAGbO55lLglEM9PLH7Nnl0XrPtaE
+dXx5RTtdBnb349Ow8H3WnleTfKspUbIVNyM48aPaXJu6Y784pUXDOC13ISFVbOew
+dPr/s/GoHgBUIm9gxkhNQYUlcSNrJCyJ6bqvrYbOmVQRusO/SaM6ozY8wFL8LDnS
+GeXmg3dAslHhuaHlFN7atF7rBtTWPsH+oQdHNKcLDK7nYq45v8VfjPUrWPfYc2nB
+l+zT4LozY3VPfPW7BG2zVBTyxXkiynz0w7tJaN/HokZGAUDqWXqjSceJqc9Q4XAG
+slIxbxkfxEJUEmJ2wHEnure6T0dJOIfbJzkCqWAeJjkrbI5mdKLuXFj94VgSlfK2
+iq3J20/5HVdHqoVGRZ5rxBUIaVEgSXB3/+9C/M0U0uxx23zxRmVkMGdhhCqXQRh/
+jFUkBzq4x3yibxJW3fRe7jXEJdo1DAAfgBnDvCUWH7lRX8hDkx6OIX4ZS4D7Va0j
+ogSC04IdZWxOP3YJ4gGwx8vvgHWnBLyFfmdFnfHXUr9A8HDDJQTupYg25PDUGHla
+SxukgOYdQ2O6jUCW0TYeUzX7y/P/Za93kWJp7XqA4v76fQ+C9d3CZT/TY0CqNgxB
+C5+PWRGvxtcy+Bne8QYCJhvNPEhfgFa9fU3Rd4w43lvTb9rsy7eBR3jJKdPLKExJ
+zEPIgVUGaMf0lawL0fIgoUI5Q3kRCmrswkTK9kr4+rSA//p0NralnZtHCWRvgs9W
+Lg4hkf1vXxsa9f16Nk6PxqU/OnJmhTnTnv9MzFoX3Sce2neD86H5c7tdguySbrsj
+5fww64rH1UwHhn/i49i3hkseax48gOAZPA8rl+L70FS8dXLpHOm4ihmv6ubVjr82
+yOxi4WmaoXfmOPBgOgGhz1nTFAaetwfhZIsgEtysuWAOsApOUyjlD/wM25988bAa
+m5FwslUGLWQfBIV1N9PC+Q0ui1ywRuLoKHNiKDSE+T5iOuv2Yf7du4nncoM/ANmU
+FnWJL3Aj1VE/O+OeUyuNEPWLHvVX5TChe5mFXZO4bXfTR4tgdJJ15HWf4LKMQdcl
+BEA=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/netwerk/test/unit/client_cert_chooser.js b/netwerk/test/unit/client_cert_chooser.js
new file mode 100644
index 0000000000..5078c68fc6
--- /dev/null
+++ b/netwerk/test/unit/client_cert_chooser.js
@@ -0,0 +1,27 @@
+// -*- 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 { ComponentUtils } = ChromeUtils.import(
+ "resource://gre/modules/ComponentUtils.jsm"
+);
+
+var Prompter = {
+ QueryInterface: ChromeUtils.generateQI(["nsIPrompt"]),
+ alert() {}, // Do nothing when asked to show an alert
+};
+
+function WindowWatcherService() {}
+WindowWatcherService.prototype = {
+ classID: Components.ID("{01ae923c-81bb-45db-b860-d423b0fc4fe1}"),
+ QueryInterface: ChromeUtils.generateQI(["nsIWindowWatcher"]),
+
+ getNewPrompter() {
+ return Prompter;
+ },
+};
+
+this.NSGetFactory = ComponentUtils.generateNSGetFactory([WindowWatcherService]);
diff --git a/netwerk/test/unit/client_cert_chooser.manifest b/netwerk/test/unit/client_cert_chooser.manifest
new file mode 100644
index 0000000000..e604c92d0e
--- /dev/null
+++ b/netwerk/test/unit/client_cert_chooser.manifest
@@ -0,0 +1,2 @@
+component {01ae923c-81bb-45db-b860-d423b0fc4fe1} cert_dialog.js
+contract @mozilla.org/embedcomp/window-watcher;1 {01ae923c-81bb-45db-b860-d423b0fc4fe1}
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..388cb184a2
--- /dev/null
+++ b/netwerk/test/unit/head_cache.js
@@ -0,0 +1,167 @@
+"use strict";
+
+var { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var _CSvc;
+function get_cache_service() {
+ if (_CSvc) {
+ return _CSvc;
+ }
+
+ return (_CSvc = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(
+ Ci.nsICacheStorageService
+ ));
+}
+
+function evict_cache_entries(where) {
+ var clearDisk = !where || where == "disk" || where == "all";
+ var clearMem = !where || where == "memory" || where == "all";
+ var clearAppCache = where == "appcache";
+
+ var svc = get_cache_service();
+ var storage;
+
+ if (clearMem) {
+ storage = svc.memoryCacheStorage(Services.loadContextInfo.default);
+ storage.asyncEvictStorage(null);
+ }
+
+ if (clearDisk) {
+ storage = svc.diskCacheStorage(Services.loadContextInfo.default, false);
+ storage.asyncEvictStorage(null);
+ }
+
+ if (clearAppCache) {
+ storage = svc.appCacheStorage(Services.loadContextInfo.default, null);
+ storage.asyncEvictStorage(null);
+ }
+}
+
+function createURI(urispec) {
+ var ioServ = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+ return ioServ.newURI(urispec);
+}
+
+function getCacheStorage(where, lci, appcache) {
+ if (!lci) {
+ lci = Services.loadContextInfo.default;
+ }
+ var svc = get_cache_service();
+ switch (where) {
+ case "disk":
+ return svc.diskCacheStorage(lci, false);
+ case "memory":
+ return svc.memoryCacheStorage(lci);
+ case "appcache":
+ return svc.appCacheStorage(lci, appcache);
+ case "pin":
+ return svc.pinningCacheStorage(lci);
+ }
+ return null;
+}
+
+function asyncOpenCacheEntry(key, where, flags, lci, callback, appcache) {
+ key = createURI(key);
+
+ function CacheListener() {}
+ CacheListener.prototype = {
+ _appCache: appcache,
+
+ QueryInterface: ChromeUtils.generateQI(["nsICacheEntryOpenCallback"]),
+
+ onCacheEntryCheck(entry, appCache) {
+ if (typeof callback === "object") {
+ return callback.onCacheEntryCheck(entry, appCache);
+ }
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable(entry, isnew, appCache, status) {
+ if (typeof callback === "object") {
+ // Root us at the callback
+ callback.__cache_listener_root = this;
+ callback.onCacheEntryAvailable(entry, isnew, appCache, status);
+ } else {
+ callback(status, entry, appCache);
+ }
+ },
+
+ run() {
+ var storage = getCacheStorage(where, lci, this._appCache);
+ 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,
+ appCache
+) {
+ 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();
+ },
+ appCache
+ );
+}
diff --git a/netwerk/test/unit/head_cache2.js b/netwerk/test/unit/head_cache2.js
new file mode 100644
index 0000000000..5a531498ca
--- /dev/null
+++ b/netwerk/test/unit/head_cache2.js
@@ -0,0 +1,428 @@
+/* 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);
+ var 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
+ var data = read_stream(inputStream, inputStream.available());
+ goon(data);
+ }
+}
+
+OpenCallback.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsICacheEntryOpenCallback"]),
+ onCacheEntryCheck(entry, appCache) {
+ 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, appCache, 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;
+ }
+
+ var 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 {
+ var 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);
+ var 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);
+ }
+
+ var 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,
+ 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.
+ get_cache_service().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..8761032238
--- /dev/null
+++ b/netwerk/test/unit/head_channels.js
@@ -0,0 +1,456 @@
+/**
+ * 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 == 0) {
+ 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 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 (
+ 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);
+}
+
+// Copied from head_psm.js.
+function add_tls_server_setup(serverBinName, certsPath, addDefaultRoot = true) {
+ add_test(function() {
+ _setupTLSServerTest(serverBinName, certsPath, addDefaultRoot);
+ });
+}
+
+// Do not call this directly; use add_tls_server_setup
+function _setupTLSServerTest(serverBinName, certsPath, addDefaultRoot) {
+ asyncStartTLSTestServer(serverBinName, certsPath, addDefaultRoot).then(
+ run_next_test
+ );
+}
+
+async function asyncStartTLSTestServer(
+ serverBinName,
+ certsPath,
+ addDefaultRoot
+) {
+ const { HttpServer } = ChromeUtils.import(
+ "resource://testing-common/httpd.js"
+ );
+ 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 envSvc = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ let greBinDir = Services.dirsvc.get("GreBinD", Ci.nsIFile);
+ envSvc.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.
+ envSvc.set("LD_LIBRARY_PATH", greBinDir.path + ":/data/local/xpcb");
+ envSvc.set("MOZ_TLS_SERVER_DEBUG_LEVEL", "3");
+ envSvc.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;
+}
diff --git a/netwerk/test/unit/head_cookies.js b/netwerk/test/unit/head_cookies.js
new file mode 100644
index 0000000000..feb6939b4f
--- /dev/null
+++ b/netwerk/test/unit/head_cookies.js
@@ -0,0 +1,973 @@
+/* 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.import("resource://gre/modules/NetUtil.jsm");
+const { CookieXPCShellUtils } = ChromeUtils.import(
+ "resource://testing-common/CookieXPCShellUtils.jsm"
+);
+
+// Don't pick up default permissions from profile.
+Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
+
+CookieXPCShellUtils.init(this);
+
+XPCOMUtils.defineLazyServiceGetter(
+ Services,
+ "cookiesvc",
+ "@mozilla.org/cookieService;1",
+ "nsICookieService"
+);
+XPCOMUtils.defineLazyServiceGetter(
+ Services,
+ "cookiemgr",
+ "@mozilla.org/cookiemanager;1",
+ "nsICookieManager"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ Services,
+ "etld",
+ "@mozilla.org/network/effective-tld-service;1",
+ "nsIEffectiveTLDService"
+);
+
+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", "shutdown-persist");
+}
+
+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", "shutdown-persist");
+
+ 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.cookiesvc.setCookieStringFromHttp(uri, "foo=bar", channel);
+ Assert.equal(Services.cookiemgr.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 obj => {
+ // eslint-disable-next-line no-undef
+ await new content.Promise(resolve => {
+ // eslint-disable-next-line no-undef
+ const ifr = content.document.createElement("iframe");
+ // eslint-disable-next-line no-undef
+ content.document.body.appendChild(ifr);
+ ifr.src = obj.url;
+ ifr.onload = () => {
+ ifr.contentDocument.cookie = obj.cookie;
+ resolve();
+ };
+ });
+ }
+ );
+ await contentPage.close();
+
+ Assert.equal(Services.cookiemgr.countCookiesFromHost(uri.host), expected[0]);
+
+ // via http request
+ Services.cookiesvc.setCookieStringFromHttp(uri, "hot=dog" + suffix, channel);
+ Assert.equal(Services.cookiemgr.countCookiesFromHost(uri.host), expected[1]);
+}
+
+function do_count_cookies() {
+ return Services.cookiemgr.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
+) {
+ 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;
+
+ 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;
+ }
+
+ 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;
+
+ 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:
+ 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:
+ 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..9fffa72ea4
--- /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() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+
+ let h3Port = 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.enabled", 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-27=:" + h3Port
+ );
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+
+ await setup_altsvc("https://foo.example.com/", h3Route);
+}
+
+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: "",
+
+ 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, "h3");
+ this.finish(true);
+ } else {
+ dump("try again to get alt svc mapping\n");
+ this.finish(false);
+ }
+ },
+};
+
+async function setup_altsvc(uri, expectedRoute) {
+ let result = false;
+ do {
+ let chan = makeChan(uri);
+ let listener = new CheckHttp3Listener();
+ listener.expectedRoute = expectedRoute;
+ 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.enabled");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.dns.disableIPv6");
+ Services.prefs.clearUserPref(
+ "network.http.http3.alt-svc-mapping-for-testing"
+ );
+ dump("cleanup done\n");
+}
diff --git a/netwerk/test/unit/head_trr.js b/netwerk/test/unit/head_trr.js
new file mode 100644
index 0000000000..bc488ec6cc
--- /dev/null
+++ b/netwerk/test/unit/head_trr.js
@@ -0,0 +1,364 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 */
+
+/* globals require, __dirname, global, Buffer */
+
+const { NodeServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+/// Sets the TRR related prefs and adds the certificate we use for the HTTP2
+/// server.
+function trr_test_setup() {
+ dump("start!\n");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+
+ Services.prefs.setBoolPref("network.http.spdy.enabled", true);
+ Services.prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ // the TRR server is on 127.0.0.1
+ Services.prefs.setCharPref("network.trr.bootstrapAddress", "127.0.0.1");
+
+ // use the h2 server as DOH provider
+ // make all native resolve calls "secretly" resolve localhost instead
+ Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
+
+ // 0 - off, 1 - reserved, 2 - TRR first, 3 - TRR only, 4 - reserved
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR first
+ Services.prefs.setBoolPref("network.trr.wait-for-portal", false);
+ // By default wait for all responses before notifying the listeners.
+ Services.prefs.setBoolPref("network.trr.wait-for-A-and-AAAA", true);
+ // 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");
+}
+
+/// 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.bootstrapAddress");
+ Services.prefs.clearUserPref("network.trr.blacklist-duration");
+ 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.skip-AAAA-when-not-supported");
+ Services.prefs.clearUserPref("network.trr.wait-for-A-and-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.spdy.enabled");
+ Services.prefs.clearUserPref("network.http.spdy.enabled.http2");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.dns.native-is-localhost");
+ Services.prefs.clearUserPref(
+ "network.trr.send_empty_accept-encoding_headers"
+ );
+}
+
+/// This class sends a DNS query and can be awaited as a promise to get the
+/// response.
+class TRRDNSListener {
+ constructor(name, options = {}) {
+ this.name = name;
+ this.options = options;
+ this.expectedAnswer = options.expectedAnswer ?? undefined;
+ this.expectedSuccess = options.expectedSuccess ?? true;
+ this.delay = options.delay;
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ let trrServer = options.trrServer || "";
+
+ const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+ );
+ const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+ );
+ const currentThread = threadManager.currentThread;
+
+ let resolverInfo =
+ trrServer == "" ? null : dns.newTRRResolverInfo(trrServer);
+ try {
+ this.request = dns.asyncResolve(
+ name,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ this.options.flags || 0,
+ resolverInfo,
+ this,
+ currentThread,
+ {} // defaultOriginAttributes
+ );
+ Assert.ok(!options.expectEarlyFail);
+ } catch (e) {
+ Assert.ok(options.expectEarlyFail);
+ this.resolve([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");
+ 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);
+ }
+}
+
+/// Implements a basic HTTP2 server
+class TRRServerCode {
+ 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 url = require("url");
+ global.path_handlers = {};
+ global.handler = (req, resp) => {
+ const path = req.headers[global.http2.constants.HTTP2_HEADER_PATH];
+ let u = url.parse(req.url, true);
+ let handler = global.path_handlers[u.pathname];
+ if (handler) {
+ return handler(req, resp, u);
+ }
+
+ // 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);
+ };
+
+ // key: string "name/type"
+ // value: array [answer1, answer2]
+ global.dns_query_answers = {};
+
+ global.http2 = require("http2");
+ global.server = global.http2.createSecureServer(options, global.handler);
+
+ await global.server.listen(port);
+
+ global.dnsPacket = require(`${__dirname}/../dns-packet`);
+ global.ip = require(`${__dirname}/../node-ip`);
+
+ return global.server.address().port;
+ }
+}
+
+/// 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) {
+ return 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");
+ return processRequest(req, resp, requestBody);
+ } else {
+ // unexpected method.
+ resp.writeHead(405);
+ resp.end("Unexpected method");
+ }
+
+ function processRequest(req, resp, payload) {
+ let dnsQuery = global.dnsPacket.decode(payload);
+ let response =
+ global.dns_query_answers[
+ `${dnsQuery.questions[0].name}/${dnsQuery.questions[0].type}`
+ ] || {};
+
+ let flags = global.dnsPacket.RECURSION_DESIRED;
+ if (
+ (!response.answers || !response.answers.length) &&
+ response.additionals &&
+ response.additionals.length > 0
+ ) {
+ flags |= global.dnsPacket.rcodes.toRcode("SERVFAIL");
+ }
+ let buf = global.dnsPacket.encode({
+ type: "response",
+ id: dnsQuery.id,
+ flags,
+ questions: dnsQuery.questions,
+ answers: response.answers || [],
+ additionals: response.additionals || [],
+ });
+
+ let writeResponse = (resp, buf) => {
+ resp.setHeader("Content-Length", buf.length);
+ resp.writeHead(200, { "Content-Type": "application/dns-message" });
+ resp.write(buf);
+ resp.end("");
+ };
+
+ if (response.delay) {
+ setTimeout(
+ arg => {
+ writeResponse(arg[0], arg[1]);
+ },
+ response.delay,
+ [resp, buf]
+ );
+ return;
+ }
+
+ writeResponse(resp, buf);
+ }
+}
+
+// A convenient wrapper around NodeServer
+class TRRServer {
+ /// 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(TRRServerCode);
+ this.port = await this.execute(`TRRServerCode.startServer(${port})`);
+ await this.registerPathHandler("/dns-query", trrQueryHandler);
+ }
+
+ /// 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.kill(this.processId);
+ this.processId = undefined;
+ }
+ }
+
+ /// @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()}`
+ );
+ }
+
+ /// @name : string - name we're providing answers for. eg: foo.example.com
+ /// @type : string - the DNS query type. eg: "A", "AAAA", "CNAME", etc
+ /// @answers : array - array of answers (hashmap) that dnsPacket can parse
+ /// eg: [{
+ /// name: "bar.example.com",
+ /// ttl: 55,
+ /// type: "A",
+ /// flush: false,
+ /// data: "1.2.3.4",
+ /// }]
+ async registerDoHAnswers(name, type, answers, additionals, delay = 0) {
+ let text = `global.dns_query_answers["${name}/${type}"] = ${JSON.stringify({
+ answers,
+ additionals,
+ delay,
+ })}`;
+ return this.execute(text);
+ }
+}
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/moz.build b/netwerk/test/unit/moz.build
new file mode 100644
index 0000000000..2e6708eaf6
--- /dev/null
+++ b/netwerk/test/unit/moz.build
@@ -0,0 +1,8 @@
+# -*- 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/.
+
+# Temporarily disabled. See bug 1256495.
+# GeneratedTestCertificate('http2-ca.pem')
diff --git a/netwerk/test/unit/perftest.ini b/netwerk/test/unit/perftest.ini
new file mode 100644
index 0000000000..789d6ae3e9
--- /dev/null
+++ b/netwerk/test/unit/perftest.ini
@@ -0,0 +1 @@
+[test_http3_perf.js]
diff --git a/netwerk/test/unit/socks_client_subprocess.js b/netwerk/test/unit/socks_client_subprocess.js
new file mode 100644
index 0000000000..1ae0e31490
--- /dev/null
+++ b/netwerk/test/unit/socks_client_subprocess.js
@@ -0,0 +1,101 @@
+/* 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(
+ stream => {
+ resolve(stream);
+ },
+ 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);
+ 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..b4df481330
--- /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_responses.js b/netwerk/test/unit/test_304_responses.js
new file mode 100644
index 0000000000..fee8a23fd5
--- /dev/null
+++ b/netwerk/test/unit/test_304_responses.js
@@ -0,0 +1,106 @@
+"use strict";
+// https://bugzilla.mozilla.org/show_bug.cgi?id=761228
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+const testFileName = "test_customConditionalRequest_304";
+const basePath = "/" + testFileName + "/";
+
+XPCOMUtils.defineLazyGetter(this, "baseURI", function() {
+ return URL + basePath;
+});
+
+const unexpected304 = "unexpected304";
+const existingCached304 = "existingCached304";
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.newURI(url);
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+function clearCache() {
+ var service = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(
+ Ci.nsICacheStorageService
+ );
+ service.clear();
+}
+
+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 finish_test(request, buffer) {
+ httpServer.stop(do_test_finished);
+}
+
+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..84295a071f
--- /dev/null
+++ b/netwerk/test/unit/test_307_redirect.js
@@ -0,0 +1,96 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "uri", function() {
+ return URL + "/redirect";
+});
+
+XPCOMUtils.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);
+
+ var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ 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..9ee0e1cb04
--- /dev/null
+++ b/netwerk/test/unit/test_421.js
@@ -0,0 +1,64 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/421";
+var httpbody = "0123456789";
+var channel;
+var ios;
+
+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..786d4fc48f
--- /dev/null
+++ b/netwerk/test/unit/test_MIME_params.js
@@ -0,0 +1,784 @@
+/**
+ * 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", ""],
+];
+
+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 {
+ var 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 (e) {}
+ }
+ continue;
+ }
+
+ // check filename parameter
+ var expectedFn =
+ tests[i].length == 3 || whichRFC == 0 ? tests[i][2] : tests[i][4];
+
+ try {
+ var 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 (e) {}
+ }
+ 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..9670b3ba19
--- /dev/null
+++ b/netwerk/test/unit/test_NetUtil.js
@@ -0,0 +1,818 @@
+/* -*- 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.import("resource://testing-common/httpd.js");
+
+// 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 = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .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 = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .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 = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .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() {
+ let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ // Check that we get the same URI back from the IO service and the utility
+ // method.
+ const TEST_URI = "http://mozilla.org";
+ let iosURI = ios.newURI(TEST_URI);
+ let NetUtilURI = NetUtil.newURI(TEST_URI);
+ Assert.ok(iosURI.equals(NetUtilURI));
+
+ run_next_test();
+}
+
+function test_newURI_takes_nsIFile() {
+ let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ // Create a test file that we can pass into NetUtil.newURI
+ let file = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .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 = ios.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 = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .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 ios = Services.io;
+ let iosChannel = ios.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));
+var index = 0;
diff --git a/netwerk/test/unit/test_SuperfluousAuth.js b/netwerk/test/unit/test_SuperfluousAuth.js
new file mode 100644
index 0000000000..fa50e385cd
--- /dev/null
+++ b/netwerk/test/unit/test_SuperfluousAuth.js
@@ -0,0 +1,99 @@
+/*
+
+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.import("resource://testing-common/httpd.js");
+
+const { MockRegistrar } = ChromeUtils.import(
+ "resource://testing-common/MockRegistrar.jsm"
+);
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+XPCOMUtils.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/embedcomp/prompt-service;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_ABORT);
+ 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..df55ebe157
--- /dev/null
+++ b/netwerk/test/unit/test_URIs.js
@@ -0,0 +1,1042 @@
+/* -*- 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";
+
+var gIoService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+);
+
+// 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/
+
+// 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:",
+ pathQueryRef: "//mozilla.org/",
+ 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 = gIoService.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);
+ }
+}
+
+// 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 = gIoService.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 = gIoService.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 = gIoService.newURI("about:blank#");
+ let uri2 = gIoService.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 = gIoService.newURI("about:blank?something");
+ uri2 = gIoService.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 = gIoService.newURI("about:blank?query#ref");
+ uri2 = gIoService.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 = gIoService.newURI("view-source:http://example.com/path#");
+ uri2 = gIoService.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 = gIoService.newURI("view-source:http://example.com/path?something");
+ uri2 = gIoService.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 = gIoService.newURI("view-source:http://example.com/path?query#ref");
+ uri2 = gIoService.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 = gIoService.newURI("view-source:about:blank#");
+ uri2 = gIoService.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 = gIoService.newURI("view-source:about:blank?something");
+ uri2 = gIoService.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 = gIoService.newURI("view-source:about:blank?query#ref");
+ uri2 = gIoService.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 = gIoService.newURI("data:text/plain,hello%20world#space hash");
+ Assert.equal(uri.spec, "data:text/plain,hello%20world#space%20hash");
+ uri = gIoService.newURI("data:text/plain,hello%20world#space%20hash");
+ Assert.equal(uri.spec, "data:text/plain,hello%20world#space%20hash");
+ uri = gIoService.newURI("data:text/plain,hello world#space%20hash");
+ Assert.equal(uri.spec, "data:text/plain,hello world#space%20hash");
+ uri = gIoService.newURI("data:text/plain,hello world#space hash");
+ Assert.equal(uri.spec, "data:text/plain,hello world#space%20hash");
+ uri = gIoService.newURI("http://example.com/test path#test path");
+ uri = gIoService.newURI("http://example.com/test%20path#test%20path");
+});
+
+add_task(function check_schemeIsNull() {
+ let uri = gIoService.newURI("data:text/plain,aaa");
+ Assert.ok(!uri.schemeIs(null));
+ uri = gIoService.newURI("http://example.com");
+ Assert.ok(!uri.schemeIs(null));
+ uri = gIoService.newURI("dummyscheme://example.com");
+ Assert.ok(!uri.schemeIs(null));
+ uri = gIoService.newURI("jar:resource://gre/chrome.toolkit.jar!/");
+ Assert.ok(!uri.schemeIs(null));
+ uri = gIoService.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 = gIoService.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 = gIoService.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 = gIoService.newURI("http://example.com");
+ let uri = gIoService.newURI("tel::+371 27028456", "utf-8", base);
+ Assert.equal(uri.spec, "tel::+371 27028456");
+});
+
+add_task(function test_extra_protocols() {
+ // dweb://
+ let url = gIoService.newURI("dweb://example.com/test");
+ Assert.equal(url.host, "example.com");
+
+ // dat://
+ url = gIoService.newURI(
+ "dat://41f8a987cfeba80a037e51cc8357d513b62514de36f2f9b3d3eeec7a8fb3b5a5/"
+ );
+ Assert.equal(
+ url.host,
+ "41f8a987cfeba80a037e51cc8357d513b62514de36f2f9b3d3eeec7a8fb3b5a5"
+ );
+ url = gIoService.newURI("dat://example.com/test");
+ Assert.equal(url.host, "example.com");
+
+ // ipfs://
+ url = gIoService.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 = gIoService.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 = gIoService.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 = gIoService.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 = gIoService.newURI("http://example.org/xenia?");
+ let resolved = gIoService.newURI("?x", null, base);
+ let expected = gIoService.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 = gIoService.newURI("http://example.org/xènia?");
+ resolved = gIoService.newURI("?x", null, base);
+ expected = gIoService.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 = gIoService.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!/");
+});
diff --git a/netwerk/test/unit/test_URIs2.js b/netwerk/test/unit/test_URIs2.js
new file mode 100644
index 0000000000..997f63658e
--- /dev/null
+++ b/netwerk/test/unit/test_URIs2.js
@@ -0,0 +1,906 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+"use strict";
+
+var gIoService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+);
+
+// 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,
+ 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",
+ 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 = gIoService.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);
+ }
+}
+
+// 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 = gIoService.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 = gIoService.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 = gIoService.newURI("http://example.org/xenia?");
+ let resolved = gIoService.newURI("?x", null, base);
+ let expected = gIoService.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 = gIoService.newURI("http://example.org/xènia?");
+ resolved = gIoService.newURI("?x", null, base);
+ expected = gIoService.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..dfe037d0ec
--- /dev/null
+++ b/netwerk/test/unit/test_XHR_redirects.js
@@ -0,0 +1,273 @@
+// 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.import("resource://testing-common/httpd.js");
+const { Preferences } = ChromeUtils.import(
+ "resource://gre/modules/Preferences.jsm"
+);
+
+var sSame;
+var sOther;
+var sRedirectPromptPref;
+
+const BUGID = "676059";
+const OTHERBUGID = "696849";
+
+XPCOMUtils.defineLazyGetter(this, "pSame", function() {
+ return sSame.identity.primaryPort;
+});
+XPCOMUtils.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 (var 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 (var 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..186e2a539a
--- /dev/null
+++ b/netwerk/test/unit/test_about_networking.js
@@ -0,0 +1,121 @@
+/* -*- 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.import("resource://testing-common/httpd.js");
+
+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
+ );
+ 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);
+
+ let ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+
+ gHttpServer.start(-1);
+
+ let uri = ioService.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..2da50acad3
--- /dev/null
+++ b/netwerk/test/unit/test_about_protocol.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";
+
+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(aOuter, aIID) {
+ if (aOuter) {
+ throw Components.Exception("", Cr.NS_ERROR_NO_AGGREGATION);
+ }
+ return unsafeAboutModule.QueryInterface(aIID);
+ },
+ lockFactory(aLock) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
+};
+
+function run_test() {
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ let classID = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator)
+ .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..766601ee5f
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_closeWithStatus.js
@@ -0,0 +1,174 @@
+/**
+ * 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.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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 (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .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");
+
+ try {
+ var etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {
+ var 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, "", true);
+
+ 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", true);
+ cc.preferAlternativeDataType(altContentType, "text/plain", true);
+ cc.preferAlternativeDataType("dummy2", "", true);
+
+ 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..05959a54af
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_cross_process.js
@@ -0,0 +1,143 @@
+/**
+ * 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.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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 (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .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");
+
+ try {
+ var etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {
+ var 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() {
+ 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, "", true);
+
+ 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, "", true);
+
+ 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..8521c13107
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_overwrite.js
@@ -0,0 +1,203 @@
+/**
+ * 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.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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, "", true);
+ }
+ 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..2132a9237f
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_simple.js
@@ -0,0 +1,196 @@
+/**
+ * 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.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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 (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .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");
+
+ try {
+ var etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {
+ var 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, "", true);
+
+ 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", true);
+ cc.preferAlternativeDataType(altContentType, "text/plain", true);
+ cc.preferAlternativeDataType("dummy2", "", true);
+
+ 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, "", true);
+ 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..c6eb665226
--- /dev/null
+++ b/netwerk/test/unit/test_alt-data_stream.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 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.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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, "", true);
+
+ 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, "", true);
+
+ 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, "", false);
+
+ 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) {
+ cc.getAltDataInputStream(altContentType, {
+ onInputStreamReady(aInputStream) {
+ Assert.ok(!!aInputStream);
+ 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..d928394272
--- /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..e9e0d5fc7b
--- /dev/null
+++ b/netwerk/test/unit/test_altsvc.js
@@ -0,0 +1,519 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var h2Port;
+var prefs;
+var spdypref;
+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() {
+ var env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ spdypref = prefs.getBoolPref("network.http.spdy.enabled");
+ http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2");
+ altsvcpref1 = prefs.getBoolPref("network.http.altsvc.enabled");
+ altsvcpref2 = prefs.getBoolPref("network.http.altsvc.oe", true);
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", 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.spdy.enabled", spdypref);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", 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 retryCounter = 0;
+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();
+ },
+ });
+ 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..a443f4732f
--- /dev/null
+++ b/netwerk/test/unit/test_altsvc_http3.js
@@ -0,0 +1,496 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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() {
+ var env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h3Port = 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.enabled", 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-27=" + 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.enabled");
+ 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 retryCounter = 0;
+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-27=:port
+function doTest1() {
+ dump("doTest1()\n");
+ origin = httpFooOrigin;
+ xaltsvc = h3Route;
+ nextTest = doTest2;
+ do_test_pending();
+ doTest();
+ xaltsvc = h3FooRoute;
+}
+
+// http://foo served from h3-27=foo:port
+function doTest2() {
+ dump("doTest2()\n");
+ origin = httpFooOrigin;
+ xaltsvc = h3FooRoute;
+ nextTest = doTest3;
+ do_test_pending();
+ doTest();
+}
+
+// http://foo served from h3-27=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-27=: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-27=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-27=: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..a93d67dac5
--- /dev/null
+++ b/netwerk/test/unit/test_altsvc_pref.js
@@ -0,0 +1,139 @@
+"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() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h3Port = env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ h3AltSvc = ":" + h3Port;
+
+ h3Route = "foo.example.com:" + h3Port;
+ do_get_profile();
+ prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.http.http3.enabled", 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-27=" + h3AltSvc
+ );
+
+ let chan = makeChan(httpsOrigin + "http3-test");
+ let listener = new Http3CheckListener();
+ listener.expectedRoute = h3Route;
+ chan.asyncOpen(listener);
+}
+
+function testsDone() {
+ prefs.clearUserPref("network.http.http3.enabled");
+ 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..e118f6f96c
--- /dev/null
+++ b/netwerk/test/unit/test_anonymous-coalescing.js
@@ -0,0 +1,186 @@
+/*
+- 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 spdypref;
+var http2pref;
+var extpref;
+
+function run_test() {
+ var env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ spdypref = prefs.getBoolPref("network.http.spdy.enabled");
+ http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2");
+ extpref = prefs.getBoolPref("network.http.originextension");
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", 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.spdy.enabled", spdypref);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", 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..18a9c8286b
--- /dev/null
+++ b/netwerk/test/unit/test_auth_dialog_permission.js
@@ -0,0 +1,283 @@
+// 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.import("resource://testing-common/httpd.js");
+
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+
+// 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);
+
+ 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);
+}
+
+var httpserv = new HttpServer();
+httpserv.registerPathHandler("/auth", authHandler);
+httpserv.start(-1);
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "PORT", function() {
+ return 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) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.newURI(url);
+}
+
+function makeChan(loadingUrl, url, contentPolicy) {
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ var uri = make_uri(loadingUrl);
+ var principal = ssm.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..75c0dd2c82
--- /dev/null
+++ b/netwerk/test/unit/test_auth_jar.js
@@ -0,0 +1,97 @@
+"use strict";
+
+function createURI(s) {
+ let service = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+ return service.newURI(s);
+}
+
+function run_test() {
+ // Set up a profile.
+ do_get_profile();
+
+ var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ 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_proxy.js b/netwerk/test/unit/test_auth_proxy.js
new file mode 100644
index 0000000000..ab0dee0bdb
--- /dev/null
+++ b/netwerk/test/unit/test_auth_proxy.js
@@ -0,0 +1,465 @@
+/* -*- 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.import("resource://testing-common/httpd.js");
+
+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;
+ }
+ return true;
+ } catch (e) {
+ do_throw(e);
+ }
+ },
+
+ asyncPromptAuth: function ap2_async(
+ channel,
+ callback,
+ context,
+ encryptionLevel,
+ authInfo
+ ) {
+ try {
+ var me = this;
+ var allOverAndDead = false;
+ executeSoon(function() {
+ try {
+ if (allOverAndDead) {
+ throw "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 "can't cancel, already ran";
+ }
+ callback.onAuthAvailable(context, authInfo);
+ allOverAndDead = true;
+ });
+ } catch (e) {
+ do_throw(e);
+ }
+ },
+};
+
+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..b49b0569b9
--- /dev/null
+++ b/netwerk/test/unit/test_authentication.js
@@ -0,0 +1,754 @@
+// 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.import("resource://testing-common/httpd.js");
+
+// Turn off the authentication dialog blocking for this test.
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+XPCOMUtils.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;
+}
+
+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);
+
+ 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) {
+ 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);
+ },
+};
+
+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);
+
+ moveToNextTest();
+ },
+};
+
+function makeChan(url, loadingUrl) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ var principal = ssm.createContentPrincipal(ios.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_noauth,
+ test_returnfalse1,
+ test_wrongpw1,
+ test_prompt1,
+ test_prompt1CrossOrigin,
+ test_prompt2CrossOrigin,
+ test_returnfalse2,
+ test_wrongpw2,
+ test_prompt2,
+ test_ntlm,
+ test_basicrealm,
+ test_nonascii,
+ test_digest_noauth,
+ test_digest,
+ test_digest_bogus_user,
+ test_short_digest,
+ test_large_realm,
+ test_large_domain,
+ test_nonascii_xhr,
+];
+
+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.registerPathHandler("/auth/ntlm/simple", authNtlmSimple);
+ httpserv.registerPathHandler("/auth/realm", authRealm);
+ httpserv.registerPathHandler("/auth/non_ascii", authNonascii);
+ httpserv.registerPathHandler("/auth/digest", authDigest);
+ httpserv.registerPathHandler("/auth/short_digest", authShortDigest);
+ httpserv.registerPathHandler("/largeRealm", largeRealm);
+ httpserv.registerPathHandler("/largeDomain", largeDomain);
+
+ httpserv.start(-1);
+
+ tests[0]();
+}
+
+function test_noauth() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_returnfalse1() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 1);
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_wrongpw1() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 1);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_prompt1() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 1);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_prompt1CrossOrigin() {
+ var chan = makeChan(URL + "/auth", "http://example.org");
+
+ chan.notificationCallbacks = new Requestor(16, 1);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_prompt2CrossOrigin() {
+ var chan = makeChan(URL + "/auth", "http://example.org");
+
+ chan.notificationCallbacks = new Requestor(16, 2);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_returnfalse2() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_wrongpw2() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 2);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_prompt2() {
+ var chan = makeChan(URL + "/auth", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 2);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_ntlm() {
+ var chan = makeChan(URL + "/auth/ntlm/simple", URL);
+
+ chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_basicrealm() {
+ var chan = makeChan(URL + "/auth/realm", URL);
+
+ chan.notificationCallbacks = new RealmTestRequestor();
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+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
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_nonascii_xhr() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", URL + "/auth/non_ascii", true, "é", "é");
+ xhr.onreadystatechange = function(event) {
+ if (xhr.readyState == 4) {
+ Assert.equal(xhr.status, 200);
+ moveToNextTest();
+ xhr.onreadystatechange = null;
+ }
+ };
+ xhr.send(null);
+
+ do_test_pending();
+}
+
+function test_digest_noauth() {
+ var chan = makeChan(URL + "/auth/digest", URL);
+
+ //chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_digest() {
+ var chan = makeChan(URL + "/auth/digest", URL);
+
+ chan.notificationCallbacks = new Requestor(0, 2);
+ listener.expectedCode = 200; // OK
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_digest_bogus_user() {
+ var chan = makeChan(URL + "/auth/digest", URL);
+ chan.notificationCallbacks = new Requestor(FLAG_BOGUS_USER, 2);
+ listener.expectedCode = 401; // unauthorized
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+// Test header "WWW-Authenticate: Digest" - bug 1338876.
+function test_short_digest() {
+ var chan = makeChan(URL + "/auth/short_digest", URL);
+ chan.notificationCallbacks = new Requestor(FLAG_NO_REALM, 2);
+ listener.expectedCode = 401; // OK
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+// 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);
+}
+
+// /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);
+}
+
+//
+// Digest functions
+//
+function bytesFromString(str) {
+ var converter = Cc[
+ "@mozilla.org/intl/scriptableunicodeconverter"
+ ].createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ var data = converter.convertToByteArray(str);
+ return data;
+}
+
+// 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("");
+}
+
+//
+// Digest handler
+//
+// /auth/digest
+function authDigest(metadata, response) {
+ var nonce = "6f93719059cf8d568005727f3250e798";
+ var opaque = "1234opaque1234";
+ var cnonceRE = /cnonce="(\w+)"/;
+ var responseRE = /response="(\w+)"/;
+ var usernameRE = /username="(\w+)"/;
+ var authenticate =
+ 'Digest realm="secret", domain="/", qop=auth,' +
+ 'algorithm=MD5, nonce="' +
+ nonce +
+ '" opaque="' +
+ opaque +
+ '"';
+ 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:/auth/digest";
+ 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 = "success";
+ } else {
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", authenticate, false);
+ body = "auth failed";
+ }
+ }
+ } else {
+ // no header, send one
+ response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", authenticate, false);
+ body = "failed, no header";
+ }
+
+ response.bodyOutputStream.write(body, body.length);
+}
+
+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 > 0) {
+ 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);
+}
+
+function test_large_realm() {
+ var chan = makeChan(URL + "/largeRealm", URL);
+
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
+
+function test_large_domain() {
+ var chan = makeChan(URL + "/largeDomain", URL);
+
+ listener.expectedCode = 401; // Unauthorized
+ chan.asyncOpen(listener);
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_authpromptwrapper.js b/netwerk/test/unit/test_authpromptwrapper.js
new file mode 100644
index 0000000000..91068dc692
--- /dev/null
+++ b/netwerk/test/unit/test_authpromptwrapper.js
@@ -0,0 +1,233 @@
+// 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 = "";
+
+ // 5: FTP
+ var uri2 = NetUtil.newURI("ftp://" + host);
+ var ftpchan = NetUtil.newChannel({
+ uri: uri2,
+ loadUsingSystemPrincipal: true,
+ });
+
+ prompt1 = new Prompt1();
+ prompt1.rv = expectedRV;
+ prompt1.scheme = "ftp";
+
+ wrapper = adapter.createAdapter(prompt1);
+ var rv = wrapper.promptAuth(ftpchan, 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 = "";
+ }
+ 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..696884f356
--- /dev/null
+++ b/netwerk/test/unit/test_backgroundfilesaver.js
@@ -0,0 +1,775 @@
+/* -*- 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.defineModuleGetter(
+ this,
+ "FileUtils",
+ "resource://gre/modules/FileUtils.jsm"
+);
+ChromeUtils.defineModuleGetter(
+ this,
+ "NetUtil",
+ "resource://gre/modules/NetUtil.jsm"
+);
+ChromeUtils.defineModuleGetter(
+ this,
+ "FileTestUtils",
+ "resource://testing-common/FileTestUtils.jsm"
+);
+
+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",
+};
+
+const gTextDecoder = new TextDecoder();
+
+// 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(aSaver, aTarget) {
+ if (aOnTargetChangeFn) {
+ aOnTargetChangeFn(aTarget);
+ }
+ },
+ onSaveComplete: function BFSO_onSaveComplete(aSaver, 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..f5ef6b7bb7
--- /dev/null
+++ b/netwerk/test/unit/test_be_conservative.js
@@ -0,0 +1,247 @@
+// -*- 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);
+
+function getCert() {
+ return new Promise((resolve, reject) => {
+ let certService = Cc[
+ "@mozilla.org/security/local-cert-service;1"
+ ].getService(Ci.nsILocalCertService);
+ certService.getOrCreateCert("beConservative-test", {
+ handleCert(c, rv) {
+ if (rv) {
+ reject(rv);
+ return;
+ }
+ resolve(c);
+ },
+ });
+ });
+}
+
+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.securityInfo.QueryInterface(
+ 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);
+ let overrideBits =
+ Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+ Ci.nsICertOverrideService.ERROR_MISMATCH;
+ certOverrideService.rememberValidityOverride(
+ hostname,
+ port,
+ cert,
+ overrideBits,
+ 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() {
+ Services.prefs.setIntPref("security.tls.version.max", 4);
+ Services.prefs.setCharPref("network.dns.localDomains", hostname);
+ let cert = await getCert();
+
+ // 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");
+});
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..190fbba157
--- /dev/null
+++ b/netwerk/test/unit/test_be_conservative_error_handling.js
@@ -0,0 +1,242 @@
+// -*- 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);
+
+function getCert() {
+ return new Promise((resolve, reject) => {
+ let certService = Cc[
+ "@mozilla.org/security/local-cert-service;1"
+ ].getService(Ci.nsILocalCertService);
+ certService.getOrCreateCert("beConservative-test", {
+ handleCert(c, rv) {
+ if (rv) {
+ reject(rv);
+ return;
+ }
+ resolve(c);
+ },
+ });
+ });
+}
+
+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.securityInfo.QueryInterface(
+ 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);
+ let overrideBits =
+ Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+ Ci.nsICertOverrideService.ERROR_MISMATCH;
+ certOverrideService.rememberValidityOverride(
+ hostname,
+ port,
+ cert,
+ overrideBits,
+ 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 = await getCert();
+
+ // 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_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_http.js b/netwerk/test/unit/test_brotli_http.js
new file mode 100644
index 0000000000..5c3d2452f0
--- /dev/null
+++ b/netwerk/test/unit/test_brotli_http.js
@@ -0,0 +1,44 @@
+// 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.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + "/content";
+});
+
+var httpServer = null;
+
+add_task(async function test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+ Services.prefs.setCharPref(
+ "network.http.accept-encoding",
+ "gzip, deflate, br"
+ );
+
+ let chan = NetUtil.newChannel({ uri: URL, loadUsingSystemPrincipal: true });
+ let [, buff] = await new Promise(resolve => {
+ chan.asyncOpen(
+ new ChannelListener(
+ (req, buff) => {
+ resolve([req, buff]);
+ },
+ null,
+ CL_IGNORE_CL
+ )
+ );
+ });
+ equal(buff, "hello");
+ Services.prefs.clearUserPref("network.http.accept-encoding");
+ 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..7e9ce74409
--- /dev/null
+++ b/netwerk/test/unit/test_bug1064258.js
@@ -0,0 +1,142 @@
+/**
+ * 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.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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 "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..a4e6117cbc
--- /dev/null
+++ b/netwerk/test/unit/test_bug1177909.js
@@ -0,0 +1,194 @@
+"use strict";
+
+const { MockRegistrar } = ChromeUtils.import(
+ "resource://testing-common/MockRegistrar.jsm"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "gProxyService",
+ "@mozilla.org/network/protocol-proxy-service;1",
+ "nsIProtocolProxyService"
+);
+
+XPCOMUtils.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" || aScheme == "ftp") {
+ 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) {
+ const prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ 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");
+});
+
+if (Services.prefs.getBoolPref("network.ftp.enabled")) {
+ add_task(async function testFtpProxy() {
+ let pi = await TestProxyTypeByURI("ftp://ftp.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 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 ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+ let chan = ioService.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 ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+ let chan = ioService.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 ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+ let chan = ioService.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");
+});
diff --git a/netwerk/test/unit/test_bug1195415.js b/netwerk/test/unit/test_bug1195415.js
new file mode 100644
index 0000000000..491fd2d753
--- /dev/null
+++ b/netwerk/test/unit/test_bug1195415.js
@@ -0,0 +1,163 @@
+// Test for bug 1195415
+
+"use strict";
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+
+ // 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..de49952623
--- /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..a0a8639cf3
--- /dev/null
+++ b/netwerk/test/unit/test_bug1279246.js
@@ -0,0 +1,98 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ 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..97a7f4d1eb
--- /dev/null
+++ b/netwerk/test/unit/test_bug1312774_http1.js
@@ -0,0 +1,153 @@
+// 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.import("resource://testing-common/httpd.js");
+var server = new HttpServer();
+server.start(-1);
+var baseURL = "http://localhost:" + server.identity.primaryPort + "/";
+var maxConnections = 0;
+var urgentRequests = 0;
+var totalRequests = 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;
+}
+
+var testOrder = 0;
+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");
+ var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ maxConnections = prefs.getIntPref(
+ "network.http.max-persistent-connections-per-server"
+ );
+ urgentRequests = 2;
+ totalRequests = maxConnections + urgentRequests;
+ 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..4c13c2b4ab
--- /dev/null
+++ b/netwerk/test/unit/test_bug1312782_http1.js
@@ -0,0 +1,206 @@
+// 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.import("resource://testing-common/httpd.js");
+
+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(windowId, requestId) {
+ let uri = baseURL;
+ var chan = make_channel(uri);
+ chan.topLevelOuterContentWindowId = windowId;
+ 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");
+ var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ maxConnections = 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.
+ var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ 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 = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ obsvc.notifyObservers(
+ windowIdWrapper,
+ "net:current-toplevel-outer-content-windowid"
+ );
+}
diff --git a/netwerk/test/unit/test_bug1355539_http1.js b/netwerk/test/unit/test_bug1355539_http1.js
new file mode 100644
index 0000000000..55bc890d66
--- /dev/null
+++ b/netwerk/test/unit/test_bug1355539_http1.js
@@ -0,0 +1,207 @@
+// 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.import("resource://testing-common/httpd.js");
+
+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");
+ var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ maxConnections = 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..e14a9e6a99
--- /dev/null
+++ b/netwerk/test/unit/test_bug1378385_http1.js
@@ -0,0 +1,202 @@
+// 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.import("resource://testing-common/httpd.js");
+
+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(windowId, requestId, priority) {
+ let uri = baseURL;
+ var chan = make_channel(uri);
+ chan.topLevelOuterContentWindowId = windowId;
+ 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, windowId) {
+ for (var i = 0; i < responses.length; i++) {
+ var id = responses[i].getHeader("X-ID");
+ log("response id=" + id + " windowId=" + windowId);
+ Assert.equal(id, windowId);
+ }
+}
+
+var responseQueue = [];
+function setup_http_server() {
+ log("setup_http_server");
+ var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ maxConnections = 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.
+ var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ 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..e41615688e
--- /dev/null
+++ b/netwerk/test/unit/test_bug1411316_http1.js
@@ -0,0 +1,117 @@
+// 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.import("resource://testing-common/httpd.js");
+
+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");
+ var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ maxConnections = 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..9f4ab24008
--- /dev/null
+++ b/netwerk/test/unit/test_bug1527293.js
@@ -0,0 +1,92 @@
+// 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.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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..16b1ff4a3f
--- /dev/null
+++ b/netwerk/test/unit/test_bug1683176.js
@@ -0,0 +1,104 @@
+// Test bug 1683176
+//
+// Summary:
+// Test the case when a channel is cancelled when "negotiate" authentication
+// is processing.
+//
+
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+let prefs;
+let httpserv;
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "PORT", function() {
+ return httpserv.identity.primaryPort;
+});
+
+function makeChan(url, loadingUrl) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ var principal = ssm.createContentPrincipal(ios.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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ 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 makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+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_bug203271.js b/netwerk/test/unit/test_bug203271.js
new file mode 100644
index 0000000000..9801d5c5d4
--- /dev/null
+++ b/netwerk/test/unit/test_bug203271.js
@@ -0,0 +1,248 @@
+//
+// 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.import("resource://testing-common/httpd.js");
+const BUGID = "203271";
+
+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..2b7521565d
--- /dev/null
+++ b/netwerk/test/unit/test_bug248970_cache.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";
+
+// names for cache devices
+const kDiskDevice = "disk";
+const kMemoryDevice = "memory";
+const kOfflineDevice = "appcache";
+
+const kCacheA = "http://cache/A";
+const kCacheA2 = "http://cache/A2";
+const kCacheB = "http://cache/B";
+const kCacheC = "http://cache/C";
+const kTestContent = "test content";
+
+function make_input_stream_scriptable(input) {
+ var wrapper = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ wrapper.init(input);
+ return wrapper;
+}
+
+const entries = [
+ // key content device should exist after leaving PB
+ [kCacheA, kTestContent, kMemoryDevice, true],
+ [kCacheA2, kTestContent, kDiskDevice, false],
+ [kCacheB, kTestContent, kDiskDevice, true],
+ [kCacheC, kTestContent, kOfflineDevice, true],
+];
+
+var store_idx;
+var store_cb = null;
+var appCache = 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,
+ appCache
+ );
+}
+
+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,
+ appCache
+ );
+}
+
+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();
+
+ Services.prefs.setBoolPref("browser.cache.offline.enable", true);
+ Services.prefs.setBoolPref("browser.cache.offline.storage.enable", true);
+
+ appCache = Cc["@mozilla.org/network/application-cache-service;1"]
+ .getService(Ci.nsIApplicationCacheService)
+ .getApplicationCache("fake-client-id|fake-group-id");
+
+ // 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
+ var obsvc = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ obsvc.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..d7f3b381a5
--- /dev/null
+++ b/netwerk/test/unit/test_bug248970_cookie.js
@@ -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/. */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpserver;
+
+function inChildProcess() {
+ return (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .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
+ var obsvc = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ obsvc.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..3da3ba610f
--- /dev/null
+++ b/netwerk/test/unit/test_bug261425.js
@@ -0,0 +1,37 @@
+"use strict";
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ var newURI = ios.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..d829bbd800
--- /dev/null
+++ b/netwerk/test/unit/test_bug263127.js
@@ -0,0 +1,58 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var server;
+const BUGID = "263127";
+
+var listener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIDownloadObserver"]),
+
+ onDownloadComplete(downloader, request, ctxt, 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 = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .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..7794408523
--- /dev/null
+++ b/netwerk/test/unit/test_bug282432.js
@@ -0,0 +1,38 @@
+"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();
+ let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ // This file does not exist.
+ let file = do_get_file("_NOT_EXIST_.txt", true);
+ Assert.ok(!file.exists());
+ let channel = NetUtil.newChannel({
+ uri: ios.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..86d8bdc373
--- /dev/null
+++ b/netwerk/test/unit/test_bug321706.js
@@ -0,0 +1,12 @@
+"use strict";
+
+const url = "http://foo.com/folder/file?/.";
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ var newURI = ios.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..acb6d7690a
--- /dev/null
+++ b/netwerk/test/unit/test_bug331825.js
@@ -0,0 +1,41 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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..583627f270
--- /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 (var 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 (var spec of error_specs) {
+ check_resolution_error(spec);
+ }
+}
diff --git a/netwerk/test/unit/test_bug365133.js b/netwerk/test/unit/test_bug365133.js
new file mode 100644
index 0000000000..1f5408d301
--- /dev/null
+++ b/netwerk/test/unit/test_bug365133.js
@@ -0,0 +1,131 @@
+"use strict";
+
+const URL = "ftp://localhost/bug365133/";
+
+const tests = [
+ [
+ /* Unix style listing, space at the end of filename */
+ "drwxrwxr-x 2 500 500 4096 Jan 01 2000 a \r\n",
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "a%20" 0 Sat%2C%2001%20Jan%202000%2000%3A00%3A00%20GMT DIRECTORY \n',
+ ],
+ [
+ /* Unix style listing, space at the end of link name */
+ "lrwxrwxrwx 1 500 500 2 Jan 01 2000 l -> a \r\n",
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "l%20" 2 Sat%2C%2001%20Jan%202000%2000%3A00%3A00%20GMT SYMBOLIC-LINK \n',
+ ],
+ [
+ /* Unix style listing, regular file with " -> " in name */
+ "-rw-rw-r-- 1 500 500 0 Jan 01 2000 arrow -> in name \r\n",
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "arrow%20-%3E%20in%20name%20" 0 Sat%2C%2001%20Jan%202000%2000%3A00%3A00%20GMT FILE \n',
+ ],
+ [
+ /* Unix style listing, tab at the end of filename */
+ "drwxrwxrwx 2 500 500 4096 Jan 01 2000 t \r\n",
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "t%09" 0 Sat%2C%2001%20Jan%202000%2000%3A00%3A00%20GMT DIRECTORY \n',
+ ],
+ [
+ /* Unix style listing, multiple " -> " in filename */
+ "lrwxrwxrwx 1 500 500 26 Jan 01 2000 symlink with arrow -> in name -> file with arrow -> in name\r\n",
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "symlink%20with%20arrow%20-%3E%20in%20name" 26 Sat%2C%2001%20Jan%202000%2000%3A00%3A00%20GMT SYMBOLIC-LINK \n',
+ ],
+ [
+ /* Unix style listing, multiple " -> " in filename, incorrect filesize */
+ "lrwxrwxrwx 1 500 500 0 Jan 01 2000 symlink with arrow -> in name -> file with arrow -> in name\r\n",
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "symlink%20with%20arrow%20-%3E%20in%20name%20-%3E%20file%20with%20arrow" 0 Sat%2C%2001%20Jan%202000%2000%3A00%3A00%20GMT SYMBOLIC-LINK \n',
+ ],
+ [
+ /* DOS style listing, space at the end of filename, year 1999 */
+ "01-01-99 01:00AM 1024 file \r\n",
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "file%20" 1024 Fri%2C%2001%20Jan%201999%2001%3A00%3A00%20GMT FILE \n',
+ ],
+ [
+ /* DOS style listing, tab at the end of filename, year 2000 */
+ "01-01-00 01:00AM 1024 file \r\n",
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "file%09" 1024 Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT FILE \n',
+ ],
+];
+
+function checkData(request, data, ctx) {
+ Assert.equal(tests[0][1], data);
+ tests.shift();
+ executeSoon(next_test);
+}
+
+function storeData() {
+ var scs = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ var converter = scs.asyncConvertData(
+ "text/ftp-dir",
+ "application/http-index-format",
+ new ChannelListener(checkData, null, CL_ALLOW_UNKNOWN_CL),
+ null
+ );
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.data = tests[0][0];
+
+ var url = NetUtil.newURI(URL);
+
+ var channel = {
+ URI: url,
+ contentLength: -1,
+ pending: true,
+ isPending() {
+ return this.pending;
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIChannel"]),
+ };
+
+ converter.onStartRequest(channel, null);
+ converter.onDataAvailable(channel, stream, 0, 0);
+ channel.pending = false;
+ converter.onStopRequest(channel, null, Cr.NS_OK);
+}
+
+function next_test() {
+ if (tests.length == 0) {
+ do_test_finished();
+ } else {
+ storeData();
+ }
+}
+
+function run_test() {
+ executeSoon(next_test);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug368702.js b/netwerk/test/unit/test_bug368702.js
new file mode 100644
index 0000000000..89a5cc4b20
--- /dev/null
+++ b/netwerk/test/unit/test_bug368702.js
@@ -0,0 +1,153 @@
+"use strict";
+
+function run_test() {
+ var tld = Cc["@mozilla.org/network/effective-tld-service;1"].getService(
+ Ci.nsIEffectiveTLDService
+ );
+ 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 ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+
+ var uri = ioService.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..9a961b0024
--- /dev/null
+++ b/netwerk/test/unit/test_bug369787.js
@@ -0,0 +1,71 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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..05ea15aeac
--- /dev/null
+++ b/netwerk/test/unit/test_bug371473.js
@@ -0,0 +1,40 @@
+"use strict";
+
+function test_not_too_long() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ var spec = "jar:http://example.com/bar.jar!/";
+ try {
+ ios.newURI(spec);
+ } catch (e) {
+ do_throw("newURI threw even though it wasn't passed a large nested URI?");
+ }
+}
+
+function test_too_long() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ 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.
+ ios.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..3d97d568c5
--- /dev/null
+++ b/netwerk/test/unit/test_bug376844.js
@@ -0,0 +1,23 @@
+"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() {
+ var ioServ = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+
+ for (var i = 0; i < testURLs.length; i++) {
+ var uri = ioServ.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..af2f78dd26
--- /dev/null
+++ b/netwerk/test/unit/test_bug379034.js
@@ -0,0 +1,21 @@
+"use strict";
+
+function run_test() {
+ const ios = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+
+ 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..01f896a90f
--- /dev/null
+++ b/netwerk/test/unit/test_bug380994.js
@@ -0,0 +1,26 @@
+/* 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() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ for (var spec of specs) {
+ var uri = ios.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..8fd43c7c90
--- /dev/null
+++ b/netwerk/test/unit/test_bug388281.js
@@ -0,0 +1,42 @@
+"use strict";
+
+function run_test() {
+ const ios = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+
+ 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..5095a77bd4
--- /dev/null
+++ b/netwerk/test/unit/test_bug396389.js
@@ -0,0 +1,71 @@
+"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,
+ },
+ {
+ name: "network.IDN.whitelist.ch",
+ newVal: true,
+ },
+];
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ var uri1 = ios.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 = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ for (var 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 = ios.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 (var 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..659fda4ab5
--- /dev/null
+++ b/netwerk/test/unit/test_bug401564.js
@@ -0,0 +1,44 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpserver = null;
+const noRedirectURI = "/content";
+const pageValue = "Final page";
+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);
+
+ var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ 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..a7bdfb178f
--- /dev/null
+++ b/netwerk/test/unit/test_bug411952.js
@@ -0,0 +1,53 @@
+"use strict";
+
+function run_test() {
+ try {
+ var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+ 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..b5549352a8
--- /dev/null
+++ b/netwerk/test/unit/test_bug412457.js
@@ -0,0 +1,117 @@
+"use strict";
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ // check if hostname is unescaped before applying IDNA
+ var newURI = ios.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_UNEXPECTED/,
+ "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..33bfda4de6
--- /dev/null
+++ b/netwerk/test/unit/test_bug412945.js
@@ -0,0 +1,42 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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..49251da7fe
--- /dev/null
+++ b/netwerk/test/unit/test_bug414122.js
@@ -0,0 +1,62 @@
+"use strict";
+
+const PR_RDONLY = 0x1;
+
+var etld = Cc["@mozilla.org/network/effective-tld-service;1"].getService(
+ Ci.nsIEffectiveTLDService
+);
+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 = 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..f202b92300
--- /dev/null
+++ b/netwerk/test/unit/test_bug427957.js
@@ -0,0 +1,106 @@
+/**
+ * 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() {
+ // add an IDN whitelist pref
+ var pbi = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ pbi.setBoolPref("network.IDN.whitelist.com", true);
+
+ 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..7fcc898e7a
--- /dev/null
+++ b/netwerk/test/unit/test_bug429347.js
@@ -0,0 +1,75 @@
+"use strict";
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ 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..faa5685e34
--- /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 = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+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..d2200c1eb6
--- /dev/null
+++ b/netwerk/test/unit/test_bug464591.js
@@ -0,0 +1,100 @@
+// 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,
+ },
+ {
+ name: "network.IDN.whitelist.org",
+ newVal: true,
+ },
+];
+
+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 = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ 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..f8f6b8ed62
--- /dev/null
+++ b/netwerk/test/unit/test_bug468426.js
@@ -0,0 +1,129 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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..545ae75e5f
--- /dev/null
+++ b/netwerk/test/unit/test_bug468594.js
@@ -0,0 +1,176 @@
+//
+// 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.import("resource://testing-common/httpd.js");
+
+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..a661dc902d
--- /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..8a0ce0752e
--- /dev/null
+++ b/netwerk/test/unit/test_bug479413.js
@@ -0,0 +1,61 @@
+/**
+ * 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() {
+ // add an IDN whitelist pref
+ var pbi = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ var whitelistPref = "network.IDN.whitelist.com";
+
+ pbi.setBoolPref(whitelistPref, true);
+
+ 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");
+
+ // reset the pref
+ if (pbi.prefHasUserValue(whitelistPref)) {
+ pbi.clearUserPref(whitelistPref);
+ }
+}
diff --git a/netwerk/test/unit/test_bug479485.js b/netwerk/test/unit/test_bug479485.js
new file mode 100644
index 0000000000..bde98c6b70
--- /dev/null
+++ b/netwerk/test/unit/test_bug479485.js
@@ -0,0 +1,65 @@
+"use strict";
+
+function run_test() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ 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..a583a21663
--- /dev/null
+++ b/netwerk/test/unit/test_bug482601.js
@@ -0,0 +1,262 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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..2827e4c875
--- /dev/null
+++ b/netwerk/test/unit/test_bug482934.js
@@ -0,0 +1,189 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.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_bug484684.js b/netwerk/test/unit/test_bug484684.js
new file mode 100644
index 0000000000..49a7f79205
--- /dev/null
+++ b/netwerk/test/unit/test_bug484684.js
@@ -0,0 +1,139 @@
+"use strict";
+
+const URL = "ftp://localhost/bug464884/";
+
+const tests = [
+ // standard ls unix format
+ [
+ "-rw-rw-r-- 1 500 500 0 Jan 01 2000 file1\r\n" +
+ "-rw-rw-r-- 1 500 500 0 Jan 01 2000 file2\r\n",
+
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "file1" 0 Sat%2C%2001%20Jan%202000%2000%3A00%3A00%20GMT FILE \n' +
+ '201: "%20file2" 0 Sat%2C%2001%20Jan%202000%2000%3A00%3A00%20GMT FILE \n',
+ ],
+ // old Hellsoft unix format
+ [
+ "-[RWCEMFA] supervisor 214059 Jan 01 2000 file1\r\n" +
+ "-[RWCEMFA] supervisor 214059 Jan 01 2000 file2\r\n",
+
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "file1" 214059 Sat%2C%2001%20Jan%202000%2000%3A00%3A00%20GMT FILE \n' +
+ '201: "file2" 214059 Sat%2C%2001%20Jan%202000%2000%3A00%3A00%20GMT FILE \n',
+ ],
+ // new Hellsoft unix format
+ [
+ "- [RWCEAFMS] jrd 192 Jan 01 2000 file1\r\n" +
+ "- [RWCEAFMS] jrd 192 Jan 01 2000 file2\r\n",
+
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "file1" 192 Sat%2C%2001%20Jan%202000%2000%3A00%3A00%20GMT FILE \n' +
+ '201: "%20file2" 192 Sat%2C%2001%20Jan%202000%2000%3A00%3A00%20GMT FILE \n',
+ ],
+ // DOS format with correct offsets
+ [
+ "01-01-00 01:00AM <DIR> dir1\r\n" +
+ "01-01-00 01:00AM <JUNCTION> junction1 -> foo1\r\n" +
+ "01-01-00 01:00AM 95077 file1\r\n" +
+ "01-01-00 01:00AM <DIR> dir2\r\n" +
+ "01-01-00 01:00AM <JUNCTION> junction2 -> foo2\r\n" +
+ "01-01-00 01:00AM 95077 file2\r\n",
+
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "dir1" 0 Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT DIRECTORY \n' +
+ '201: "junction1" Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT SYMBOLIC-LINK \n' +
+ '201: "file1" 95077 Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT FILE \n' +
+ '201: "%20dir2" 0 Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT DIRECTORY \n' +
+ '201: "%20junction2" Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT SYMBOLIC-LINK \n' +
+ '201: "%20file2" 95077 Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT FILE \n',
+ ],
+ // DOS format with wrong offsets
+ [
+ "01-01-00 01:00AM <DIR> dir1\r\n" +
+ "01-01-00 01:00AM <DIR> dir2\r\n" +
+ "01-01-00 01:00AM <DIR> dir3\r\n" +
+ "01-01-00 01:00AM <JUNCTION> junction1 -> foo1\r\n" +
+ "01-01-00 01:00AM <JUNCTION> junction2 -> foo2\r\n" +
+ "01-01-00 01:00AM <JUNCTION> junction3 -> foo3\r\n" +
+ "01-01-00 01:00AM 95077 file1\r\n" +
+ "01-01-00 01:00AM 95077 file2\r\n",
+
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "dir1" 0 Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT DIRECTORY \n' +
+ '201: "dir2" 0 Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT DIRECTORY \n' +
+ '201: "dir3" 0 Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT DIRECTORY \n' +
+ '201: "junction1" Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT SYMBOLIC-LINK \n' +
+ '201: "junction2" Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT SYMBOLIC-LINK \n' +
+ '201: "junction3" Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT SYMBOLIC-LINK \n' +
+ '201: "file1" 95077 Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT FILE \n' +
+ '201: "file2" 95077 Sat%2C%2001%20Jan%202000%2001%3A00%3A00%20GMT FILE \n',
+ ],
+];
+
+function checkData(request, data, ctx) {
+ Assert.equal(tests[0][1], data);
+ tests.shift();
+ executeSoon(next_test);
+}
+
+function storeData(status, entry) {
+ var scs = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ var converter = scs.asyncConvertData(
+ "text/ftp-dir",
+ "application/http-index-format",
+ new ChannelListener(checkData, null, CL_ALLOW_UNKNOWN_CL),
+ null
+ );
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.data = tests[0][0];
+
+ var url = NetUtil.newURI(URL);
+
+ var channel = {
+ URI: url,
+ contentLength: -1,
+ pending: true,
+ isPending() {
+ return this.pending;
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIChannel"]),
+ };
+
+ converter.onStartRequest(channel, null);
+ converter.onDataAvailable(channel, stream, 0, 0);
+ channel.pending = false;
+ converter.onStopRequest(channel, null, Cr.NS_OK);
+}
+
+function next_test() {
+ if (tests.length == 0) {
+ do_test_finished();
+ } else {
+ storeData();
+ }
+}
+
+function run_test() {
+ executeSoon(next_test);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug490095.js b/netwerk/test/unit/test_bug490095.js
new file mode 100644
index 0000000000..7e8de69af1
--- /dev/null
+++ b/netwerk/test/unit/test_bug490095.js
@@ -0,0 +1,158 @@
+//
+// 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.import("resource://testing-common/httpd.js");
+
+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..e4fe449648
--- /dev/null
+++ b/netwerk/test/unit/test_bug504014.js
@@ -0,0 +1,74 @@
+"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() {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ for (var i = 0; i < valid_URIs.length; i++) {
+ try {
+ ios.newURI(valid_URIs[i]);
+ } catch (e) {
+ do_throw("cannot create URI:" + valid_URIs[i]);
+ }
+ }
+
+ for (var i = 0; i < invalid_URIs.length; i++) {
+ try {
+ ios.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..321da39912
--- /dev/null
+++ b/netwerk/test/unit/test_bug510359.js
@@ -0,0 +1,102 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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_bug515583.js b/netwerk/test/unit/test_bug515583.js
new file mode 100644
index 0000000000..fe2bad5399
--- /dev/null
+++ b/netwerk/test/unit/test_bug515583.js
@@ -0,0 +1,82 @@
+"use strict";
+
+const URL = "ftp://localhost/bug515583/";
+
+const tests = [
+ [
+ "[RWCEM1 4 1-MAR-1993 18:09:01.12\r\n" +
+ "[RWCEM1] 4 2-MAR-1993 18:09:01.12\r\n" +
+ "[RWCEM1]A 4 3-MAR-1993 18:09:01.12\r\n" +
+ "[RWCEM1]B; 4 4-MAR-1993 18:09:01.12\r\n" +
+ "[RWCEM1];1 4 5-MAR-1993 18:09:01.12\r\n" +
+ "[RWCEM1]; 4 6-MAR-1993 18:09:01.12\r\n" +
+ "[RWCEM1]C;1D 4 7-MAR-1993 18:09:01.12\r\n" +
+ "[RWCEM1]E;1 4 8-MAR-1993 18:09:01.12\r\n",
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "A" 2048 Wed%2C%2003%20Mar%201993%2018%3A09%3A01%20GMT FILE \n' +
+ '201: "E" 2048 Mon%2C%2008%20Mar%201993%2018%3A09%3A01%20GMT FILE \n',
+ ],
+ [
+ "\r\r\r\n",
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n",
+ ],
+];
+
+function checkData(request, data, ctx) {
+ Assert.equal(tests[0][1], data);
+ tests.shift();
+ executeSoon(next_test);
+}
+
+function storeData(status, entry) {
+ var scs = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ var converter = scs.asyncConvertData(
+ "text/ftp-dir",
+ "application/http-index-format",
+ new ChannelListener(checkData, null, CL_ALLOW_UNKNOWN_CL),
+ null
+ );
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.data = tests[0][0];
+
+ var url = NetUtil.newURI(URL);
+
+ var channel = {
+ URI: url,
+ contentLength: -1,
+ pending: true,
+ isPending() {
+ return this.pending;
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIChannel"]),
+ };
+
+ converter.onStartRequest(channel, null);
+ converter.onDataAvailable(channel, stream, 0, 0);
+ channel.pending = false;
+ converter.onStopRequest(channel, null, Cr.NS_OK);
+}
+
+function next_test() {
+ if (tests.length == 0) {
+ do_test_finished();
+ } else {
+ storeData();
+ }
+}
+
+function run_test() {
+ executeSoon(next_test);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug526789.js b/netwerk/test/unit/test_bug526789.js
new file mode 100644
index 0000000000..07ce2d18a3
--- /dev/null
+++ b/netwerk/test/unit/test_bug526789.js
@@ -0,0 +1,287 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+ var expiry = (Date.now() + 1000) * 1000;
+
+ cm.removeAll();
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ // 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 = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+ return cm.cookies.length;
+}
+
+async function testDomainCookie(uriString, domain) {
+ var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+
+ 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 = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+
+ cm.removeAll();
+
+ await CookieXPCShellUtils.setCookieToDocument(
+ uriString,
+ "foo=bar; domain=" + domain + "/"
+ );
+
+ Assert.equal(cm.countCookiesFromHost(domain), 0);
+ Assert.equal(cm.countCookiesFromHost(domain + "."), 0);
+ cm.removeAll();
+}
diff --git a/netwerk/test/unit/test_bug528292.js b/netwerk/test/unit/test_bug528292.js
new file mode 100644
index 0000000000..dada3ef6cf
--- /dev/null
+++ b/netwerk/test/unit/test_bug528292.js
@@ -0,0 +1,93 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+const sentCookieVal = "foo=bar";
+const responseBody = "response body";
+
+XPCOMUtils.defineLazyGetter(this, "baseURL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+const preRedirectPath = "/528292/pre-redirect";
+
+XPCOMUtils.defineLazyGetter(this, "preRedirectURL", function() {
+ return baseURL + preRedirectPath;
+});
+
+const postRedirectPath = "/528292/post-redirect";
+
+XPCOMUtils.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 (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .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.
+ Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch)
+ .setIntPref("network.cookie.cookieBehavior", 1);
+ Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch)
+ .setBoolPref("network.cookieJarSettings.unblocked_for_testing", true);
+ }
+
+ var ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+
+ // 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 = ioService.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..9f60d7ac00
--- /dev/null
+++ b/netwerk/test/unit/test_bug536324_64bit_content_length.js
@@ -0,0 +1,64 @@
+/* Test to ensure our 64-bit content length implementation works, at least for
+ a simple HTTP case */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+// 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_bug543805.js b/netwerk/test/unit/test_bug543805.js
new file mode 100644
index 0000000000..4546b81489
--- /dev/null
+++ b/netwerk/test/unit/test_bug543805.js
@@ -0,0 +1,136 @@
+"use strict";
+
+const URL = "ftp://localhost/bug543805/";
+
+var dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+var year = new Date().getFullYear().toString();
+var day = dayNames[new Date(year, 0, 1).getDay()];
+
+const tests = [
+ // AIX ls format
+ [
+ "-rw-r--r-- 1 0 11 Jan 1 20:19 nodup.file\r\n" +
+ "-rw-r--r-- 1 0 22 Jan 1 20:19 test.blankfile\r\n" +
+ "-rw-r--r-- 1 0 33 Apr 1 2008 test2.blankfile\r\n" +
+ "-rw-r--r-- 1 0 44 Jan 1 20:19 nodup.file\r\n" +
+ "-rw-r--r-- 1 0 55 Jan 1 20:19 test.file\r\n" +
+ "-rw-r--r-- 1 0 66 Apr 1 2008 test2.file\r\n",
+
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "%20nodup.file" 11 ' +
+ day +
+ "%2C%2001%20Jan%20" +
+ year +
+ "%2020%3A19%3A00%20GMT FILE \n" +
+ '201: "%20test.blankfile" 22 ' +
+ day +
+ "%2C%2001%20Jan%20" +
+ year +
+ "%2020%3A19%3A00%20GMT FILE \n" +
+ '201: "%20test2.blankfile" 33 Tue%2C%2001%20Apr%202008%2000%3A00%3A00%20GMT FILE \n' +
+ '201: "nodup.file" 44 ' +
+ day +
+ "%2C%2001%20Jan%20" +
+ year +
+ "%2020%3A19%3A00%20GMT FILE \n" +
+ '201: "test.file" 55 ' +
+ day +
+ "%2C%2001%20Jan%20" +
+ year +
+ "%2020%3A19%3A00%20GMT FILE \n" +
+ '201: "test2.file" 66 Tue%2C%2001%20Apr%202008%2000%3A00%3A00%20GMT FILE \n',
+ ],
+
+ // standard ls format
+ [
+ "-rw-r--r-- 1 500 500 11 Jan 1 20:19 nodup.file\r\n" +
+ "-rw-r--r-- 1 500 500 22 Jan 1 20:19 test.blankfile\r\n" +
+ "-rw-r--r-- 1 500 500 33 Apr 1 2008 test2.blankfile\r\n" +
+ "-rw-r--r-- 1 500 500 44 Jan 1 20:19 nodup.file\r\n" +
+ "-rw-r--r-- 1 500 500 55 Jan 1 20:19 test.file\r\n" +
+ "-rw-r--r-- 1 500 500 66 Apr 1 2008 test2.file\r\n",
+
+ "300: " +
+ URL +
+ "\n" +
+ "200: filename content-length last-modified file-type\n" +
+ '201: "%20nodup.file" 11 ' +
+ day +
+ "%2C%2001%20Jan%20" +
+ year +
+ "%2020%3A19%3A00%20GMT FILE \n" +
+ '201: "%20test.blankfile" 22 ' +
+ day +
+ "%2C%2001%20Jan%20" +
+ year +
+ "%2020%3A19%3A00%20GMT FILE \n" +
+ '201: "%20test2.blankfile" 33 Tue%2C%2001%20Apr%202008%2000%3A00%3A00%20GMT FILE \n' +
+ '201: "nodup.file" 44 ' +
+ day +
+ "%2C%2001%20Jan%20" +
+ year +
+ "%2020%3A19%3A00%20GMT FILE \n" +
+ '201: "test.file" 55 ' +
+ day +
+ "%2C%2001%20Jan%20" +
+ year +
+ "%2020%3A19%3A00%20GMT FILE \n" +
+ '201: "test2.file" 66 Tue%2C%2001%20Apr%202008%2000%3A00%3A00%20GMT FILE \n',
+ ],
+];
+
+function checkData(request, data, ctx) {
+ Assert.equal(tests[0][1], data);
+ tests.shift();
+ executeSoon(next_test);
+}
+
+function storeData(status, entry) {
+ var scs = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ var converter = scs.asyncConvertData(
+ "text/ftp-dir",
+ "application/http-index-format",
+ new ChannelListener(checkData, null, CL_ALLOW_UNKNOWN_CL),
+ null
+ );
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.data = tests[0][0];
+
+ var url = NetUtil.newURI(URL);
+
+ var channel = {
+ URI: url,
+ contentLength: -1,
+ pending: true,
+ isPending() {
+ return this.pending;
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIChannel"]),
+ };
+
+ converter.onStartRequest(channel, null);
+ converter.onDataAvailable(channel, stream, 0, 0);
+ channel.pending = false;
+ converter.onStopRequest(channel, null, Cr.NS_OK);
+}
+
+function next_test() {
+ if (tests.length == 0) {
+ do_test_finished();
+ } else {
+ storeData();
+ }
+}
+
+function run_test() {
+ executeSoon(next_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..95fcf6e9bc
--- /dev/null
+++ b/netwerk/test/unit/test_bug553970.js
@@ -0,0 +1,53 @@
+"use strict";
+
+function makeURL(spec) {
+ return Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ .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..cc78a40993
--- /dev/null
+++ b/netwerk/test/unit/test_bug561042.js
@@ -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/. */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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..03ff7b3e35
--- /dev/null
+++ b/netwerk/test/unit/test_bug561276.js
@@ -0,0 +1,64 @@
+//
+// Verify that we hit the net if we discover a cycle of redirects
+// coming from cache.
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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..8cc8a7b341
--- /dev/null
+++ b/netwerk/test/unit/test_bug580508.js
@@ -0,0 +1,33 @@
+"use strict";
+
+var ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+);
+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..a6b4b8a802
--- /dev/null
+++ b/netwerk/test/unit/test_bug586908.js
@@ -0,0 +1,104 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+const { MockRegistrar } = ChromeUtils.import(
+ "resource://testing-common/MockRegistrar.jsm"
+);
+
+var httpserv = null;
+
+const CID = Components.ID("{5645d2c1-d6d8-4091-b117-fe7ee4027db7}");
+XPCOMUtils.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
+ const prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ 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;
+ return pac(metadata, response);
+ }
+
+ 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..07e4bd760b
--- /dev/null
+++ b/netwerk/test/unit/test_bug596443.js
@@ -0,0 +1,115 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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..bc9de4e519
--- /dev/null
+++ b/netwerk/test/unit/test_bug618835.js
@@ -0,0 +1,122 @@
+//
+// 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.import("resource://testing-common/httpd.js");
+
+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..1df40c3a48
--- /dev/null
+++ b/netwerk/test/unit/test_bug633743.js
@@ -0,0 +1,193 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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 == 0) {
+ 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..b7ad89b1c1
--- /dev/null
+++ b/netwerk/test/unit/test_bug650522.js
@@ -0,0 +1,34 @@
+/* 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);
+
+ var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+ var expiry = (Date.now() + 1000) * 1000;
+
+ // Test our handling of host names with a single character at the beginning
+ // followed by a dot.
+ cm.add(
+ "e.com",
+ "/",
+ "foo",
+ "bar",
+ false,
+ false,
+ true,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTP
+ );
+ Assert.equal(cm.countCookiesFromHost("e.com"), 1);
+
+ CookieXPCShellUtils.createServer({ hosts: ["e.com"] });
+ const cookies = await CookieXPCShellUtils.getCookieStringFromDocument(
+ "http://e.com/"
+ );
+ Assert.equal(cookies, "foo=bar");
+});
diff --git a/netwerk/test/unit/test_bug650995.js b/netwerk/test/unit/test_bug650995.js
new file mode 100644
index 0000000000..73a004390a
--- /dev/null
+++ b/netwerk/test/unit/test_bug650995.js
@@ -0,0 +1,189 @@
+//
+// Test that "max_entry_size" prefs for disk- and memory-cache prevents
+// caching resources with size out of bounds
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+do_get_profile();
+
+const prefService = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+
+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() {
+ get_cache_service().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("browser.cache.offline.enable", false);
+ prefService.setBoolPref("browser.cache.offline.storage.enable", false);
+ 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..3a5660dfdb
--- /dev/null
+++ b/netwerk/test/unit/test_bug654926.js
@@ -0,0 +1,102 @@
+"use strict";
+
+var _PSvc;
+function get_pref_service() {
+ if (_PSvc) {
+ return _PSvc;
+ }
+
+ return (_PSvc = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ ));
+}
+
+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
+ get_pref_service().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..641b1d233e
--- /dev/null
+++ b/netwerk/test/unit/test_bug654926_doom_and_read.js
@@ -0,0 +1,90 @@
+"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 make_input_stream_scriptable(input) {
+ var wrapper = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ wrapper.init(input);
+ return wrapper;
+}
+
+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..60a14765b2
--- /dev/null
+++ b/netwerk/test/unit/test_bug659569.js
@@ -0,0 +1,58 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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..00abe8de84
--- /dev/null
+++ b/netwerk/test/unit/test_bug660066.js
@@ -0,0 +1,56 @@
+/* -*- 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..b3d87402e1
--- /dev/null
+++ b/netwerk/test/unit/test_bug667087.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+ var expiry = (Date.now() + 1000) * 1000;
+
+ // Test our handling of host names with a single character consisting only
+ // of a single character
+ cm.add(
+ "a",
+ "/",
+ "foo",
+ "bar",
+ false,
+ false,
+ true,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTP
+ );
+ Assert.equal(cm.countCookiesFromHost("a"), 1);
+
+ CookieXPCShellUtils.createServer({ hosts: ["a"] });
+ const cookies = await CookieXPCShellUtils.getCookieStringFromDocument(
+ "http://a/"
+ );
+ Assert.equal(cookies, "foo=bar");
+});
diff --git a/netwerk/test/unit/test_bug667818.js b/netwerk/test/unit/test_bug667818.js
new file mode 100644
index 0000000000..1ec185c832
--- /dev/null
+++ b/netwerk/test/unit/test_bug667818.js
@@ -0,0 +1,50 @@
+"use strict";
+
+function makeURI(str) {
+ return Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ .newURI(str);
+}
+
+add_task(async () => {
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ var serv = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+ 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
+ serv.setCookieStringFromHttp(
+ uri,
+ "test2=test2; path=/; domain=example.com;",
+ channel
+ );
+
+ Assert.equal(
+ await CookieXPCShellUtils.getCookieStringFromDocument(uri.spec),
+ "test2=test2"
+ );
+});
diff --git a/netwerk/test/unit/test_bug667907.js b/netwerk/test/unit/test_bug667907.js
new file mode 100644
index 0000000000..689dcd0886
--- /dev/null
+++ b/netwerk/test/unit/test_bug667907.js
@@ -0,0 +1,86 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpserver = null;
+var simplePath = "/simple";
+var normalPath = "/normal";
+var httpbody = "<html></html>";
+
+XPCOMUtils.defineLazyGetter(this, "uri1", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort + simplePath;
+});
+
+XPCOMUtils.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..bb16f0a5d7
--- /dev/null
+++ b/netwerk/test/unit/test_bug669001.js
@@ -0,0 +1,179 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpServer = null;
+var path = "/bug699001";
+
+XPCOMUtils.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 == 0) {
+ 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) {
+ var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ 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_bug767025.js b/netwerk/test/unit/test_bug767025.js
new file mode 100644
index 0000000000..cc10d85ae2
--- /dev/null
+++ b/netwerk/test/unit/test_bug767025.js
@@ -0,0 +1,315 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+const { PermissionTestUtils } = ChromeUtils.import(
+ "resource://testing-common/PermissionTestUtils.jsm"
+);
+
+/**
+ * This testcase does the following steps to make sure that bug767025 removes
+ * files as expected.
+ *
+ * STEPS:
+ * - Schedule a offline cache update for app.manifest.
+ * - pages/foo1, pages/foo2, pages/foo3, and pages/foo4 are cached.
+ * - Activate pages/foo1
+ * - Doom pages/foo1, and pages/foo2.
+ * - pages/foo1 should keep alive while pages/foo2 was gone.
+ * - Activate pages/foo3
+ * - Evict all documents.
+ * - all documents except pages/foo1 are gone since pages/foo1 & pages/foo3
+ * are activated.
+ */
+
+const kNS_OFFLINECACHEUPDATESERVICE_CONTRACTID =
+ "@mozilla.org/offlinecacheupdate-service;1";
+const kNS_CACHESTORAGESERVICE_CONTRACTID =
+ "@mozilla.org/netwerk/cache-storage-service;1";
+const kNS_APPLICATIONCACHESERVICE_CONTRACTID =
+ "@mozilla.org/network/application-cache-service;1";
+
+const kManifest =
+ "CACHE MANIFEST\n" +
+ "/pages/foo1\n" +
+ "/pages/foo2\n" +
+ "/pages/foo3\n" +
+ "/pages/foo4\n";
+
+const kDataFileSize = 1024; // file size for each content page
+const kHttpLocation = "http://localhost:4444/";
+
+function manifest_handler(metadata, response) {
+ info("manifest\n");
+ response.setHeader("content-type", "text/cache-manifest");
+
+ response.write(kManifest);
+}
+
+function datafile_handler(metadata, response) {
+ info("datafile_handler\n");
+ let data = "";
+
+ while (data.length < kDataFileSize) {
+ data =
+ data +
+ Math.random()
+ .toString(36)
+ .substring(2, 15);
+ }
+
+ response.setHeader("content-type", "text/plain");
+ response.write(data.substring(0, kDataFileSize));
+}
+
+function app_handler(metadata, response) {
+ info("app_handler\n");
+ response.setHeader("content-type", "text/html");
+
+ response.write("<html></html>");
+}
+
+var httpServer;
+
+function init_profile() {
+ var ps = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setBoolPref("browser.cache.offline.storage.enable", true);
+ ps.setComplexValue(
+ "browser.cache.offline.parent_directory",
+ Ci.nsIFile,
+ do_get_profile()
+ );
+ info("profile " + do_get_profile());
+}
+
+function init_http_server() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/app.appcache", manifest_handler);
+ httpServer.registerPathHandler("/app", app_handler);
+ for (let i = 1; i <= 4; i++) {
+ httpServer.registerPathHandler("/pages/foo" + i, datafile_handler);
+ }
+ httpServer.start(4444);
+}
+
+function clean_app_cache() {
+ let cache_service = Cc[kNS_CACHESTORAGESERVICE_CONTRACTID].getService(
+ Ci.nsICacheStorageService
+ );
+ let storage = cache_service.appCacheStorage(
+ Services.loadContextInfo.default,
+ null
+ );
+ storage.asyncEvictStorage(null);
+}
+
+function do_app_cache(manifestURL, pageURL) {
+ let update_service = Cc[kNS_OFFLINECACHEUPDATESERVICE_CONTRACTID].getService(
+ Ci.nsIOfflineCacheUpdateService
+ );
+
+ PermissionTestUtils.add(
+ manifestURL,
+ "offline-app",
+ Ci.nsIPermissionManager.ALLOW_ACTION
+ );
+
+ let update = update_service.scheduleUpdate(
+ manifestURL,
+ pageURL,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null
+ ); /* no window */
+
+ return update;
+}
+
+function watch_update(update, stateChangeHandler, cacheAvailHandler) {
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI([]),
+
+ updateStateChanged: stateChangeHandler,
+ applicationCacheAvailable: cacheAvailHandler,
+ };
+ ~update.addObserver(observer);
+
+ return update;
+}
+
+function start_and_watch_app_cache(
+ manifestURL,
+ pageURL,
+ stateChangeHandler,
+ cacheAvailHandler
+) {
+ let ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+ let update = do_app_cache(
+ ioService.newURI(manifestURL),
+ ioService.newURI(pageURL)
+ );
+ watch_update(update, stateChangeHandler, cacheAvailHandler);
+ return update;
+}
+
+const {
+ STATE_FINISHED: STATE_FINISHED,
+ STATE_CHECKING: STATE_CHECKING,
+ STATE_ERROR: STATE_ERROR,
+} = Ci.nsIOfflineCacheUpdateObserver;
+
+/*
+ * Start caching app1 as a non-pinned app.
+ */
+function start_cache_nonpinned_app() {
+ info("Start non-pinned App1");
+ start_and_watch_app_cache(
+ kHttpLocation + "app.appcache",
+ kHttpLocation + "app",
+ function(update, state) {
+ switch (state) {
+ case STATE_FINISHED:
+ check_bug();
+ break;
+
+ case STATE_ERROR:
+ do_throw("App cache state = " + state);
+ break;
+ }
+ },
+ function(appcache) {
+ info("app avail " + appcache + "\n");
+ }
+ );
+}
+
+var hold_entry_foo1 = null;
+
+function check_bug() {
+ // activate foo1
+ asyncOpenCacheEntry(
+ kHttpLocation + "pages/foo1",
+ "appcache",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ function(status, entry, appcache) {
+ let storage = get_cache_service().appCacheStorage(
+ Services.loadContextInfo.default,
+ appcache
+ );
+
+ // Doom foo1 & foo2
+ storage.asyncDoomURI(createURI(kHttpLocation + "pages/foo1"), "", {
+ onCacheEntryDoomed() {
+ storage.asyncDoomURI(createURI(kHttpLocation + "pages/foo2"), "", {
+ onCacheEntryDoomed() {
+ check_evict_cache(appcache);
+ },
+ });
+ },
+ });
+
+ hold_entry_foo1 = entry;
+ }
+ );
+}
+
+function check_evict_cache(appcache) {
+ // Only foo2 should be removed.
+ let file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("5");
+ file.append("9");
+ file.append("8379C6596B8CA4-0");
+ Assert.equal(file.exists(), true);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("C");
+ file.append("2");
+ file.append("5F356A168B5E3B-0");
+ Assert.equal(file.exists(), false);
+
+ // activate foo3
+ asyncOpenCacheEntry(
+ kHttpLocation + "pages/foo3",
+ "appcache",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ function(status, entry, appcache) {
+ // evict all documents.
+ let storage = get_cache_service().appCacheStorage(
+ Services.loadContextInfo.default,
+ appcache
+ );
+ storage.asyncEvictStorage(null);
+
+ // All documents are removed except foo1 & foo3.
+ syncWithCacheIOThread(function() {
+ // foo1
+ let file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("5");
+ file.append("9");
+ file.append("8379C6596B8CA4-0");
+ Assert.equal(file.exists(), true);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("0");
+ file.append("0");
+ file.append("61FEE819921D39-0");
+ Assert.equal(file.exists(), false);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("3");
+ file.append("9");
+ file.append("0D8759F1DE5452-0");
+ Assert.equal(file.exists(), false);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("C");
+ file.append("2");
+ file.append("5F356A168B5E3B-0");
+ Assert.equal(file.exists(), false);
+
+ // foo3
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("D");
+ file.append("C");
+ file.append("1ADCCC843B5C00-0");
+ Assert.equal(file.exists(), true);
+
+ file = do_get_profile().clone();
+ file.append("OfflineCache");
+ file.append("F");
+ file.append("0");
+ file.append("FC3E6D6C1164E9-0");
+ Assert.equal(file.exists(), false);
+
+ httpServer.stop(do_test_finished);
+ }, true /* force even with the new cache back end */);
+ },
+ appcache
+ );
+}
+
+function run_test() {
+ if (typeof _XPCSHELL_PROCESS == "undefined" || _XPCSHELL_PROCESS != "child") {
+ init_profile();
+ clean_app_cache();
+ }
+
+ init_http_server();
+ start_cache_nonpinned_app();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_bug770243.js b/netwerk/test/unit/test_bug770243.js
new file mode 100644
index 0000000000..5c79d6b1c1
--- /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.import("resource://testing-common/httpd.js");
+
+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 = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ 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..29bf894698
--- /dev/null
+++ b/netwerk/test/unit/test_bug812167.js
@@ -0,0 +1,141 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+/*
+- 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();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI1", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort + randomPath1;
+});
+
+var randomPath2 = "/redirect-expires-past/" + Math.random();
+
+XPCOMUtils.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(request, buffer) {
+ Assert.equal(buffer, 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..54e5d79a96
--- /dev/null
+++ b/netwerk/test/unit/test_bug826063.js
@@ -0,0 +1,91 @@
+/* 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"];
+if (Services.prefs.getBoolPref("network.ftp.enabled")) {
+ URIs.push("ftp://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..071366e66f
--- /dev/null
+++ b/netwerk/test/unit/test_bug856978.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/. */
+
+// 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.import("resource://testing-common/httpd.js");
+
+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);
+ Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService)
+ .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;
+
+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..a3d1b78c29
--- /dev/null
+++ b/netwerk/test/unit/test_bug894586.js
@@ -0,0 +1,157 @@
+/*
+ * 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";
+ },
+ get defaultPort() {
+ return -1;
+ },
+ get protocolFlags() {
+ return (
+ 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
+ );
+ },
+ 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,
+
+ /** nsIFactory */
+ createInstance(aOuter, aIID) {
+ if (aOuter) {
+ throw Components.Exception(
+ "createInstance no aggregation",
+ Cr.NS_ERROR_NO_AGGREGATION
+ );
+ }
+ return this.QueryInterface(aIID);
+ },
+ lockFactory() {},
+
+ /** nsISupports */
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIProtocolHandler",
+ "nsIRequest",
+ "nsIChannel",
+ "nsIFactory",
+ ]),
+ classID: Components.ID("{16d594bc-d9d8-47ae-a139-ea714dc0c35c}"),
+};
+
+/**
+ * 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();
+ var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(
+ handler.classID,
+ "",
+ "@mozilla.org/network/protocol;1?name=" + handler.scheme,
+ handler
+ );
+ 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 {
+ registrar.unregisterFactory(handler.classID, handler);
+ }
+}
+
+// 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..501fa7ae87
--- /dev/null
+++ b/netwerk/test/unit/test_cache-control_request.js
@@ -0,0 +1,447 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.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..121c30ced4
--- /dev/null
+++ b/netwerk/test/unit/test_cache-entry-id.js
@@ -0,0 +1,215 @@
+/**
+ * Test for the "CacheEntryId" under several cases.
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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 ||
+ appInfo.getService(Ci.nsIXULRuntime).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, "", true);
+ }
+
+ 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..7286041111
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-00-service-get.js
@@ -0,0 +1,19 @@
+"use strict";
+
+function run_test() {
+ // Just check the contract ID alias works well.
+ try {
+ var serviceA = Cc[
+ "@mozilla.org/netwerk/cache-storage-service;1"
+ ].getService(Ci.nsICacheStorageService);
+ Assert.ok(serviceA);
+ var serviceB = Cc[
+ "@mozilla.org/network/cache-storage-service;1"
+ ].getService(Ci.nsICacheStorageService);
+ 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..ee7da95c17
--- /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(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ // Open for rewrite (truncate), write different meta and data
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ null,
+ new OpenCallback(NEW, "a2m", "a2d", function(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a2m", "a2d", function(entry) {
+ 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..32bed8a69e
--- /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(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://ro/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ // Open for rewrite (truncate), write different meta and data
+ asyncOpenCacheEntry(
+ "http://ro/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ null,
+ new OpenCallback(NEW, "a2m", "a2d", function(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://ro/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_READONLY,
+ null,
+ new OpenCallback(NORMAL, "a2m", "a2d", function(entry) {
+ 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..9c41e114c3
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-01b-basic-datasize.js
@@ -0,0 +1,49 @@
+"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(entry) {
+ // Open for read and check
+ Assert.equal(entry.dataSize, 3);
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ // Open for rewrite (truncate), write different meta and data
+ Assert.equal(entry.dataSize, 3);
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ null,
+ new OpenCallback(NEW | WAITFORWRITE, "a2m", "a2d", function(entry) {
+ // Open for read and check
+ Assert.equal(entry.dataSize, 3);
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a2m", "a2d", function(entry) {
+ Assert.equal(entry.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..7c97c5ced5
--- /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(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://mt/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "", function(entry) {
+ // Open for rewrite (truncate), write different meta and data
+ asyncOpenCacheEntry(
+ "http://mt/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_TRUNCATE,
+ null,
+ new OpenCallback(NEW, "a2m", "a2d", function(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://mt/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a2m", "a2d", function(entry) {
+ 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..faed1c9d69
--- /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(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ // Open but don't want the entry
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NOTWANTED, "a1m", "a1d", function(entry) {
+ // 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(entry) {
+ 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..a151f2e752
--- /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(entry) {
+ var bypassed = false;
+
+ // Open and bypass
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_BYPASS_IF_BUSY,
+ null,
+ new OpenCallback(NOTFOUND, "", "", function(entry) {
+ 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..2ee2bf85b8
--- /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, null, 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..8c00e2f632
--- /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(entry) {
+ // 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) {
+ // Try it again normally, should go
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "b1m", "b1d", function(entry) {
+ // ...and check
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "b1m", "b1d", function(entry) {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
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..f506b1165e
--- /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(entry) {
+ // Try it again, should go
+ asyncOpenCacheEntry(
+ "http://c/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "c1m", "c1d", function(entry) {
+ // ...and check
+ asyncOpenCacheEntry(
+ "http://c/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(false, "c1m", "c1d", function(entry) {
+ 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..164ec4c1ed
--- /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(entry) {
+ // Open but let OCEA throw ones again
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW | THROWAVAIL, null, null, function(entry) {
+ // Try it again, should go
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "d1m", "d1d", function(entry) {
+ // ...and check
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "d1m", "d1d", function(entry) {
+ 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..ecf3f8061e
--- /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(entry) {
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "b1m", "b1d", function(entry) {
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "b1m", "b1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://c/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "c1m", "c1d", function(entry) {
+ asyncOpenCacheEntry(
+ "http://c/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "c1m", "c1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "d1m", "d1d", function(entry) {
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "d1m", "d1d", function(entry) {
+ 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..1eebb84543
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-06-pb-mode.js
@@ -0,0 +1,53 @@
+"use strict";
+
+function exitPB() {
+ var obsvc = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ obsvc.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(entry) {
+ asyncOpenCacheEntry(
+ "http://p1/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.private,
+ new OpenCallback(NORMAL, "p1m", "p1d", function(entry) {
+ // 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..79065cab19
--- /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(entry) {
+ asyncOpenCacheEntry(
+ "http://mem1/",
+ "memory",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "m1m", "m1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://c/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry(
+ "http://c/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry(
+ "http://d/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ 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..6944064697
--- /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..7e62c45030
--- /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..dd444f82fb
--- /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(entry) {
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ 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..f9f32a9ab2
--- /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(entry) {
+ 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..688b1f1e00
--- /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..5868a29552
--- /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 storage = getCacheStorage("memory");
+ var mc = new MultipleCallbacks(3, function() {
+ storage.asyncEvictStorage(
+ new EvictionCallback(true, function() {
+ storage.asyncVisitStorage(
+ new VisitCallback(0, 0, [], function() {
+ var storage = getCacheStorage("disk");
+
+ var expectedConsumption = 2048;
+
+ storage.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(entry) {
+ asyncOpenCacheEntry(
+ "http://mem1/",
+ "memory",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "m2m", "m2d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ 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..5c40ba94bf
--- /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 storage = getCacheStorage("disk");
+ storage.asyncEvictStorage(
+ new EvictionCallback(true, function() {
+ storage.asyncVisitStorage(
+ new VisitCallback(0, 0, [], function() {
+ var storage = getCacheStorage("memory");
+ storage.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(entry) {
+ asyncOpenCacheEntry(
+ "http://mem1/",
+ "memory",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "m2m", "m2d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "a1m", "a1d", function(entry) {
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "b1m", "b1d", function(entry) {
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "b1m", "b1d", function(entry) {
+ 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..9c25a48d7b
--- /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..03aded21b0
--- /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..cb00a4a8cd
--- /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..b7271996b9
--- /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(entry) {
+ // 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..60b38cd2e1
--- /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(entry) {
+ asyncOpenCacheEntry(
+ "http://200/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "21m", "21d", function(entry) {
+ // 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, null, 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..48ca1c15ef
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-17-evict-all.js
@@ -0,0 +1,18 @@
+"use strict";
+
+function run_test() {
+ do_get_profile();
+
+ var svc = get_cache_service();
+ svc.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..82bafb5ea9
--- /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(entry) {
+ // 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(entry) {
+ // And check...
+ asyncOpenCacheEntry(
+ "http://nv/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "v2m", "v2d", function(entry) {
+ 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..1baddebbd6
--- /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(entry) {
+ // 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(entry) {
+ entry.setValid();
+ }
+ ).onCacheEntryAvailable(entry, true, null, 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..01ed41d2e7
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-20-range-200.js
@@ -0,0 +1,66 @@
+"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(entry) {
+ // 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(entry) {
+ entry.setValid();
+ }
+ ).onCacheEntryAvailable(entry, true, null, 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..3e2b195d5f
--- /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(entry) {
+ asyncOpenCacheEntry(
+ "http://anon1/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.anonymous,
+ new OpenCallback(NORMAL, "an1", "an1", function(entry) {
+ // 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(entry) {
+ asyncOpenCacheEntry(
+ "http://anon1/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.default,
+ new OpenCallback(NORMAL, "na1", "na1", function(entry) {
+ // 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(entry) {
+ 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..b9bc961cfa
--- /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..81871224a7
--- /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..67582594cc
--- /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..c1c82c4141
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-25-chunk-memory-limit.js
@@ -0,0 +1,57 @@
+"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();
+
+ var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+
+ // set max chunks memory so that only one full chunk fits within the limit
+ prefBranch.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(status, entry) {
+ Assert.equal(status, Cr.NS_OK);
+ var oStr2 = entry.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..378e41b5db
--- /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(entry) {
+ // 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(entry) {
+ 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..223fbd6057
--- /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..50c6b99938
--- /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(entry) {
+ var secondOpen = NowSeconds();
+ Assert.equal(entry.fetchCount, 2);
+ do_check_time(entry.lastFetched, firstOpen, secondOpen);
+ do_check_time(entry.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..7ed078786a
--- /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(entry) {
+ Assert.equal(entry.fetchCount, 1);
+ do_check_time(entry.lastFetched, now1);
+ do_check_time(entry.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..a686aaa046
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29a-concurrent_read_resumable_entry_size_zero.js
@@ -0,0 +1,74 @@
+/*
+
+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.import("resource://testing-common/httpd.js");
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+XPCOMUtils.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..e0da2202c3
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29b-concurrent_read_non-resumable_entry_size_zero.js
@@ -0,0 +1,78 @@
+/*
+
+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.import("resource://testing-common/httpd.js");
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+XPCOMUtils.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..b76db5c39b
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29c-concurrent_read_half-interrupted.js
@@ -0,0 +1,93 @@
+/*
+
+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.import("resource://testing-common/httpd.js");
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+XPCOMUtils.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..fe64d93007
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29d-concurrent_read_half-corrupted-206.js
@@ -0,0 +1,93 @@
+/*
+
+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.import("resource://testing-common/httpd.js");
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+XPCOMUtils.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..45e67e63a3
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-29e-concurrent_read_half-non-206-response.js
@@ -0,0 +1,88 @@
+/*
+
+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.import("resource://testing-common/httpd.js");
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+XPCOMUtils.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..f5ef4e69f3
--- /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(entry) {
+ // Open for read and check
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.default,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ // Now clear the whole cache
+ get_cache_service().clear();
+
+ // The pinned entry should be intact
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ Services.loadContextInfo.default,
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ 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..68dcd995cc
--- /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(entry) {
+ // 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(entry) {
+ // 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(entry) {
+ 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..bb5ed79b85
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30c-pinning-deferred-doom.js
@@ -0,0 +1,188 @@
+/*
+
+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 = get_cache_service().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.
+ get_cache_service().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)
+
+ var os = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ os.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
+ get_cache_service().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..74272072d2
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-30d-pinning-WasEvicted-API.js
@@ -0,0 +1,151 @@
+/*
+
+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 = get_cache_service().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.
+ get_cache_service().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)
+
+ var os = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ os.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.
+ get_cache_service().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..b9f1b7eec7
--- /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
+
+ get_cache_service().asyncVisitAllStorages(
+ // Test should store 8 entries across 4 originAttributes
+ new VisitCallback(8, expectedConsumption, entries, function() {
+ get_cache_service().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(entry) {
+ asyncOpenCacheEntry(
+ "http://a/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lcis[i],
+ new OpenCallback(NORMAL, "a1m", "a1d", function(entry) {
+ mc.fired();
+ })
+ );
+ })
+ );
+
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lcis[i],
+ new OpenCallback(NEW, "b1m", "b1d", function(entry) {
+ asyncOpenCacheEntry(
+ "http://b/",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ lcis[i],
+ new OpenCallback(NORMAL, "b1m", "b1d", function(entry) {
+ 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..52987a576f
--- /dev/null
+++ b/netwerk/test/unit/test_cache2-32-clear-origin.js
@@ -0,0 +1,68 @@
+"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(entry) {
+ asyncOpenCacheEntry(
+ URL + "/a",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "e1m", "e1d", function(entry) {
+ asyncOpenCacheEntry(
+ URL2 + "/a",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "f1m", "f1d", function(entry) {
+ asyncOpenCacheEntry(
+ URL2 + "/a",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "f1m", "f1d", function(entry) {
+ var url = Services.io.newURI(URL);
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ url,
+ {}
+ );
+
+ get_cache_service().clearOrigin(principal);
+
+ asyncOpenCacheEntry(
+ URL + "/a",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NEW, "e1m", "e1d", function(entry) {
+ asyncOpenCacheEntry(
+ URL2 + "/a",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ new OpenCallback(NORMAL, "f1m", "f1d", function(entry) {
+ finish_cache2_test();
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+ })
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_cacheForOfflineUse_no-store.js b/netwerk/test/unit/test_cacheForOfflineUse_no-store.js
new file mode 100644
index 0000000000..9d8c14d657
--- /dev/null
+++ b/netwerk/test/unit/test_cacheForOfflineUse_no-store.js
@@ -0,0 +1,104 @@
+"use strict";
+// https://bugzilla.mozilla.org/show_bug.cgi?id=760955
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpServer = null;
+const testFileName = "test_nsHttpChannel_CacheForOfflineUse-no-store";
+const cacheClientID = testFileName + "|fake-group-id";
+const basePath = "/" + testFileName + "/";
+
+XPCOMUtils.defineLazyGetter(this, "baseURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + basePath;
+});
+
+const normalEntry = "normal";
+const noStoreEntry = "no-store";
+
+var cacheUpdateObserver = null;
+var appCache = null;
+
+function make_channel_for_offline_use(url, callback, ctx) {
+ var chan = NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+
+ var cacheService = Cc[
+ "@mozilla.org/network/application-cache-service;1"
+ ].getService(Ci.nsIApplicationCacheService);
+ appCache = cacheService.getApplicationCache(cacheClientID);
+
+ var appCacheChan = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ appCacheChan.applicationCacheForWrite = appCache;
+ return chan;
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.newURI(url);
+}
+
+const responseBody = "response body";
+
+// A HTTP channel for updating the offline cache should normally succeed.
+function normalHandler(metadata, response) {
+ info("normalHandler");
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+function checkNormal(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ asyncCheckCacheEntryPresence(
+ baseURI + normalEntry,
+ "appcache",
+ true,
+ run_next_test,
+ appCache
+ );
+}
+add_test(function test_normal() {
+ var chan = make_channel_for_offline_use(baseURI + normalEntry);
+ chan.asyncOpen(new ChannelListener(checkNormal, chan));
+});
+
+// An HTTP channel for updating the offline cache should fail when it gets a
+// response with Cache-Control: no-store.
+function noStoreHandler(metadata, response) {
+ info("noStoreHandler");
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "no-store");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+function checkNoStore(request, buffer) {
+ Assert.equal(buffer, "");
+ asyncCheckCacheEntryPresence(
+ baseURI + noStoreEntry,
+ "appcache",
+ false,
+ run_next_test,
+ appCache
+ );
+}
+add_test(function test_noStore() {
+ var chan = make_channel_for_offline_use(baseURI + noStoreEntry);
+ // The no-store should cause the channel to fail to load.
+ chan.asyncOpen(new ChannelListener(checkNoStore, chan, CL_EXPECT_FAILURE));
+});
+
+function run_test() {
+ do_get_profile();
+
+ var ps = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setBoolPref("browser.cache.offline.storage.enable", true);
+
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler(basePath + normalEntry, normalHandler);
+ httpServer.registerPathHandler(basePath + noStoreEntry, noStoreHandler);
+ httpServer.start(-1);
+ run_next_test();
+}
+
+function finish_test(request, buffer) {
+ httpServer.stop(do_test_finished);
+}
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..aeb4f4f1d2
--- /dev/null
+++ b/netwerk/test/unit/test_cache_204_response.js
@@ -0,0 +1,60 @@
+/* -*- 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.import("resource://testing-common/httpd.js");
+
+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..554759be21
--- /dev/null
+++ b/netwerk/test/unit/test_cache_jar.js
@@ -0,0 +1,104 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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 = Cc["@mozilla.org/xre/runtime;1"].getService(Ci.nsIXULRuntime)
+ .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..4c0ae215f5
--- /dev/null
+++ b/netwerk/test/unit/test_cacheflags.js
@@ -0,0 +1,436 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+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 httpsBase = "http://localhost:4445";
+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 == 0) {
+ httpserver.stop(do_test_finished);
+ return;
+ }
+
+ var test = gTests.shift();
+ test.run();
+}
+
+function handler(httpStatus, metadata, response) {
+ gHitServer = true;
+ try {
+ var etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {
+ var 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..55ca0874b9
--- /dev/null
+++ b/netwerk/test/unit/test_captive_portal_service.js
@@ -0,0 +1,207 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+let httpserver = null;
+XPCOMUtils.defineLazyGetter(this, "cpURI", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort + "/captive.txt";
+});
+
+const SUCCESS_STRING = "success\n";
+let cpResponse = SUCCESS_STRING;
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ 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.txt", 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/plain");
+ response.bodyOutputStream.write(cpResponse, cpResponse.length);
+ });
+ httpserver.registerPathHandler("/captive.txt", (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/plain");
+ response.bodyOutputStream.write("bad", "bad".length);
+ });
+
+ httpserver.registerPathHandler("/captive.txt", (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.txt", (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 = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment)
+ .get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ // Any kind of redirection should trigger the captive portal login.
+ httpserver.registerPathHandler("/captive.txt", (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);
+});
diff --git a/netwerk/test/unit/test_channel_close.js b/netwerk/test/unit/test_channel_close.js
new file mode 100644
index 0000000000..e05f0e9bb2
--- /dev/null
+++ b/netwerk/test/unit/test_channel_close.js
@@ -0,0 +1,68 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpProtocolHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=http"
+].getService(Ci.nsIHttpProtocolHandler);
+
+XPCOMUtils.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_priority.js b/netwerk/test/unit/test_channel_priority.js
new file mode 100644
index 0000000000..2a757de90a
--- /dev/null
+++ b/netwerk/test/unit/test_channel_priority.js
@@ -0,0 +1,97 @@
+/* -*- 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";
+
+/* globals NetUtil*/
+/* globals HttpServer */
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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
+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..ea0149e5c5
--- /dev/null
+++ b/netwerk/test/unit/test_chunked_responses.js
@@ -0,0 +1,172 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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.import("resource://testing-common/httpd.js");
+
+Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
+});
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var index = 0;
+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;
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+ var flags = test_flags[num]; // OK if flags undefined for test
+ channel.asyncOpen(
+ 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;
+
+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();
+}
+
+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;
+
+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();
+}
+
+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;
+
+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();
+}
+
+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;
+
+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();
+}
+
+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;
+
+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();
+}
+
+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_compareURIs.js b/netwerk/test/unit/test_compareURIs.js
new file mode 100644
index 0000000000..c2899b33bc
--- /dev/null
+++ b/netwerk/test/unit/test_compareURIs.js
@@ -0,0 +1,60 @@
+"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],
+ ];
+
+ var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+
+ 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 {
+ secman.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..1374eb01ab
--- /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_content_encoding_gzip.js b/netwerk/test/unit/test_content_encoding_gzip.js
new file mode 100644
index 0000000000..a3fb41e76c
--- /dev/null
+++ b/netwerk/test/unit/test_content_encoding_gzip.js
@@ -0,0 +1,210 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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 not a brotli document
+ {
+ url: "/test/cebrotli2",
+ flags: CL_EXPECT_GZIP | CL_EXPECT_FAILURE,
+ ce: "br",
+ body: [0x0b, 0x0a, 0x09],
+ datalen: 3,
+ },
+
+ // 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,
+ },
+];
+
+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");
+ }
+ 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);
+ }
+ 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ cePref = prefs.getCharPref("network.http.accept-encoding");
+ prefs.setCharPref("network.http.accept-encoding", "gzip, deflate, br");
+
+ 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.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..b651d36065
--- /dev/null
+++ b/netwerk/test/unit/test_content_length_underrun.js
@@ -0,0 +1,280 @@
+/*
+ * Test Content-Length underrun behavior
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+// Test infrastructure
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var index = 0;
+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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ 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;
+ httpserver.registerPathHandler(testPath, eval("handler" + num));
+
+ var channel = setupChannel(testPath);
+ let flags = test_flags[num]; // OK if flags undefined for test
+ channel.asyncOpen(
+ new ChannelListener(eval("completeTest" + num), channel, flags)
+ );
+}
+
+function run_gzip_test(num) {
+ let testPath = testPathBase + num;
+ 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;
+
+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();
+}
+
+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;
+
+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();
+}
+
+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;
+
+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();
+}
+
+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;
+
+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();
+}
+
+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;
+
+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();
+}
+
+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.
+
+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..770eb1922b
--- /dev/null
+++ b/netwerk/test/unit/test_content_sniffer.js
@@ -0,0 +1,163 @@
+// This file tests nsIContentSniffer, introduced in bug 324985
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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(outer, iid) {
+ if (outer) {
+ throw Components.Exception("", Cr.NS_ERROR_NO_AGGREGATION);
+ }
+ return this.QueryInterface(iid);
+ },
+ lockFactory: function sniffer_lockf(lock) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+
+ 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 = Cc["@mozilla.org/categorymanager;1"].getService(
+ Ci.nsICategoryManager
+ );
+ 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..3f81ae93e9
--- /dev/null
+++ b/netwerk/test/unit/test_cookie_blacklist.js
@@ -0,0 +1,39 @@
+"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
+ );
+
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ var cookieURI = ios.newURI("http://mozilla.org/test_cookie_blacklist.js");
+ const channel = NetUtil.newChannel({
+ uri: cookieURI,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ var cookieService = Cc["@mozilla.org/cookieService;1"].getService(
+ Ci.nsICookieService
+ );
+ cookieService.setCookieStringFromHttp(cookieURI, "BadCookie1=\x01", channel);
+ cookieService.setCookieStringFromHttp(cookieURI, "BadCookie2=\v", channel);
+ cookieService.setCookieStringFromHttp(
+ cookieURI,
+ "Bad\x07Name=illegal",
+ channel
+ );
+ cookieService.setCookieStringFromHttp(cookieURI, GOOD_COOKIE, channel);
+ cookieService.setCookieStringFromHttp(cookieURI, SPACEY_COOKIE, channel);
+
+ CookieXPCShellUtils.createServer({ hosts: ["mozilla.org"] });
+
+ const storedCookie = await CookieXPCShellUtils.getCookieStringFromDocument(
+ cookieURI.spec
+ );
+ Assert.equal(storedCookie, GOOD_COOKIE + "; " + SPACEY_COOKIE);
+});
diff --git a/netwerk/test/unit/test_cookie_header.js b/netwerk/test/unit/test_cookie_header.js
new file mode 100644
index 0000000000..82074196fc
--- /dev/null
+++ b/netwerk/test/unit/test_cookie_header.js
@@ -0,0 +1,113 @@
+// This file tests bug 250375
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserv.identity.primaryPort + "/";
+});
+
+function inChildProcess() {
+ return (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .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..70bb360e0f
--- /dev/null
+++ b/netwerk/test/unit/test_cookie_ipv6.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/. */
+
+/*
+ * Test that channels with different LoadInfo
+ * are stored in separate namespaces ("cookie jars")
+ */
+
+"use strict";
+
+let ip = "[::1]";
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return `http://${ip}:${httpserver.identity.primaryPort}/`;
+});
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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));
+ });
+ var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+ equal(cm.cookies.length, 1);
+});
diff --git a/netwerk/test/unit/test_cookiejars.js b/netwerk/test/unit/test_cookiejars.js
new file mode 100644
index 0000000000..5a421ec3f6
--- /dev/null
+++ b/netwerk/test/unit/test_cookiejars.js
@@ -0,0 +1,172 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpserver = new HttpServer();
+
+var cookieSetPath = "/setcookie";
+var cookieCheckPath = "/checkcookie";
+
+function inChildProcess() {
+ return (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .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);
+ 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..cafecdf7fe
--- /dev/null
+++ b/netwerk/test/unit/test_cookiejars_safebrowsing.js
@@ -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/. */
+
+/*
+ * 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.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "SafeBrowsing",
+ "resource://gre/modules/SafeBrowsing.jsm"
+);
+
+var setCookiePath = "/setcookie";
+var checkCookiePath = "/checkcookie";
+var safebrowsingUpdatePath = "/safebrowsingUpdate";
+var safebrowsingGethashPath = "/safebrowsingGethash";
+var httpserver;
+
+function inChildProcess() {
+ return (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .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..c7a455cb53
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_async_failure.js
@@ -0,0 +1,508 @@
+/* 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();
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ // 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(profile).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();
+});
+
+function do_get_backup_file(profile) {
+ let file = profile.clone();
+ file.append("cookies.sqlite.bak");
+ return file;
+}
+
+function do_get_rebuild_backup_file(profile) {
+ 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.cookiemgr.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.cookiemgr.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.cookiemgr.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.cookiemgr.countCookiesFromHost("foo.com"), 1);
+ Assert.equal(Services.cookiemgr.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(profile).exists());
+ let backupdb = Services.storage.openDatabase(do_get_backup_file(profile));
+ 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.cookiemgr.countCookiesFromHost("foo.com"), 1);
+ let cookies = Services.cookiemgr.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(profile).remove(false);
+ Assert.ok(!do_get_cookie_file(profile).exists());
+ Assert.ok(!do_get_backup_file(profile).exists());
+}
+
+async function run_test_2() {
+ // Load the profile and populate it.
+ do_load_profile();
+
+ Services.cookiesvc.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) {
+ let uri = NetUtil.newURI("http://" + i + ".com/");
+ Services.cookiesvc.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(profile).exists());
+
+ // Recreate a new database since it was corrupted
+ Assert.equal(Services.cookiemgr.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(profile).exists());
+ Assert.equal(do_get_backup_file(profile).fileSize, size);
+ let db = Services.storage.openDatabase(do_get_cookie_file(profile));
+ db.close();
+
+ do_load_profile();
+ Assert.equal(Services.cookiemgr.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(profile).remove(false);
+ Assert.ok(!do_get_cookie_file(profile).exists());
+ Assert.ok(!do_get_backup_file(profile).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.cookiesvc.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.cookiesvc.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.cookiesvc.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(profile).exists());
+
+ // Recreate a new database since it was corrupted
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("hither.com"), 0);
+ Assert.equal(Services.cookiemgr.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(profile).exists());
+ Assert.equal(do_get_backup_file(profile).fileSize, size);
+
+ // Rename it back, and try loading the entire database synchronously.
+ do_get_backup_file(profile).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(profile).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(profile).exists());
+ Assert.equal(do_get_backup_file(profile).fileSize, size);
+
+ // Clean up.
+ do_get_cookie_file(profile).remove(false);
+ do_get_backup_file(profile).remove(false);
+ Assert.ok(!do_get_cookie_file(profile).exists());
+ Assert.ok(!do_get_backup_file(profile).exists());
+}
+
+async function run_test_4() {
+ // Load the profile and populate it.
+ do_load_profile();
+ Services.cookiesvc.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) {
+ let uri = NetUtil.newURI("http://" + i + ".com/");
+ Services.cookiesvc.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(profile).exists());
+
+ // Recreate a new database since it was corrupted
+ Assert.equal(Services.cookiemgr.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.cookiemgr.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(profile).exists());
+ Assert.equal(do_get_backup_file(profile).fileSize, size);
+
+ // Load the profile, and check that it contains the new cookie.
+ do_load_profile();
+ Assert.equal(Services.cookiemgr.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(profile).remove(false);
+ Assert.ok(!do_get_cookie_file(profile).exists());
+ Assert.ok(!do_get_backup_file(profile).exists());
+}
+
+async function run_test_5() {
+ // Load the profile and populate it.
+ do_load_profile();
+ Services.cookiesvc.runInTransaction(_ => {
+ let uri = NetUtil.newURI("http://bar.com/");
+ const channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+ Services.cookiesvc.setCookieStringFromHttp(
+ uri,
+ "oh=hai; path=/; max-age=1000",
+ channel
+ );
+ for (let i = 0; i < 3000; ++i) {
+ let uri = NetUtil.newURI("http://" + i + ".com/");
+ Services.cookiesvc.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(profile).exists());
+
+ // Recreate a new database since it was corrupted
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("bar.com"), 0);
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("0.com"), 0);
+ Assert.equal(do_count_cookies(), 0);
+ Assert.ok(do_get_backup_file(profile).exists());
+ Assert.equal(do_get_backup_file(profile).fileSize, size);
+ Assert.ok(!do_get_rebuild_backup_file(profile).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(profile).exists());
+ Assert.equal(do_get_backup_file(profile).fileSize, size);
+
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("bar.com"), 0);
+ Assert.equal(Services.cookiemgr.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(profile).remove(false);
+ Assert.ok(!do_get_cookie_file(profile).exists());
+ Assert.ok(!do_get_backup_file(profile).exists());
+}
diff --git a/netwerk/test/unit/test_cookies_persistence.js b/netwerk/test/unit/test_cookies_persistence.js
new file mode 100644
index 0000000000..a155bb5305
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_persistence.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// test for cookie persistence across sessions, for the cases:
+// 1) network.cookie.lifetimePolicy = 0 (expire naturally)
+// 2) network.cookie.lifetimePolicy = 2 (expire at end of session)
+
+"use strict";
+
+add_task(async () => {
+ // Set up a profile.
+ do_get_profile();
+
+ 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 = channel1.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);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ await do_set_cookies(uri1, channel1, false, [1, 2]);
+ await do_set_cookies(uri2, channel2, 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);
+
+ // Again, but don't wait for the async close to complete. This should always
+ // work, since we blocked on close above and haven't kicked off any writes
+ // since then.
+ 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 cookies set to session-only
+ Services.prefs.setIntPref("network.cookie.lifetimePolicy", 2);
+ Services.cookies.removeAll();
+ await do_set_cookies(uri1, channel1, false, [1, 2]);
+ await do_set_cookies(uri2, channel2, 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);
+});
diff --git a/netwerk/test/unit/test_cookies_privatebrowsing.js b/netwerk/test/unit/test_cookies_privatebrowsing.js
new file mode 100644
index 0000000000..e594fdf3f9
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_privatebrowsing.js
@@ -0,0 +1,139 @@
+/* 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);
+
+ 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.cookiesvc.setCookieStringFromHttp(
+ uri1,
+ "oh=hai; max-age=1000",
+ make_channel(uri1.spec)
+ );
+ Assert.equal(Services.cookiemgr.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.cookiesvc.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.cookiesvc.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.cookiemgr.countCookiesFromHost(uri1.host), 1);
+ Assert.equal(Services.cookiemgr.countCookiesFromHost(uri2.host), 0);
+
+ // Fake a profile change.
+ await promise_close_profile();
+ do_load_profile();
+
+ // Check that the right cookie persisted.
+ Assert.equal(Services.cookiemgr.countCookiesFromHost(uri1.host), 1);
+ Assert.equal(Services.cookiemgr.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.cookiesvc.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.cookiemgr.countCookiesFromHost(uri1.host), 1);
+ Assert.equal(Services.cookiemgr.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.cookiemgr.countCookiesFromHost(uri1.host), 1);
+ Assert.equal(Services.cookiemgr.countCookiesFromHost(uri2.host), 0);
+
+ // Let's release the last PB window.
+ privateBrowsingHolder.close();
+});
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..f9cff71f4d
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_profile_close.js
@@ -0,0 +1,112 @@
+/* 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
+ );
+
+ // 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.cookiemgr.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.cookiesvc.getCookieStringFromHttp(uri, channel), "");
+
+ await CookieXPCShellUtils.setCookieToDocument(uri.spec, "oh2=hai");
+
+ Services.cookiesvc.setCookieStringFromHttp(uri, "oh3=hai", channel);
+ Assert.equal(
+ await CookieXPCShellUtils.getCookieStringFromDocument("http://foo.com/"),
+ ""
+ );
+
+ do_check_throws(function() {
+ Services.cookiemgr.removeAll();
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function() {
+ Services.cookiemgr.cookies;
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function() {
+ Services.cookiemgr.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.cookiemgr.remove("foo.com", "", "oh4", {});
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function() {
+ Services.cookiemgr.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.cookiemgr.cookieExists(cookie.host, cookie.path, cookie.name, {})
+ );
+});
diff --git a/netwerk/test/unit/test_cookies_read.js b/netwerk/test/unit/test_cookies_read.js
new file mode 100644
index 0000000000..614862e3f5
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_read.js
@@ -0,0 +1,114 @@
+/* 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
+ );
+
+ // Start the cookieservice, to force creation of a database.
+ // Get the sessionCookies to join the initialization in cookie thread
+ Services.cookiemgr.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) {
+ let uri = NetUtil.newURI("http://" + i + ".com/");
+ Services.cookiesvc.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.cookiemgr.countCookiesFromHost("999.com"), 1);
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("abc.com"), 0);
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("100.com"), 1);
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("400.com"), 1);
+ Assert.equal(Services.cookiemgr.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.cookiemgr.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.cookiemgr.remove(host, "oh", "/", {});
+ }
+ for (let i = CMAX - 100; i < CMAX; ++i) {
+ let host = i.toString() + ".com";
+ Services.cookiemgr.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.cookiemgr.countCookiesFromHost(host), 1);
+ }
+});
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..f63955b483
--- /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 = 12;
+
+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.cookiesvc.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.cookiesvc.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..413fd1e2e4
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_thirdparty.js
@@ -0,0 +1,162 @@
+/* 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(
+ "network.cookie.rejectForeignWithExceptions.enabled",
+ 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();
+ }
+});
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..a846689790
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_thirdparty_session.js
@@ -0,0 +1,71 @@
+/* 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
+ );
+
+ 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);
+});
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..61dbcda46e
--- /dev/null
+++ b/netwerk/test/unit/test_cookies_upgrade_10.js
@@ -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/. */
+
+"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.obs.notifyObservers(null, "profile-before-change");
+
+ // check for upgraded schema.
+ Assert.equal(12, 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..3ce36b2242
--- /dev/null
+++ b/netwerk/test/unit/test_data_protocol.js
@@ -0,0 +1,91 @@
+/* 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",
+ "foobar",
+ ],
+ [
+ "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",
+ ],
+ // Bug 781693
+ ["data:text/plain;base64;x=y,dGVzdA==", "text/plain", "test"],
+ ["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..92ac3042b3
--- /dev/null
+++ b/netwerk/test/unit/test_defaultURI.js
@@ -0,0 +1,185 @@
+"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.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_disabled_ftp.js b/netwerk/test/unit/test_disabled_ftp.js
new file mode 100644
index 0000000000..953c9ac45b
--- /dev/null
+++ b/netwerk/test/unit/test_disabled_ftp.js
@@ -0,0 +1,21 @@
+"use strict";
+
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("network.ftp.enabled");
+});
+
+add_task(async function ftp_disabled() {
+ Services.prefs.setBoolPref("network.ftp.enabled", false);
+ Services.prefs.setBoolPref("network.protocol-handler.external.ftp", false);
+
+ Assert.throws(
+ () => {
+ NetUtil.newChannel({
+ uri: "ftp://ftp.de.debian.org/",
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ },
+ /NS_ERROR_UNKNOWN_PROTOCOL/,
+ "creating the FTP channel must throw"
+ );
+});
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..f31325b8c0
--- /dev/null
+++ b/netwerk/test/unit/test_dns_by_type_resolve.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";
+
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+let prefs;
+let h2Port;
+let listen;
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const mainThread = threadManager.currentThread;
+
+const defaultOriginAttributes = {};
+
+function setup() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ // the TRR server is on 127.0.0.1
+ prefs.setCharPref("network.trr.bootstrapAddress", "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", 2); // 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.
+ 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
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+}
+
+setup();
+registerCleanupFunction(() => {
+ prefs.clearUserPref("network.http.spdy.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled.http2");
+ 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.bootstrapAddress");
+ prefs.clearUserPref("network.trr.blacklist-duration");
+ prefs.clearUserPref("network.trr.request-timeout");
+ prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
+});
+
+let test_answer = "bXkgdm9pY2UgaXMgbXkgcGFzc3dvcmQ=";
+let test_answer_addr = "127.0.0.1";
+
+class DNSListener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+ // So we can await this as a promise.
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+
+DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIDNSListener",
+]);
+
+add_task(async function testTXTResolve() {
+ // use the h2 server as DOH provider
+ prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/doh"
+ );
+
+ let listenerEsni = new DNSListener();
+ let request = dns.asyncResolve(
+ "_esni.example.com",
+ dns.RESOLVE_TYPE_TXT,
+ 0,
+ null, // resolverInfo
+ listenerEsni,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, inRecord, inStatus] = await listenerEsni;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+ 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() {
+ prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/txt-dns-push"
+ );
+ let listenerAddr = new DNSListener();
+ let request = dns.asyncResolve(
+ "_esni_push.example.com",
+ dns.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null, // resolverInfo
+ listenerAddr,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, inRecord, inStatus] = await listenerAddr;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+ 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.
+ prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/404"
+ );
+ let listenerEsni = new DNSListener();
+ let request = dns.asyncResolve(
+ "_esni_push.example.com",
+ dns.RESOLVE_TYPE_TXT,
+ 0,
+ null, // resolverInfo
+ listenerEsni,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, inRecord, inStatus] = await listenerEsni;
+ Assert.equal(inRequest, request, "correct request was used");
+ 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/test_dns_cancel.js b/netwerk/test/unit/test_dns_cancel.js
new file mode 100644
index 0000000000..60cff1cc52
--- /dev/null
+++ b/netwerk/test/unit/test_dns_cancel.js
@@ -0,0 +1,125 @@
+"use strict";
+
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+
+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 requestList1Canceled1;
+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 threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+ );
+ var mainThread = threadManager.currentThread;
+
+ var flags = Ci.nsIDNSService.RESOLVE_BYPASS_CACHE;
+
+ // This one will be canceled with cancelAsyncResolve.
+ requestList1Canceled1 = dns.asyncResolve(
+ hostname2,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ flags,
+ null, // resolverInfo
+ listener1,
+ mainThread,
+ defaultOriginAttributes
+ );
+ dns.cancelAsyncResolve(
+ hostname2,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ flags,
+ null, // resolverInfo
+ listener1,
+ Cr.NS_ERROR_ABORT,
+ defaultOriginAttributes
+ );
+
+ // This one will not be canceled.
+ requestList1NotCanceled = 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 = 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 = 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 = 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..686bbf9b4a
--- /dev/null
+++ b/netwerk/test/unit/test_dns_disable_ipv4.js
@@ -0,0 +1,55 @@
+//
+// Tests that calling asyncResolve with the RESOLVE_DISABLE_IPV4 flag doesn't
+// return any IPv4 addresses.
+//
+
+"use strict";
+
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+);
+
+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 IPv6 address
+ dump(answer);
+ Assert.ok(answer.includes(":"));
+ } catch (e) {
+ break;
+ }
+ }
+ do_test_finished();
+ },
+};
+
+const defaultOriginAttributes = {};
+
+function run_test() {
+ do_test_pending();
+ try {
+ dns.asyncResolve(
+ "example.org",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ Ci.nsIDNSService.RESOLVE_DISABLE_IPV4,
+ null, // resolverInfo
+ listener,
+ null,
+ defaultOriginAttributes
+ );
+ } catch (e) {
+ dump(e);
+ Assert.ok(false);
+ do_test_finished();
+ }
+}
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..c22cf57144
--- /dev/null
+++ b/netwerk/test/unit/test_dns_disable_ipv6.js
@@ -0,0 +1,56 @@
+//
+// Tests that calling asyncResolve with the RESOLVE_DISABLE_IPV6 flag doesn't
+// return any IPv6 addresses.
+//
+
+"use strict";
+
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+);
+
+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 {
+ 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..7b840e2a3b
--- /dev/null
+++ b/netwerk/test/unit/test_dns_disabled.js
@@ -0,0 +1,94 @@
+"use strict";
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+const mainThread = threadManager.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 {
+ 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..0af59de813
--- /dev/null
+++ b/netwerk/test/unit/test_dns_localredirect.js
@@ -0,0 +1,68 @@
+"use strict";
+
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+
+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 threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+ );
+ var mainThread = threadManager.currentThread;
+ nextTest = do_test_2;
+ dns.asyncResolve(
+ "local.vingtetun.org",
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ do_test_pending();
+}
+
+function do_test_2() {
+ var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+ );
+ var mainThread = threadManager.currentThread;
+ nextTest = testsDone;
+ prefs.setCharPref("network.dns.forceResolve", "localhost");
+ 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..3b95fe542f
--- /dev/null
+++ b/netwerk/test/unit/test_dns_offline.js
@@ -0,0 +1,113 @@
+"use strict";
+
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+);
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+var mainThread = threadManager.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 {
+ 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() {
+ 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() {
+ 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..b7824fed3f
--- /dev/null
+++ b/netwerk/test/unit/test_dns_onion.js
@@ -0,0 +1,82 @@
+"use strict";
+
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+var mainThread = threadManager.currentThread;
+
+var onionPref;
+var localdomainPref;
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+
+// 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);
+ 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 {
+ 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..31bc1478a1
--- /dev/null
+++ b/netwerk/test/unit/test_dns_originAttributes.js
@@ -0,0 +1,99 @@
+"use strict";
+
+var dns = Cc["@mozilla.org/network/dns-service;1"].getService(Ci.nsIDNSService);
+var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+var mainThread = threadManager.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);
+ 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();
+ 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 {
+ 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..70fa688d8e
--- /dev/null
+++ b/netwerk/test/unit/test_dns_override.js
@@ -0,0 +1,311 @@
+"use strict";
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+const defaultOriginAttributes = {};
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const mainThread = threadManager.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 > 0) {
+ 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_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");
+ dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Assert.equal(await listener.firstAddress(), "1.2.3.4");
+
+ dns.clearCache(false);
+ override.clearOverrides();
+});
+
+add_task(async function test_ipv6() {
+ let listener = new Listener();
+ override.addIPOverride(DOMAIN, "fe80::6a99:9b2b:6ccc:6e1b");
+ dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Assert.equal(await listener.firstAddress(), "fe80::6a99:9b2b:6ccc:6e1b");
+
+ dns.clearCache(false);
+ override.clearOverrides();
+});
+
+add_task(async function test_clearOverrides() {
+ let listener = new Listener();
+ override.addIPOverride(DOMAIN, "1.2.3.4");
+ dns.asyncResolve(
+ DOMAIN,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Assert.equal(await listener.firstAddress(), "1.2.3.4");
+
+ dns.clearCache(false);
+ override.clearOverrides();
+
+ listener = new Listener();
+ 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));
+ 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();
+ 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();
+ 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));
+ 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();
+ 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",
+ ]);
+
+ 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();
+ 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();
+ 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"]);
+
+ dns.clearCache(false);
+ override.clearOverrides();
+});
+
+add_task(async function test_cname_flag() {
+ override.addIPOverride(DOMAIN, "2.2.2.2");
+ let listener = new Listener();
+ 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();
+ 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");
+
+ dns.clearCache(false);
+ override.clearOverrides();
+
+ override.addIPOverride(DOMAIN, "2.2.2.2");
+ override.setCnameOverride(DOMAIN, OTHER);
+ listener = new Listener();
+ 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");
+
+ dns.clearCache(false);
+ override.clearOverrides();
+});
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..462cb90aac
--- /dev/null
+++ b/netwerk/test/unit/test_dns_override_for_localhost.js
@@ -0,0 +1,92 @@
+"use strict";
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+const defaultOriginAttributes = {};
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const mainThread = threadManager.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"]);
+
+["localhost", "vhost.localhost"].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.
+ 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();
+ 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");
+
+ 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..8fbe91c346
--- /dev/null
+++ b/netwerk/test/unit/test_dns_proxy_bypass.js
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+);
+
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+
+var url = "ws://dnsleak.example.com";
+
+var dnsRequestObserver = {
+ register() {
+ this.obs = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ 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") {
+ info(data);
+ if (data.indexOf("dnsleak.example.com") > -1) {
+ try {
+ Assert.ok(false);
+ } catch (e) {}
+ }
+ }
+ },
+};
+
+var listener = {
+ onAcknowledge(aContext, aSize) {},
+ onBinaryMessageAvailable(aContext, aMsg) {},
+ onMessageAvailable(aContext, aMsg) {},
+ onServerClose(aContext, aCode, aReason) {},
+ onStart(aContext) {},
+ onStop(aContext, aStatusCode) {
+ 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");
+ dnsRequestObserver.unregister();
+ do_test_finished();
+ },
+};
+
+function run_test() {
+ dnsRequestObserver.register();
+ 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);
+ var 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 = ioService.newURI(url);
+ chan.asyncOpen(uri, url, 0, listener, null);
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_dns_service.js b/netwerk/test/unit/test_dns_service.js
new file mode 100644
index 0000000000..5ee3ef2f0f
--- /dev/null
+++ b/netwerk/test/unit/test_dns_service.js
@@ -0,0 +1,68 @@
+"use strict";
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const defaultOriginAttributes = {};
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const mainThread = threadManager.currentThread;
+
+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 DOMAIN = "localhost";
+const ADDR1 = "127.0.0.1";
+const ADDR2 = "::1";
+
+add_task(async function test_dns_localhost() {
+ let listener = new Listener();
+ 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();
+ 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");
+});
diff --git a/netwerk/test/unit/test_domain_eviction.js b/netwerk/test/unit/test_domain_eviction.js
new file mode 100644
index 0000000000..232fc89d99
--- /dev/null
+++ b/netwerk/test/unit/test_domain_eviction.js
@@ -0,0 +1,185 @@
+/* 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.cookiemgr.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.cookiemgr.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.cookiemgr.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.cookiemgr.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.cookiemgr.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.cookiemgr.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.cookiemgr.countCookiesFromHost(aBaseDomain),
+ cookies.length
+ );
+ Assert.equal(Services.cookiemgr.countCookiesFromHost(aHost), cookies.length);
+
+ for (let cookie of Services.cookiemgr.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_doomentry.js b/netwerk/test/unit/test_doomentry.js
new file mode 100644
index 0000000000..23928d223d
--- /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) {
+ get_cache_service()
+ .diskCacheStorage(Services.loadContextInfo.default, false)
+ .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(status, 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..0c747c6066
--- /dev/null
+++ b/netwerk/test/unit/test_duplicate_headers.js
@@ -0,0 +1,558 @@
+/*
+ * Tests bugs 597706, 655389: prevent duplicate headers with differing values
+ * for some headers like Content-Length, Location, etc.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+// Test infrastructure
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var index = 0;
+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_event_sink.js b/netwerk/test/unit/test_event_sink.js
new file mode 100644
index 0000000000..ecb16d845d
--- /dev/null
+++ b/netwerk/test/unit/test_event_sink.js
@@ -0,0 +1,195 @@
+// This file tests channel event sinks (bug 315598 et al)
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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(outer, iid) {
+ if (outer) {
+ throw Components.Exception("", Cr.NS_ERROR_NO_AGGREGATION);
+ }
+ return this.QueryInterface(iid);
+ },
+ lockFactory: function eventsink_lockf(lock) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+
+ 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 catMan = Cc["@mozilla.org/categorymanager;1"].getService(
+ Ci.nsICategoryManager
+ );
+
+ var chan;
+ if (listener._iteration == 1) {
+ // Step 2: Category entry
+ catMan.nsICategoryManager.addCategoryEntry(
+ categoryName,
+ "unit test",
+ sinkContract,
+ false,
+ true
+ );
+ chan = makeChan(URL + "/redirect");
+ } else {
+ // Step 3: Global contract id
+ catMan.nsICategoryManager.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..43be39dba2
--- /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.cookiemgr.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.cookiemgr.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.cookiemgr.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.cookiemgr.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.cookiemgr.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.cookiemgr.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.cookiemgr.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.cookiemgr.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.cookiemgr.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..1d12b6bc2b
--- /dev/null
+++ b/netwerk/test/unit/test_extract_charset_from_content_type.js
@@ -0,0 +1,245 @@
+/* -*- 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 reset() {
+ delete charset.value;
+ delete charsetStart.value;
+ delete charsetEnd.value;
+ hadCharset = undefined;
+}
+
+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 = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
+ 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_fallback_no-cache-entry_canceled.js b/netwerk/test/unit/test_fallback_no-cache-entry_canceled.js
new file mode 100644
index 0000000000..d5ca20badf
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_no-cache-entry_canceled.js
@@ -0,0 +1,131 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.newURI(url);
+}
+
+var responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response) {
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(
+ masterEntryContent,
+ masterEntryContent.length
+ );
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response) {
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer) {
+ Assert.equal(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"].getService(
+ Ci.nsIPermissionManager
+ );
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createContentPrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump(
+ "Previous test failed to clear offline-app permission! Expect failures.\n"
+ );
+ }
+ pm.addFromPrincipal(
+ principal,
+ "offline-app",
+ Ci.nsIPermissionManager.ALLOW_ACTION
+ );
+
+ var ps = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setBoolPref("browser.cache.offline.storage.enable", true);
+ ps.setComplexValue(
+ "browser.cache.offline.parent_directory",
+ Ci.nsIFile,
+ do_get_profile()
+ );
+
+ cacheUpdateObserver = {
+ observe() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ // In this test the randomURI doesn't exists
+ var chan = make_channel(randomURI);
+ chan.loadFlags =
+ Ci.nsIRequest.INHIBIT_CACHING |
+ Ci.nsIRequest.LOAD_FROM_CACHE |
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE;
+ chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+ },
+ };
+
+ var os = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed");
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].getService(
+ Ci.nsIOfflineCacheUpdateService
+ );
+ us.scheduleUpdate(
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/manifest"
+ ),
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/masterEntry"
+ ),
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_fallback_no-cache-entry_passing.js b/netwerk/test/unit/test_fallback_no-cache-entry_passing.js
new file mode 100644
index 0000000000..9bbadb4631
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_no-cache-entry_passing.js
@@ -0,0 +1,130 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.newURI(url);
+}
+
+var responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response) {
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(
+ masterEntryContent,
+ masterEntryContent.length
+ );
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response) {
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"].getService(
+ Ci.nsIPermissionManager
+ );
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createContentPrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump(
+ "Previous test failed to clear offline-app permission! Expect failures.\n"
+ );
+ }
+ pm.addFromPrincipal(
+ principal,
+ "offline-app",
+ Ci.nsIPermissionManager.ALLOW_ACTION
+ );
+
+ var ps = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setBoolPref("browser.cache.offline.storage.enable", true);
+ ps.setComplexValue(
+ "browser.cache.offline.parent_directory",
+ Ci.nsIFile,
+ do_get_profile()
+ );
+
+ cacheUpdateObserver = {
+ observe() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ // In this test the randomURI doesn't exists
+ var chan = make_channel(randomURI);
+ chan.loadFlags =
+ Ci.nsIRequest.INHIBIT_CACHING |
+ Ci.nsIRequest.LOAD_FROM_CACHE |
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE;
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen(new ChannelListener(finish_test));
+ },
+ };
+
+ var os = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed");
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].getService(
+ Ci.nsIOfflineCacheUpdateService
+ );
+ us.scheduleUpdate(
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/manifest"
+ ),
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/masterEntry"
+ ),
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_fallback_redirect-to-different-origin_canceled.js b/netwerk/test/unit/test_fallback_redirect-to-different-origin_canceled.js
new file mode 100644
index 0000000000..d04797cacf
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_redirect-to-different-origin_canceled.js
@@ -0,0 +1,133 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.newURI(url);
+}
+
+const responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response) {
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(
+ masterEntryContent,
+ masterEntryContent.length
+ );
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response) {
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// redirect handler returns redirect
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "http://example.com/", false);
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer) {
+ Assert.equal(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"].getService(
+ Ci.nsIPermissionManager
+ );
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createContentPrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump(
+ "Previous test failed to clear offline-app permission! Expect failures.\n"
+ );
+ }
+ pm.addFromPrincipal(
+ principal,
+ "offline-app",
+ Ci.nsIPermissionManager.ALLOW_ACTION
+ );
+
+ var ps = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setBoolPref("browser.cache.offline.storage.enable", true);
+ ps.setComplexValue(
+ "browser.cache.offline.parent_directory",
+ Ci.nsIFile,
+ do_get_profile()
+ );
+
+ cacheUpdateObserver = {
+ observe() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ var chan = make_channel(randomURI);
+ chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+ },
+ };
+
+ var os = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed");
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].getService(
+ Ci.nsIOfflineCacheUpdateService
+ );
+ us.scheduleUpdate(
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/manifest"
+ ),
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/masterEntry"
+ ),
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_fallback_redirect-to-different-origin_passing.js b/netwerk/test/unit/test_fallback_redirect-to-different-origin_passing.js
new file mode 100644
index 0000000000..dc1702da3f
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_redirect-to-different-origin_passing.js
@@ -0,0 +1,132 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.newURI(url);
+}
+
+var responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response) {
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(
+ masterEntryContent,
+ masterEntryContent.length
+ );
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response) {
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// redirect handler returns redirect
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "http://example.com/", false);
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.registerPathHandler(randomPath, redirectHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"].getService(
+ Ci.nsIPermissionManager
+ );
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createContentPrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump(
+ "Previous test failed to clear offline-app permission! Expect failures.\n"
+ );
+ }
+ pm.addFromPrincipal(
+ principal,
+ "offline-app",
+ Ci.nsIPermissionManager.ALLOW_ACTION
+ );
+
+ var ps = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setBoolPref("browser.cache.offline.storage.enable", true);
+ ps.setComplexValue(
+ "browser.cache.offline.parent_directory",
+ Ci.nsIFile,
+ do_get_profile()
+ );
+
+ cacheUpdateObserver = {
+ observe() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ var chan = make_channel(randomURI);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen(new ChannelListener(finish_test));
+ },
+ };
+
+ var os = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed");
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].getService(
+ Ci.nsIOfflineCacheUpdateService
+ );
+ us.scheduleUpdate(
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/manifest"
+ ),
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/masterEntry"
+ ),
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_fallback_request-error_canceled.js b/netwerk/test/unit/test_fallback_request-error_canceled.js
new file mode 100644
index 0000000000..8f1eb8b597
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_request-error_canceled.js
@@ -0,0 +1,140 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.newURI(url);
+}
+
+var responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response) {
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(
+ masterEntryContent,
+ masterEntryContent.length
+ );
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response) {
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// redirect handler returns redirect
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "http://example.com/", false);
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer) {
+ Assert.equal(buffer, "");
+ do_test_finished();
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"].getService(
+ Ci.nsIPermissionManager
+ );
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createContentPrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump(
+ "Previous test failed to clear offline-app permission! Expect failures.\n"
+ );
+ }
+ pm.addFromPrincipal(
+ principal,
+ "offline-app",
+ Ci.nsIPermissionManager.ALLOW_ACTION
+ );
+
+ var ps = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setBoolPref("browser.cache.offline.storage.enable", true);
+ ps.setComplexValue(
+ "browser.cache.offline.parent_directory",
+ Ci.nsIFile,
+ do_get_profile()
+ );
+
+ cacheUpdateObserver = {
+ observe() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+
+ // doing this to eval the lazy getter, otherwise the make_channel call would hang
+ randomURI;
+ httpServer.stop(function() {
+ // Now shut the server down to have an error in onStartRequest
+ var chan = make_channel(randomURI);
+ chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen(
+ new ChannelListener(finish_test, null, CL_EXPECT_FAILURE)
+ );
+ });
+ },
+ };
+
+ var os = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed");
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].getService(
+ Ci.nsIOfflineCacheUpdateService
+ );
+ us.scheduleUpdate(
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/manifest"
+ ),
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/masterEntry"
+ ),
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_fallback_request-error_passing.js b/netwerk/test/unit/test_fallback_request-error_passing.js
new file mode 100644
index 0000000000..89a672ee4c
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_request-error_passing.js
@@ -0,0 +1,136 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/redirect/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.newURI(url);
+}
+
+var responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response) {
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(
+ masterEntryContent,
+ masterEntryContent.length
+ );
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response) {
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nredirect /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// redirect handler returns redirect
+function redirectHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 301, "Moved");
+ response.setHeader("Location", "http://example.com/", false);
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ do_test_finished();
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"].getService(
+ Ci.nsIPermissionManager
+ );
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createContentPrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump(
+ "Previous test failed to clear offline-app permission! Expect failures.\n"
+ );
+ }
+ pm.addFromPrincipal(
+ principal,
+ "offline-app",
+ Ci.nsIPermissionManager.ALLOW_ACTION
+ );
+
+ var ps = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setBoolPref("browser.cache.offline.storage.enable", true);
+ ps.setComplexValue(
+ "browser.cache.offline.parent_directory",
+ Ci.nsIFile,
+ do_get_profile()
+ );
+
+ cacheUpdateObserver = {
+ observe() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ randomURI; // doing this so the lazy value gets computed
+ httpServer.stop(function() {
+ // Now shut the server down to have an error in onstartrequest
+ var chan = make_channel(randomURI);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen(new ChannelListener(finish_test));
+ });
+ },
+ };
+
+ var os = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed");
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].getService(
+ Ci.nsIOfflineCacheUpdateService
+ );
+ us.scheduleUpdate(
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/manifest"
+ ),
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/masterEntry"
+ ),
+ systemPrincipal,
+ null
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_fallback_response-error_canceled.js b/netwerk/test/unit/test_fallback_response-error_canceled.js
new file mode 100644
index 0000000000..5bd8a2d222
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_response-error_canceled.js
@@ -0,0 +1,133 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/error/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.newURI(url);
+}
+
+var responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response) {
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(
+ masterEntryContent,
+ masterEntryContent.length
+ );
+}
+
+// manifest defines fallback namespace from any /error path to /content
+function manifestHandler(metadata, response) {
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nerror /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// error handler returns error
+function errorHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 404, "Bad request");
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer) {
+ Assert.equal(buffer, "");
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.registerPathHandler(randomPath, errorHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"].getService(
+ Ci.nsIPermissionManager
+ );
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createContentPrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump(
+ "Previous test failed to clear offline-app permission! Expect failures.\n"
+ );
+ }
+ pm.addFromPrincipal(
+ principal,
+ "offline-app",
+ Ci.nsIPermissionManager.ALLOW_ACTION
+ );
+
+ var ps = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setBoolPref("browser.cache.offline.storage.enable", true);
+ ps.setComplexValue(
+ "browser.cache.offline.parent_directory",
+ Ci.nsIFile,
+ do_get_profile()
+ );
+
+ cacheUpdateObserver = {
+ observe() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ var chan = make_channel(randomURI);
+ chan.notificationCallbacks = new ChannelEventSink(ES_ABORT_REDIRECT);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
+ },
+ };
+
+ var os = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed");
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].getService(
+ Ci.nsIOfflineCacheUpdateService
+ );
+ us.scheduleUpdate(
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/manifest"
+ ),
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/masterEntry"
+ ),
+ systemPrincipal,
+ null
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_fallback_response-error_passing.js b/netwerk/test/unit/test_fallback_response-error_passing.js
new file mode 100644
index 0000000000..c9715a916a
--- /dev/null
+++ b/netwerk/test/unit/test_fallback_response-error_passing.js
@@ -0,0 +1,132 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpServer = null;
+// Need to randomize, because apparently no one clears our cache
+var randomPath = "/error/" + Math.random();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + randomPath;
+});
+
+var cacheUpdateObserver = null;
+var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.newURI(url);
+}
+
+var responseBody = "Content body";
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response) {
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(
+ masterEntryContent,
+ masterEntryContent.length
+ );
+}
+
+// manifest defines fallback namespace from any /error path to /content
+function manifestHandler(metadata, response) {
+ var manifestContent = "CACHE MANIFEST\nFALLBACK:\nerror /content\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// content handler correctly returns some plain text data
+function contentHandler(metadata, response) {
+ response.setHeader("Content-Type", "text/plain");
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+}
+
+// error handler returns error
+function errorHandler(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 404, "Bad request");
+}
+
+// finally check we got fallback content
+function finish_test(request, buffer) {
+ Assert.equal(buffer, responseBody);
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.registerPathHandler("/content", contentHandler);
+ httpServer.registerPathHandler(randomPath, errorHandler);
+ httpServer.start(-1);
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"].getService(
+ Ci.nsIPermissionManager
+ );
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ var uri = make_uri("http://localhost:" + httpServer.identity.primaryPort);
+ var principal = ssm.createContentPrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump(
+ "Previous test failed to clear offline-app permission! Expect failures.\n"
+ );
+ }
+ pm.addFromPrincipal(
+ principal,
+ "offline-app",
+ Ci.nsIPermissionManager.ALLOW_ACTION
+ );
+
+ var ps = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setBoolPref("browser.cache.offline.storage.enable", true);
+ ps.setComplexValue(
+ "browser.cache.offline.parent_directory",
+ Ci.nsIFile,
+ do_get_profile()
+ );
+
+ cacheUpdateObserver = {
+ observe() {
+ dump("got offline-cache-update-completed\n");
+ // offline cache update completed.
+ var chan = make_channel(randomURI);
+ var chanac = chan.QueryInterface(Ci.nsIApplicationCacheChannel);
+ chanac.chooseApplicationCache = true;
+ chan.asyncOpen(new ChannelListener(finish_test));
+ },
+ };
+
+ var os = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ os.addObserver(cacheUpdateObserver, "offline-cache-update-completed");
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].getService(
+ Ci.nsIOfflineCacheUpdateService
+ );
+ us.scheduleUpdate(
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/manifest"
+ ),
+ make_uri(
+ "http://localhost:" + httpServer.identity.primaryPort + "/masterEntry"
+ ),
+ systemPrincipal,
+ null
+ );
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_file_protocol.js b/netwerk/test/unit/test_file_protocol.js
new file mode 100644
index 0000000000..9aa4cf4183
--- /dev/null
+++ b/netwerk/test/unit/test_file_protocol.js
@@ -0,0 +1,280 @@
+/* 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 getFile(key) {
+ var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(
+ Ci.nsIProperties
+ );
+ return dirSvc.get(key, Ci.nsIFile);
+}
+
+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 ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return NetUtil.newChannel({
+ uri: ios.newFileURI(file),
+ loadUsingSystemPrincipal: true,
+ });
+}
+
+/*
+ * 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 != 0) {
+ 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
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ Assert.equal(
+ chan.originalURI.pathQueryRef,
+ ios.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..d8beec0b8a
--- /dev/null
+++ b/netwerk/test/unit/test_filestreams.js
@@ -0,0 +1,306 @@
+/* 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 = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .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 = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .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 = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .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..90db800575
--- /dev/null
+++ b/netwerk/test/unit/test_getHost.js
@@ -0,0 +1,63 @@
+// Test getLocalHost/getLocalPort and getRemoteHost/getRemotePort.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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_gre_resources.js b/netwerk/test/unit/test_gre_resources.js
new file mode 100644
index 0000000000..098dae660d
--- /dev/null
+++ b/netwerk/test/unit/test_gre_resources.js
@@ -0,0 +1,32 @@
+// test that things that are expected to be in gre-resources are still there
+
+"use strict";
+
+var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+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 > 0);
+ } 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_gzipped_206.js b/netwerk/test/unit/test_gzipped_206.js
new file mode 100644
index 0000000000..a60bef3d26
--- /dev/null
+++ b/netwerk/test/unit/test_gzipped_206.js
@@ -0,0 +1,167 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpserver = null;
+
+// testString = "This is a slightly longer test\n";
+const responseBody = [
+ 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,
+];
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
+}
+
+var doRangeResponse = false;
+
+function cachedHandler(metadata, response) {
+ response.setHeader("Content-Type", "application/x-gzip", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+ response.setHeader("ETag", "Just testing");
+ response.setHeader("Cache-Control", "max-age=3600000"); // avoid validation
+
+ var body = responseBody;
+
+ if (doRangeResponse) {
+ Assert.ok(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 = body.slice(from, to + 1);
+ response.setHeader("Content-Length", "" + (to + 1 - from));
+ // always respond to successful range requests with 206
+ response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
+ response.setHeader(
+ "Content-Range",
+ from + "-" + to + "/" + responseBody.length,
+ false
+ );
+ } else {
+ // This response will get cut off prematurely
+ response.setHeader("Content-Length", "" + responseBody.length);
+ response.setHeader("Accept-Ranges", "bytes");
+ body = body.slice(0, 17); // slice off a piece to send first
+ doRangeResponse = true;
+ }
+
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
+ Ci.nsIBinaryOutputStream
+ );
+ bos.setOutputStream(response.bodyOutputStream);
+
+ response.processAsync();
+ bos.writeByteArray(body);
+ response.finish();
+}
+
+function continue_test(request, data) {
+ Assert.equal(17, data.length);
+ var chan = make_channel(
+ "http://localhost:" + httpserver.identity.primaryPort + "/cached/test.gz"
+ );
+ chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_GZIP));
+}
+
+var enforcePref;
+
+function finish_test(request, data, ctx) {
+ Assert.equal(request.status, 0);
+ Assert.equal(data.length, responseBody.length);
+ for (var i = 0; i < data.length; ++i) {
+ Assert.equal(data.charCodeAt(i), responseBody[i]);
+ }
+ Services.prefs.setBoolPref("network.http.enforce-framing.http1", enforcePref);
+ httpserver.stop(do_test_finished);
+}
+
+function run_test() {
+ enforcePref = Services.prefs.getBoolPref(
+ "network.http.enforce-framing.http1"
+ );
+ Services.prefs.setBoolPref("network.http.enforce-framing.http1", false);
+
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler("/cached/test.gz", cachedHandler);
+ httpserver.start(-1);
+
+ // wipe out cached content
+ evict_cache_entries();
+
+ var chan = make_channel(
+ "http://localhost:" + httpserver.identity.primaryPort + "/cached/test.gz"
+ );
+ chan.asyncOpen(
+ new ChannelListener(continue_test, null, CL_EXPECT_GZIP | CL_IGNORE_CL)
+ );
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_head.js b/netwerk/test/unit/test_head.js
new file mode 100644
index 0000000000..c25b3e3dbc
--- /dev/null
+++ b/netwerk/test/unit/test_head.js
@@ -0,0 +1,169 @@
+//
+// HTTP headers test
+//
+
+// Note: sets Cc and Ci variables
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+const ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+var channel;
+var ios;
+
+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");
+ Assert.equal(setOK, "http://foo2.invalid:90/bar");
+
+ // 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/bar");
+
+ 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..df319cbaee
--- /dev/null
+++ b/netwerk/test/unit/test_head_request_no_response_body.js
@@ -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/. */
+
+/*
+
+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.import("resource://testing-common/httpd.js");
+
+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..f3b3571992
--- /dev/null
+++ b/netwerk/test/unit/test_header_Accept-Language.js
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefService)
+ .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..bbad7c9879
--- /dev/null
+++ b/netwerk/test/unit/test_header_Accept-Language_case.js
@@ -0,0 +1,53 @@
+"use strict";
+
+var testpath = "/bug1054739";
+
+function run_test() {
+ let intlPrefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefService)
+ .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..c4a3f6e336
--- /dev/null
+++ b/netwerk/test/unit/test_header_Server_Timing.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/. */
+
+//
+// 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 env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ var serverPort = 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..baaacdf17b
--- /dev/null
+++ b/netwerk/test/unit/test_headers.js
@@ -0,0 +1,175 @@
+//
+// 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.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = new HttpServer();
+var index = 0;
+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
+function handler1(metadata, response) {
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.setHeader("Content-Disposition", "attachment; filename=foo");
+ response.setHeader("Content-Type", "text/plain", false);
+}
+
+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
+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);
+}
+
+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
+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);
+}
+
+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
+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);
+}
+
+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..589766b522
--- /dev/null
+++ b/netwerk/test/unit/test_hostnameIsLocalIPAddress.js
@@ -0,0 +1,41 @@
+"use strict";
+
+var ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+);
+
+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 = ioService.newURI(uri);
+ equal(isLocal, ioService.hostnameIsLocalIPAddress(nsuri));
+ }
+}
diff --git a/netwerk/test/unit/test_hostnameIsSharedIPAddress.js b/netwerk/test/unit/test_hostnameIsSharedIPAddress.js
new file mode 100644
index 0000000000..80a848bfbb
--- /dev/null
+++ b/netwerk/test/unit/test_hostnameIsSharedIPAddress.js
@@ -0,0 +1,21 @@
+"use strict";
+
+var ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+);
+
+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 = ioService.newURI(uri);
+ equal(isShared, ioService.hostnameIsSharedIPAddress(nsuri));
+ }
+}
diff --git a/netwerk/test/unit/test_http1-proxy.js b/netwerk/test/unit/test_http1-proxy.js
new file mode 100644
index 0000000000..88faf7d884
--- /dev/null
+++ b/netwerk/test/unit/test_http1-proxy.js
@@ -0,0 +1,229 @@
+/* -*- 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.import("resource://testing-common/httpd.js");
+
+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.js b/netwerk/test/unit/test_http2-proxy.js
new file mode 100644
index 0000000000..1c9d6151be
--- /dev/null
+++ b/netwerk/test/unit/test_http2-proxy.js
@@ -0,0 +1,615 @@
+/* -*- 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
+ */
+
+/* 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) {
+ if (
+ uri.pathQueryRef.startsWith("/execute") ||
+ uri.pathQueryRef.startsWith("/fork") ||
+ uri.pathQueryRef.startsWith("/kill")
+ ) {
+ // So we allow NodeServer.execute to work
+ cb.onProxyFilterResult(pi);
+ return;
+ }
+ 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 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
+ )
+ );
+ });
+}
+
+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 setupProxy() {
+ if (!proxy) {
+ throw new Error("proxy is null");
+ }
+ proxy.proxy_session_count = 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;
+ }
+
+ 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 `Unxpected 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;
+}
+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");
+
+ const env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ let server_port = 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 proxy = await NodeServer.execute(
+ processId,
+ `http2ProxyCode.startNewProxy()`
+ );
+ proxy_port = proxy.port;
+ Assert.notEqual(proxy_port, null);
+
+ Services.prefs.setStringPref(
+ "services.settings.server",
+ `data:text/html,test`
+ );
+
+ Services.prefs.setBoolPref("network.http.spdy.enabled", true);
+ Services.prefs.setBoolPref("network.http.spdy.enabled.http2", 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.spdy.enabled");
+ Services.prefs.clearUserPref("network.http.spdy.enabled.http2");
+ 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"
+ );
+});
+
+// Make sure that the above error codes don't kill the session and we still reach the end server
+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 created after proxy 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"
+ );
+});
diff --git a/netwerk/test/unit/test_http2.js b/netwerk/test/unit/test_http2.js
new file mode 100644
index 0000000000..afc47ba740
--- /dev/null
+++ b/netwerk/test/unit/test_http2.js
@@ -0,0 +1,1442 @@
+// test HTTP/2
+
+"use strict";
+
+// 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));
+ }
+
+ run_next_test();
+ do_test_finished();
+ },
+};
+
+/*
+ * Support for testing valid multiplexing of streams
+ */
+
+var multiplexContent = generateContent(30 * 1024);
+var completed_channels = [];
+function register_completed_channel(listener) {
+ completed_channels.push(listener);
+ if (completed_channels.length == 2) {
+ Assert.notEqual(
+ completed_channels[0].streamID,
+ completed_channels[1].streamID
+ );
+ run_next_test();
+ do_test_finished();
+ }
+}
+
+/* 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);
+
+ // This is what does most of the hard work for us
+ register_completed_channel(this);
+};
+
+// 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:" + serverPort + "/push.js" ||
+ request.originalURI.spec ==
+ "https://localhost:" + serverPort + "/push2.js" ||
+ request.originalURI.spec == "https://localhost:" + 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:" + 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) {
+ run_next_test();
+ do_test_finished();
+ }
+};
+
+Http2ContinuedHeaderListener.prototype.onPush = function(
+ associatedChannel,
+ pushChannel
+) {
+ Assert.equal(
+ associatedChannel.originalURI.spec,
+ "https://localhost:" + 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);
+
+ run_next_test();
+ do_test_finished();
+};
+
+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
+ run_next_test();
+ do_test_finished();
+};
+
+// 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")
+ );
+};
+
+function makeChan(url) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+}
+
+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
+function test_http2_blocking_download() {
+ var chan = makeChan("https://localhost:" + serverPort + "/bigdownload");
+ var internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
+ internalChannel.initialRwin = 500000; // make the stream.suspend push back in h2
+ var listener = new Http2CheckListener();
+ 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 = makeChan("https://localhost:" + serverPort + "/");
+ var sl = new ResumeStalledChannelListener();
+ sl.resumable = chan;
+ simpleChannel.asyncOpen(sl);
+ });
+}
+
+// Make sure we make a HTTP2 connection and both us and the server mark it as such
+function test_http2_basic() {
+ var chan = makeChan("https://localhost:" + serverPort + "/");
+ var listener = new Http2CheckListener();
+ chan.asyncOpen(listener);
+}
+
+function test_http2_basic_unblocked_dep() {
+ var chan = makeChan(
+ "https://localhost:" + serverPort + "/basic_unblocked_dep"
+ );
+ var cos = chan.QueryInterface(Ci.nsIClassOfService);
+ cos.addClassFlags(Ci.nsIClassOfService.Unblocked);
+ var listener = new Http2CheckListener();
+ chan.asyncOpen(listener);
+}
+
+// make sure we don't use h2 when disallowed
+function test_http2_nospdy() {
+ var chan = makeChan("https://localhost:" + serverPort + "/");
+ var listener = new Http2CheckListener();
+ 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) {
+ if (xhr.readyState != 4) {
+ return;
+ }
+
+ Assert.equal(xhr.status, 200);
+ Assert.equal(checkIsHttp2(xhr), true);
+ run_next_test();
+ do_test_finished();
+}
+
+// Fires off an XHR request over h2
+function test_http2_xhr() {
+ var req = new XMLHttpRequest();
+ req.open("GET", "https://localhost:" + serverPort + "/", true);
+ req.addEventListener("readystatechange", function(evt) {
+ checkXhr(req);
+ });
+ req.send(null);
+}
+
+var concurrent_channels = [];
+
+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) {
+ prefs.setIntPref("network.http.spdy.default-concurrent", this.reset);
+ }
+ run_next_test();
+ do_test_finished();
+ }
+};
+
+function test_http2_concurrent() {
+ var concurrent_listener = new Http2ConcurrentListener();
+ concurrent_listener.target = 201;
+ concurrent_listener.reset = prefs.getIntPref(
+ "network.http.spdy.default-concurrent"
+ );
+ prefs.setIntPref("network.http.spdy.default-concurrent", 100);
+
+ for (var i = 0; i < concurrent_listener.target; i++) {
+ concurrent_channels[i] = makeChan(
+ "https://localhost:" + serverPort + "/750ms"
+ );
+ concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ concurrent_channels[i].asyncOpen(concurrent_listener);
+ }
+}
+
+function test_http2_concurrent_post() {
+ var concurrent_listener = new Http2ConcurrentListener();
+ concurrent_listener.target = 8;
+ concurrent_listener.recvdHdr = posts[2].length;
+ concurrent_listener.reset = prefs.getIntPref(
+ "network.http.spdy.default-concurrent"
+ );
+ prefs.setIntPref("network.http.spdy.default-concurrent", 3);
+
+ for (var i = 0; i < concurrent_listener.target; i++) {
+ concurrent_channels[i] = makeChan(
+ "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
+function test_http2_multiplex() {
+ var chan1 = makeChan("https://localhost:" + serverPort + "/multiplex1");
+ var chan2 = makeChan("https://localhost:" + serverPort + "/multiplex2");
+ var listener1 = new Http2MultiplexListener();
+ var listener2 = new Http2MultiplexListener();
+ chan1.asyncOpen(listener1);
+ chan2.asyncOpen(listener2);
+}
+
+// Test to make sure we gateway non-standard headers properly
+function test_http2_header() {
+ var chan = makeChan("https://localhost:" + serverPort + "/header");
+ var hvalue = "Headers are fun";
+ chan.setRequestHeader("X-Test-Header", hvalue, false);
+ var listener = new Http2HeaderListener("X-Received-Test-Header", function(
+ received_hvalue
+ ) {
+ Assert.equal(received_hvalue, hvalue);
+ });
+ chan.asyncOpen(listener);
+}
+
+// Test to make sure cookies are split into separate fields before compression
+function test_http2_cookie_crumbling() {
+ var chan = makeChan("https://localhost:" + serverPort + "/cookie_crumbling");
+ var cookiesSent = ["a=b", "c=d01234567890123456789", "e=f"].sort();
+ chan.setRequestHeader("Cookie", cookiesSent.join("; "), false);
+ 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);
+ });
+ });
+ chan.asyncOpen(listener);
+}
+
+function test_http2_push1() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener(true);
+ chan.asyncOpen(listener);
+}
+
+function test_http2_push2() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push.js");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener(true);
+ chan.asyncOpen(listener);
+}
+
+function test_http2_push3() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push2");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener(true);
+ chan.asyncOpen(listener);
+}
+
+function test_http2_push4() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push2.js");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener(true);
+ chan.asyncOpen(listener);
+}
+
+function test_http2_push5() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push5");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener(true);
+ chan.asyncOpen(listener);
+}
+
+function test_http2_push6() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push5.js");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushListener(true);
+ chan.asyncOpen(listener);
+}
+
+// this is a basic test where the server sends a simple document with 2 header
+// blocks. bug 1027364
+function test_http2_doubleheader() {
+ var chan = makeChan("https://localhost:" + serverPort + "/doubleheader");
+ var listener = new Http2CheckListener();
+ chan.asyncOpen(listener);
+}
+
+// Make sure we handle GETs that cover more than 2 frames properly
+function test_http2_big() {
+ var chan = makeChan("https://localhost:" + serverPort + "/big");
+ var listener = new Http2BigListener();
+ chan.asyncOpen(listener);
+}
+
+function test_http2_huge_suspended() {
+ var chan = makeChan("https://localhost:" + serverPort + "/huge");
+ var listener = new Http2HugeSuspendedListener();
+ 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
+function test_http2_post() {
+ var chan = makeChan("https://localhost:" + serverPort + "/post");
+ var listener = new Http2PostListener(md5s[0]);
+ do_post(posts[0], chan, listener, "POST");
+}
+
+// Make sure we can do a simple PATCH
+function test_http2_patch() {
+ var chan = makeChan("https://localhost:" + serverPort + "/patch");
+ var listener = new Http2PostListener(md5s[0]);
+ do_post(posts[0], chan, listener, "PATCH");
+}
+
+// Make sure we can do a POST that covers more than 2 frames
+function test_http2_post_big() {
+ var chan = makeChan("https://localhost:" + serverPort + "/post");
+ var listener = new Http2PostListener(md5s[1]);
+ do_post(posts[1], chan, listener, "POST");
+}
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpserv = null;
+var httpserv2 = null;
+var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+var altsvcClientListener = {
+ 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");
+ var chan = makeChan(
+ "http://foo.example.com:" + httpserv.identity.primaryPort + "/altsvc1"
+ ).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:" + httpserv2.identity.primaryPort,
+ false
+ );
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen(altsvcClientListener);
+ } else {
+ Assert.ok(isHttp2Connection);
+ var chan = makeChan(
+ "http://foo.example.com:" + httpserv2.identity.primaryPort + "/altsvc2"
+ ).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen(altsvcClientListener2);
+ }
+ },
+};
+
+var altsvcClientListener2 = {
+ 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 = makeChan(
+ "http://foo.example.com:" + httpserv2.identity.primaryPort + "/altsvc2"
+ ).QueryInterface(Ci.nsIHttpChannel);
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen(altsvcClientListener2);
+ } else {
+ Assert.ok(isHttp2Connection);
+ run_next_test();
+ do_test_finished();
+ }
+ },
+};
+
+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);
+}
+function test_http2_altsvc() {
+ var chan = makeChan(
+ "http://foo.example.com:" + httpserv.identity.primaryPort + "/altsvc1"
+ ).QueryInterface(Ci.nsIHttpChannel);
+ chan.asyncOpen(altsvcClientListener);
+}
+
+var Http2PushApiListener = function() {};
+
+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:" + serverPort + "/pushapi1"
+ );
+ Assert.equal(pushChannel.getRequestHeader("x-pushed-request"), "true");
+
+ pushChannel.asyncOpen(this);
+ if (
+ pushChannel.originalURI.spec ==
+ "https://localhost:" + serverPort + "/pushapi1/2"
+ ) {
+ pushChannel.cancel(Cr.NS_ERROR_ABORT);
+ } else if (
+ pushChannel.originalURI.spec ==
+ "https://localhost:" + 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:" + serverPort + "/pushapi1/2"
+ );
+
+ var data = read_stream(stream, cnt);
+
+ if (
+ request.originalURI.spec ==
+ "https://localhost:" + serverPort + "/pushapi1"
+ ) {
+ Assert.equal(data[0], "0");
+ --this.checksPending;
+ } else if (
+ request.originalURI.spec ==
+ "https://localhost:" + serverPort + "/pushapi1/1"
+ ) {
+ Assert.equal(data[0], "1");
+ --this.checksPending; // twice
+ } else if (
+ request.originalURI.spec ==
+ "https://localhost:" + 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:" + 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) {
+ run_next_test();
+ do_test_finished();
+ }
+ },
+};
+
+// 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
+
+function test_http2_pushapi_1() {
+ var chan = makeChan("https://localhost:" + serverPort + "/pushapi1");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2PushApiListener();
+ chan.notificationCallbacks = listener;
+ chan.asyncOpen(listener);
+}
+
+var WrongSuiteListener = function() {};
+WrongSuiteListener.prototype = new Http2CheckListener();
+WrongSuiteListener.prototype.shouldBeHttp2 = false;
+WrongSuiteListener.prototype.onStopRequest = function(request, status) {
+ prefs.setBoolPref("security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", true);
+ prefs.clearUserPref("security.tls.version.max");
+ Http2CheckListener.prototype.onStopRequest.call(this);
+};
+
+// test that we use h1 without the mandatory cipher suite available when
+// offering at most tls1.2
+function test_http2_wrongsuite_tls12() {
+ prefs.setBoolPref("security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", false);
+ prefs.setIntPref("security.tls.version.max", 3);
+ var chan = makeChan("https://localhost:" + serverPort + "/wrongsuite");
+ chan.loadFlags =
+ Ci.nsIRequest.LOAD_FRESH_CONNECTION |
+ Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ var listener = new WrongSuiteListener();
+ chan.asyncOpen(listener);
+}
+
+// test that we use h2 when offering tls1.3 or higher regardless of if the
+// mandatory cipher suite is available
+function test_http2_wrongsuite_tls13() {
+ prefs.setBoolPref("security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", false);
+ var chan = makeChan("https://localhost:" + serverPort + "/wrongsuite");
+ chan.loadFlags =
+ Ci.nsIRequest.LOAD_FRESH_CONNECTION |
+ Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
+ var listener = new WrongSuiteListener();
+ listener.shouldBeHttp2 = true;
+ chan.asyncOpen(listener);
+}
+
+function test_http2_h11required_stream() {
+ var chan = makeChan(
+ "https://localhost:" + serverPort + "/h11required_stream"
+ );
+ var listener = new Http2CheckListener();
+ 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);
+
+ run_next_test();
+ do_test_finished();
+};
+
+function test_http2_h11required_session() {
+ var chan = makeChan(
+ "https://localhost:" + serverPort + "/h11required_session"
+ );
+ var listener = new H11RequiredSessionListener();
+ listener.shouldBeHttp2 = false;
+ chan.asyncOpen(listener);
+}
+
+function test_http2_retry_rst() {
+ var chan = makeChan("https://localhost:" + serverPort + "/rstonce");
+ var listener = new Http2CheckListener();
+ chan.asyncOpen(listener);
+}
+
+function test_http2_continuations() {
+ var chan = makeChan("https://localhost:" + serverPort + "/continuedheaders");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2ContinuedHeaderListener();
+ 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);
+
+ run_next_test();
+ do_test_finished();
+};
+
+function Http2IllegalHpackListener() {}
+Http2IllegalHpackListener.prototype = new Http2CheckListener();
+Http2IllegalHpackListener.prototype.shouldGoAway = false;
+
+Http2IllegalHpackListener.prototype.onStopRequest = function(request, status) {
+ var chan = makeChan(
+ "https://localhost:" + serverPort + "/illegalhpack_validate"
+ );
+ var listener = new Http2IllegalHpackValidationListener();
+ listener.shouldGoAway = this.shouldGoAway;
+ chan.asyncOpen(listener);
+};
+
+function test_http2_illegalhpacksoft() {
+ var chan = makeChan("https://localhost:" + serverPort + "/illegalhpacksoft");
+ var listener = new Http2IllegalHpackListener();
+ listener.shouldGoAway = false;
+ listener.shouldSucceed = false;
+ chan.asyncOpen(listener);
+}
+
+function test_http2_illegalhpackhard() {
+ var chan = makeChan("https://localhost:" + serverPort + "/illegalhpackhard");
+ var listener = new Http2IllegalHpackListener();
+ listener.shouldGoAway = true;
+ listener.shouldSucceed = false;
+ chan.asyncOpen(listener);
+}
+
+function test_http2_folded_header() {
+ var chan = makeChan("https://localhost:" + serverPort + "/foldedheader");
+ chan.loadGroup = loadGroup;
+ var listener = new Http2CheckListener();
+ listener.shouldSucceed = false;
+ chan.asyncOpen(listener);
+}
+
+function test_http2_empty_data() {
+ var chan = makeChan("https://localhost:" + serverPort + "/emptydata");
+ var listener = new Http2CheckListener();
+ chan.asyncOpen(listener);
+}
+
+function test_http2_push_firstparty1() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push");
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { firstPartyDomain: "foo.com" };
+ var listener = new Http2PushListener(true);
+ chan.asyncOpen(listener);
+}
+
+function test_http2_push_firstparty2() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push.js");
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { firstPartyDomain: "bar.com" };
+ var listener = new Http2PushListener(false);
+ chan.asyncOpen(listener);
+}
+
+function test_http2_push_firstparty3() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push.js");
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { firstPartyDomain: "foo.com" };
+ var listener = new Http2PushListener(true);
+ chan.asyncOpen(listener);
+}
+
+function test_http2_push_userContext1() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push");
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { userContextId: 1 };
+ var listener = new Http2PushListener(true);
+ chan.asyncOpen(listener);
+}
+
+function test_http2_push_userContext2() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push.js");
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { userContextId: 2 };
+ var listener = new Http2PushListener(false);
+ chan.asyncOpen(listener);
+}
+
+function test_http2_push_userContext3() {
+ var chan = makeChan("https://localhost:" + serverPort + "/push.js");
+ chan.loadGroup = loadGroup;
+ chan.loadInfo.originAttributes = { userContextId: 1 };
+ var listener = new Http2PushListener(true);
+ chan.asyncOpen(listener);
+}
+
+function test_http2_status_phrase() {
+ var chan = makeChan("https://localhost:" + serverPort + "/statusphrase");
+ var listener = new Http2CheckListener();
+ 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() {};
+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 = makeChan("https://localhost:" + serverPort + "/diskcache");
+ var listener = new PulledDiskCacheListener();
+ chan.loadGroup = 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 = makeChan("https://localhost:" + serverPort + "/diskcache");
+ var listener = new FromDiskCacheListener();
+ chan.loadGroup = loadGroup;
+ chan.asyncOpen(listener);
+};
+
+function continue_test_http2_disk_cache_push(status, entry, appCache) {
+ // 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 = makeChan("https://localhost:" + serverPort + "/pushindisk");
+ var listener = new Http2DiskCachePushListener();
+ chan.loadGroup = loadGroup;
+ chan.asyncOpen(listener);
+}
+
+function test_http2_disk_cache_push() {
+ asyncOpenCacheEntry(
+ "https://localhost:" + serverPort + "/diskcache",
+ "disk",
+ Ci.nsICacheStorage.OPEN_NORMALLY,
+ null,
+ continue_test_http2_disk_cache_push,
+ false
+ );
+}
+
+function test_complete() {
+ resetPrefs();
+ do_test_pending();
+ httpserv.stop(do_test_finished);
+ do_test_pending();
+ httpserv2.stop(do_test_finished);
+
+ do_test_finished();
+ do_timeout(0, run_next_test);
+}
+
+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 = makeChan("https://localhost:" + serverPort + "/doublypushed");
+ var listener = new Http2DoublypushedListener();
+ chan.loadGroup = 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");
+
+ run_next_test();
+ do_test_finished();
+};
+
+function test_http2_doublepush() {
+ var chan = makeChan("https://localhost:" + serverPort + "/doublepush");
+ var listener = new Http2DoublepushListener();
+ chan.loadGroup = loadGroup;
+ chan.asyncOpen(listener);
+}
+
+// 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
+var tests = [
+ test_http2_post_big,
+ test_http2_basic,
+ test_http2_concurrent,
+ test_http2_concurrent_post,
+ test_http2_basic_unblocked_dep,
+ test_http2_nospdy,
+ test_http2_push1,
+ test_http2_push2,
+ test_http2_push3,
+ test_http2_push4,
+ test_http2_push5,
+ test_http2_push6,
+ test_http2_altsvc,
+ test_http2_doubleheader,
+ test_http2_xhr,
+ test_http2_header,
+ test_http2_cookie_crumbling,
+ test_http2_multiplex,
+ test_http2_big,
+ test_http2_huge_suspended,
+ test_http2_post,
+ test_http2_patch,
+ test_http2_pushapi_1,
+ test_http2_continuations,
+ test_http2_blocking_download,
+ test_http2_illegalhpacksoft,
+ test_http2_illegalhpackhard,
+ test_http2_folded_header,
+ test_http2_empty_data,
+ test_http2_status_phrase,
+ test_http2_doublepush,
+ test_http2_disk_cache_push,
+ // 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
+ test_http2_h11required_stream,
+ test_http2_h11required_session,
+ test_http2_retry_rst,
+ test_http2_wrongsuite_tls12,
+ test_http2_wrongsuite_tls13,
+ test_http2_push_firstparty1,
+ test_http2_push_firstparty2,
+ test_http2_push_firstparty3,
+ test_http2_push_userContext1,
+ test_http2_push_userContext2,
+ test_http2_push_userContext3,
+
+ // cleanup
+ test_complete,
+];
+var current_test = 0;
+
+function run_next_test() {
+ if (current_test < tests.length) {
+ dump("starting test number " + current_test + "\n");
+ tests[current_test]();
+ current_test++;
+ do_test_pending();
+ }
+}
+
+var prefs;
+var spdypref;
+var spdypush;
+var http2pref;
+var altsvcpref1;
+var altsvcpref2;
+var loadGroup;
+var serverPort;
+var speculativeLimit;
+
+function resetPrefs() {
+ prefs.setIntPref("network.http.speculative-parallel-limit", speculativeLimit);
+ prefs.setBoolPref("network.http.spdy.enabled", spdypref);
+ prefs.setBoolPref("network.http.spdy.allow-push", spdypush);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", http2pref);
+ prefs.setBoolPref("network.http.altsvc.enabled", altsvcpref1);
+ prefs.setBoolPref("network.http.altsvc.oe", altsvcpref2);
+ prefs.clearUserPref("network.dns.localDomains");
+ prefs.clearUserPref("network.cookieJarSettings.unblocked_for_testing");
+}
+
+function run_test() {
+ var env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ serverPort = 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();
+ prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ speculativeLimit = prefs.getIntPref(
+ "network.http.speculative-parallel-limit"
+ );
+ 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");
+
+ // Enable all versions of spdy to see that we auto negotiate http/2
+ spdypref = prefs.getBoolPref("network.http.spdy.enabled");
+ spdypush = prefs.getBoolPref("network.http.spdy.allow-push");
+ http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2");
+ altsvcpref1 = prefs.getBoolPref("network.http.altsvc.enabled");
+ altsvcpref2 = prefs.getBoolPref("network.http.altsvc.oe", true);
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.allow-push", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", 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"
+ );
+ 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
+ );
+
+ // And make go!
+ run_next_test();
+}
diff --git a/netwerk/test/unit/test_http3.js b/netwerk/test/unit/test_http3.js
new file mode 100644
index 0000000000..dcadde3c6b
--- /dev/null
+++ b/netwerk/test/unit/test_http3.js
@@ -0,0 +1,569 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+// 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 env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ let h2Port = env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+ let h3Port = env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ h3AltSvc = ":" + h3Port;
+
+ h3Route = "foo.example.com:" + h3Port;
+ do_get_profile();
+ prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.http.http3.enabled", 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 + "/";
+
+ 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-27=" + 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");
+ Assert.equal(this.onDataAvailableFired, true);
+ }
+ 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");
+ 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-27=: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");
+ }
+
+ 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");
+ 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.enabled");
+ prefs.clearUserPref("network.dns.localDomains");
+ prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+ dump("testDone\n");
+ do_test_pending();
+ h1Server.stop(do_test_finished);
+}
diff --git a/netwerk/test/unit/test_http3_421.js b/netwerk/test/unit/test_http3_421.js
new file mode 100644
index 0000000000..65853b1091
--- /dev/null
+++ b/netwerk/test/unit/test_http3_421.js
@@ -0,0 +1,175 @@
+"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 env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ let h2Port = env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+ let h3Port = env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ h3AltSvc = ":" + h3Port;
+
+ h3Route = "foo.example.com:" + h3Port;
+ do_get_profile();
+ prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.http.http3.enabled", 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");
+ 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-27=: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.enabled");
+ 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..77e7498a9c
--- /dev/null
+++ b/netwerk/test/unit/test_http3_alt_svc.js
@@ -0,0 +1,129 @@
+"use strict";
+
+let httpsOrigin;
+let h3AltSvc;
+let h3Port;
+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 env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ let h2Port = env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+ let h3Port = env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ h3AltSvc = ":" + h3Port;
+
+ h3Route = "foo.example.com:" + h3Port;
+ do_get_profile();
+ prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.http.http3.enabled", 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 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");
+ 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-27=:h3port,h3-29=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.enabled");
+ prefs.clearUserPref("network.dns.localDomains");
+ prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
+ dump("testDone\n");
+}
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..fd1fdaf543
--- /dev/null
+++ b/netwerk/test/unit/test_http3_error_before_connect.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";
+
+let httpsUri;
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.http3.enabled");
+ 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 chanPromise(chan, listener) {
+ return new Promise(resolve => {
+ function finish() {
+ resolve();
+ }
+ listener.finish = finish;
+ chan.asyncOpen(listener);
+ });
+}
+
+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 env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+
+ let h2Port = env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ let h3Port = env.get("MOZHTTP3_PORT_NO_RESPONSE");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+
+ Services.prefs.setBoolPref("network.http.http3.enabled", 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-27=:" + 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..9a2d78847b
--- /dev/null
+++ b/netwerk/test/unit/test_http3_fast_fallback.js
@@ -0,0 +1,597 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+var { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
+
+let prefs;
+let h2Port;
+let h3Port;
+let listen;
+let trrServer;
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const mainThread = threadManager.currentThread;
+
+const defaultOriginAttributes = {};
+
+function setup() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ h3Port = env.get("MOZHTTP3_PORT_NO_RESPONSE");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+ prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.security.esni.enabled", false);
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ // the TRR server is on 127.0.0.1
+ prefs.setCharPref("network.trr.bootstrapAddress", "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", 2); // 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.
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+
+ Services.prefs.setBoolPref("network.http.http3.enabled", true);
+
+ // 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, "http2-ca.pem", "CTu,u,u");
+}
+
+setup();
+registerCleanupFunction(async () => {
+ prefs.clearUserPref("network.security.esni.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled.http2");
+ 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.bootstrapAddress");
+ prefs.clearUserPref("network.trr.request-timeout");
+ prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
+ prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ prefs.clearUserPref("network.dns.echconfig.enabled");
+ prefs.clearUserPref("network.dns.echconfig.fallback_to_origin");
+ prefs.clearUserPref("network.dns.httpssvc.reset_exclustion_list");
+ prefs.clearUserPref("network.http.http3.enabled");
+ 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");
+ if (trrServer) {
+ await trrServer.stop();
+ }
+});
+
+class DNSListener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+ // So we can await this as a promise.
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+
+DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIDNSListener",
+]);
+
+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 => {
+ 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));
+ });
+}
+
+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.enabled", 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-27=:" + h3Port
+ );
+ Services.prefs.setBoolPref("network.dns.disableIPv6", true);
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
+
+ await fast_fallback_test();
+});
+
+let HTTPObserver = {
+ observeActivity(
+ aChannel,
+ aType,
+ aSubtype,
+ aTimestamp,
+ aSizeData,
+ aStringData
+ ) {
+ aChannel.QueryInterface(Ci.nsIChannel);
+ if (aChannel.URI.spec == `https://foo.example.com:${h2Port}/`) {
+ if (
+ aType == Ci.nsIHttpActivityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION &&
+ aSubtype ==
+ Ci.nsIHttpActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_HEADER
+ ) {
+ // We need to enable speculative connection again, since the backup
+ // connection is done by using speculative connection.
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
+ let observerService = Cc[
+ "@mozilla.org/network/http-activity-distributor;1"
+ ].getService(Ci.nsIHttpActivityDistributor);
+ observerService.removeObserver(HTTPObserver);
+ }
+ }
+ },
+};
+
+// 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");
+ 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);
+
+ let observerService = Cc[
+ "@mozilla.org/network/http-activity-distributor;1"
+ ].getService(Ci.nsIHttpActivityDistributor);
+ observerService.addObserver(HTTPObserver);
+
+ 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.enabled", true);
+
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 1000
+ );
+
+ await trrServer.registerDoHAnswers("test.fastfallback.com", "HTTPS", [
+ {
+ name: "test.fastfallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.fastfallback1.com",
+ values: [
+ { key: "alpn", value: "h3-27" },
+ { 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", [
+ {
+ name: "test.fastfallback1.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ]);
+
+ await trrServer.registerDoHAnswers("test.fastfallback2.com", "A", [
+ {
+ 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.enabled", true);
+
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 10
+ );
+
+ await trrServer.registerDoHAnswers("test.fastfallback.org", "HTTPS", [
+ {
+ name: "test.fastfallback.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.fastfallback1.org",
+ values: [
+ { key: "alpn", value: "h3-27" },
+ { 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", [
+ {
+ name: "test.fastfallback1.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ]);
+
+ await trrServer.registerDoHAnswers("test.fastfallback2.org", "A", [
+ {
+ 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.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.enabled", true);
+
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 1000
+ );
+
+ await trrServer.registerDoHAnswers("test.ech.org", "HTTPS", [
+ {
+ name: "test.ech.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.ech1.org",
+ values: [
+ { key: "alpn", value: "h3-27" },
+ { 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", [
+ {
+ name: "test.ech1.org",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ]);
+
+ await trrServer.registerDoHAnswers("test.ech3.org", "A", [
+ {
+ 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.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.enabled", true);
+
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 1000
+ );
+
+ await trrServer.registerDoHAnswers("test.partial_ech.org", "HTTPS", [
+ {
+ name: "test.partial_ech.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.partial_ech1.org",
+ values: [
+ { key: "alpn", value: "h3-27" },
+ { 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", [
+ {
+ name: "test.partial_ech1.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();
+});
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..48b194fbba
--- /dev/null
+++ b/netwerk/test/unit/test_http3_fatal_stream_error.js
@@ -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/. */
+"use strict";
+
+let httpsUri;
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.http3.enabled");
+ 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 chanPromise(chan, listener) {
+ return new Promise(resolve => {
+ function finish(result) {
+ resolve(result);
+ }
+ listener.finish = finish;
+ chan.asyncOpen(listener);
+ });
+}
+
+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 env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+
+ let h2Port = env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+
+ let h3Port = env.get("MOZHTTP3_PORT_FAILED");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+
+ Services.prefs.setBoolPref("network.http.http3.enabled", 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-27=:" + 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..e309c2c00d
--- /dev/null
+++ b/netwerk/test/unit/test_http3_large_post.js
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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();
+});
+
+let Http3Listener = function() {};
+
+Http3Listener.prototype = {
+ 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);
+ }
+ 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");
+
+ 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();
+ listener.amount = amount;
+ let chan = makeChan("https://foo.example.com/post", amount);
+ 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();
+ listener.amount = amount;
+ let chan = makeChan("https://foo.example.com/post", amount);
+ await chanPromise(chan, listener);
+});
diff --git a/netwerk/test/unit/test_http3_perf.js b/netwerk/test/unit/test_http3_perf.js
new file mode 100644
index 0000000000..679450a163
--- /dev/null
+++ b/netwerk/test/unit/test_http3_perf.js
@@ -0,0 +1,262 @@
+"use strict";
+
+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++;
+ }
+}
+
+function run_test() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ let h2Port = env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+ let h3Port = env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ h3AltSvc = ":" + h3Port;
+
+ h3Route = "foo.example.com:" + h3Port;
+ do_get_profile();
+ prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.http.http3.enabled", 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");
+ 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");
+ 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-27=: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.enabled");
+ 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_server_not_existing.js b/netwerk/test/unit/test_http3_server_not_existing.js
new file mode 100644
index 0000000000..31ce77cc36
--- /dev/null
+++ b/netwerk/test/unit/test_http3_server_not_existing.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";
+
+let httpsUri;
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.http.http3.enabled");
+ 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 chanPromise(chan, listener) {
+ return new Promise(resolve => {
+ function finish() {
+ resolve();
+ }
+ listener.finish = finish;
+ chan.asyncOpen(listener);
+ });
+}
+
+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 env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+
+ let h2Port = env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+
+ Services.prefs.setBoolPref("network.http.http3.enabled", 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-27=: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..a7525a5d4b
--- /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();
+});
+
+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");
+ 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_httpResponseTimeout.js b/netwerk/test/unit/test_httpResponseTimeout.js
new file mode 100644
index 0000000000..6c82758156
--- /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.import("resource://testing-common/httpd.js");
+
+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 = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+
+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_headers.js b/netwerk/test/unit/test_http_headers.js
new file mode 100644
index 0000000000..e87d4cc2bb
--- /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 (var i = 0; i < 100; ++i) {
+ chan.setRequestHeader("foopy" + i, i, false);
+ }
+
+ for (var 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_sfv.js b/netwerk/test/unit/test_http_sfv.js
new file mode 100644
index 0000000000..c1254865fb
--- /dev/null
+++ b/netwerk/test/unit/test_http_sfv.js
@@ -0,0 +1,590 @@
+"use strict";
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+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
+ 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));
+ let inner_list_items = [
+ gService.newItem(
+ gService.newDecimal(-1865.75653),
+ gService.newParameters()
+ ),
+ gService.newItem(gService.newToken("token"), gService.newParameters()),
+ gService.newItem(gService.newString(`no"yes`), gService.newParameters()),
+ ];
+ let new_list_member = gService.newInnerList(
+ inner_list_items,
+ inner_list_params
+ );
+
+ // set one of list members to inner list and check it's serialized as expected
+ let members = list_field.members;
+ members[1] = new_list_member;
+ list_field.members = members;
+ 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..85b4f99d89
--- /dev/null
+++ b/netwerk/test/unit/test_httpcancel.js
@@ -0,0 +1,260 @@
+// 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.import("resource://testing-common/httpd.js");
+
+function inChildProcess() {
+ return (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
+ );
+}
+
+var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+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.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/");
+ 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);
+
+ // 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
+ var env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ 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 str1 = "b".repeat(128 * 1024);
+ response.write(str1, str1.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_httpssvc_https_upgrade.js b/netwerk/test/unit/test_httpssvc_https_upgrade.js
new file mode 100644
index 0000000000..789ee0e7a6
--- /dev/null
+++ b/netwerk/test/unit/test_httpssvc_https_upgrade.js
@@ -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/. */
+
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+let prefs;
+let h2Port;
+let listen;
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+const mainThread = threadManager.currentThread;
+
+const defaultOriginAttributes = {};
+
+function setup() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ // the TRR server is on 127.0.0.1
+ prefs.setCharPref("network.trr.bootstrapAddress", "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", 2); // 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.
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/httpssvc_as_altsvc"
+ );
+
+ // 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, "http2-ca.pem", "CTu,u,u");
+}
+
+setup();
+registerCleanupFunction(() => {
+ prefs.clearUserPref("network.http.spdy.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled.http2");
+ 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.bootstrapAddress");
+ prefs.clearUserPref("network.trr.request-timeout");
+ prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
+ prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+});
+
+class DNSListener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+ // So we can await this as a promise.
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+
+DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIDNSListener",
+]);
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).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;
+ }
+ }
+ 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() {
+ dns.clearCache(true);
+
+ let dnsListener = new DNSListener();
+
+ // Do DNS resolution before creating the channel, so the HTTPSSVC record will
+ // be resolved from the cache.
+ let request = dns.asyncResolve(
+ "test.httpssvc.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ dnsListener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await dnsListener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ // 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() {
+ 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() {
+ 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();
+ let dnsListener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIDNSListener"]),
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ channel.resume();
+ },
+ };
+ dns.asyncResolve(
+ "foo.notexisted.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ dnsListener,
+ mainThread,
+ defaultOriginAttributes
+ );
+ }
+ },
+ };
+
+ 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));
+});
diff --git a/netwerk/test/unit/test_httpssvc_iphint.js b/netwerk/test/unit/test_httpssvc_iphint.js
new file mode 100644
index 0000000000..f54fa9f457
--- /dev/null
+++ b/netwerk/test/unit/test_httpssvc_iphint.js
@@ -0,0 +1,301 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+let prefs;
+let h2Port;
+let listen;
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const mainThread = threadManager.currentThread;
+
+const defaultOriginAttributes = {};
+
+function setup() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ // the TRR server is on 127.0.0.1
+ prefs.setCharPref("network.trr.bootstrapAddress", "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", 2); // 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.
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+
+ // 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, "http2-ca.pem", "CTu,u,u");
+}
+
+setup();
+registerCleanupFunction(() => {
+ prefs.clearUserPref("network.http.spdy.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled.http2");
+ 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.bootstrapAddress");
+ prefs.clearUserPref("network.trr.request-timeout");
+ prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
+ prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+});
+
+class DNSListener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+ // So we can await this as a promise.
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+
+DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIDNSListener",
+]);
+
+// Test if IP hint addresses can be accessed as regular A/AAAA records.
+add_task(async function testStoreIPHint() {
+ 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.IPHint.com", "HTTPS", [
+ {
+ name: "test.IPHint.com",
+ ttl: 55,
+ 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 listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "test.IPHint.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, inRecord, inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
+ 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(flags, answer) {
+ let listener = new DNSListener();
+ let request = dns.asyncResolve(
+ "test.IPHint.com",
+ dns.RESOLVE_TYPE_DEFAULT,
+ flags,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, inRecord, inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ let addresses = [];
+ while (inRecord.hasMore()) {
+ addresses.push(inRecord.getNextAddrAsString());
+ }
+ Assert.deepEqual(addresses, answer);
+ }
+
+ await verifyAnswer(Ci.nsIDNSService.RESOLVE_IP_HINT, [
+ "1.2.3.4",
+ "5.6.7.8",
+ "::1",
+ "fe80::794f:6d2c:3d5e:7836",
+ ]);
+
+ await verifyAnswer(
+ Ci.nsIDNSService.RESOLVE_IP_HINT | Ci.nsIDNSService.RESOLVE_DISABLE_IPV4,
+ ["::1", "fe80::794f:6d2c:3d5e:7836"]
+ );
+
+ await verifyAnswer(
+ Ci.nsIDNSService.RESOLVE_IP_HINT | Ci.nsIDNSService.RESOLVE_DISABLE_IPV6,
+ ["1.2.3.4", "5.6.7.8"]
+ );
+});
+
+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));
+ });
+}
+
+// Test if we can connect to the server with the IP hint address.
+add_task(async function testConnectionWithIPHint() {
+ dns.clearCache(true);
+ prefs.setIntPref("network.trr.mode", 3);
+ prefs.setCharPref(
+ "network.trr.uri",
+ "https://127.0.0.1:" + h2Port + "/httpssvc_use_iphint"
+ );
+
+ // Resolving test.iphint.com should be failed.
+ let listener = new DNSListener();
+ let request = dns.asyncResolve(
+ "test.iphint.com",
+ dns.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ 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/`);
+ // 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
+ );
+});
diff --git a/netwerk/test/unit/test_httpssvc_priority.js b/netwerk/test/unit/test_httpssvc_priority.js
new file mode 100644
index 0000000000..140adb9ab8
--- /dev/null
+++ b/netwerk/test/unit/test_httpssvc_priority.js
@@ -0,0 +1,235 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+let prefs;
+let h2Port;
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const mainThread = threadManager.currentThread;
+
+const defaultOriginAttributes = {};
+
+function setup() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ // the TRR server is on 127.0.0.1
+ prefs.setCharPref("network.trr.bootstrapAddress", "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", 2); // 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.
+ 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
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+}
+
+setup();
+registerCleanupFunction(() => {
+ prefs.clearUserPref("network.http.spdy.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled.http2");
+ 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.bootstrapAddress");
+ prefs.clearUserPref("network.trr.request-timeout");
+ prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
+ prefs.clearUserPref("network.dns.echconfig.enabled");
+});
+
+class DNSListener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+ // So we can await this as a promise.
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+
+DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIDNSListener",
+]);
+
+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", [
+ {
+ 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 listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "test.priority.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, inRecord, inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ 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);
+ dns.clearCache(true);
+ listener = new DNSListener();
+
+ request = dns.asyncResolve(
+ "test.priority.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ [inRequest, inRecord, inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ 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..b0fbc26867
--- /dev/null
+++ b/netwerk/test/unit/test_httpssvc_retry_with_ech.js
@@ -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/. */
+
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+let prefs;
+let h2Port;
+let listen;
+let trrServer;
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const mainThread = threadManager.currentThread;
+
+const defaultOriginAttributes = {};
+
+function setup() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.security.esni.enabled", false);
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ // the TRR server is on 127.0.0.1
+ prefs.setCharPref("network.trr.bootstrapAddress", "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", 2); // 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.
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+
+ 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);
+
+ // 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, "http2-ca.pem", "CTu,u,u");
+
+ add_tls_server_setup(
+ "EncryptedClientHelloServer",
+ "../../../security/manager/ssl/tests/unit/test_encrypted_client_hello"
+ );
+}
+
+setup();
+registerCleanupFunction(async () => {
+ prefs.clearUserPref("network.security.esni.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled.http2");
+ 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.bootstrapAddress");
+ prefs.clearUserPref("network.trr.request-timeout");
+ prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
+ prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ prefs.clearUserPref("network.dns.echconfig.enabled");
+ prefs.clearUserPref("network.dns.echconfig.fallback_to_origin");
+ if (trrServer) {
+ await trrServer.stop();
+ }
+});
+
+class DNSListener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+ // So we can await this as a promise.
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+
+DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIDNSListener",
+]);
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).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));
+ });
+}
+
+add_task(async function testConnectWithECH() {
+ const ECH_CONFIG_FIXED =
+ "AEr+CABGABZlY2gtcHVibGljLmV4YW1wbGUuY29tACCKB1Y5SfrGIyk27W82xPpzWTDs3q72c04xSurDWlb9CgAgAAQAAQADADIAAA==";
+ 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", [
+ {
+ 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", [
+ {
+ name: "ech-private.example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "ech-private.example.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ let chan = makeChan(`https://ech-private.example.com`);
+ await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ let securityInfo = chan.securityInfo.QueryInterface(
+ Ci.nsITransportSecurityInfo
+ );
+ Assert.ok(securityInfo.isAcceptedEch, "This host should have accepted ECH");
+
+ await trrServer.stop();
+});
+
+add_task(async function testEchRetry() {
+ dns.clearCache(true);
+
+ const ECH_CONFIG_TRUSTED_RETRY =
+ "AEr+CABGABZlY2gtcHVibGljLmV4YW1wbGUuY29tACCKB1Y5SfrGIyk27W82xPpzWTDs3q72c04xSurDWlb9CgAgAAQAAQABADIAAA==";
+ 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", [
+ {
+ 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", [
+ {
+ name: "ech-private.example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "ech-private.example.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
+ let chan = makeChan(`https://ech-private.example.com`);
+ await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ let securityInfo = chan.securityInfo.QueryInterface(
+ Ci.nsITransportSecurityInfo
+ );
+ Assert.ok(securityInfo.isAcceptedEch, "This host should have accepted ECH");
+
+ await trrServer.stop();
+});
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..b149fcbdcf
--- /dev/null
+++ b/netwerk/test/unit/test_httpssvc_retry_without_ech.js
@@ -0,0 +1,223 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+let prefs;
+let h2Port;
+let listen;
+let trrServer;
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const mainThread = threadManager.currentThread;
+
+const defaultOriginAttributes = {};
+
+function setup() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.security.esni.enabled", false);
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ // the TRR server is on 127.0.0.1
+ prefs.setCharPref("network.trr.bootstrapAddress", "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", 2); // 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.
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+
+ 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);
+
+ // 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, "http2-ca.pem", "CTu,u,u");
+
+ // An arbitrary, non-ECH server.
+ add_tls_server_setup(
+ "DelegatedCredentialsServer",
+ "../../../security/manager/ssl/tests/unit/test_delegated_credentials"
+ );
+
+ let nssComponent = Cc["@mozilla.org/psm;1"].getService(Ci.nsINSSComponent);
+ nssComponent.clearSSLExternalAndInternalSessionCache();
+}
+
+setup();
+registerCleanupFunction(async () => {
+ prefs.clearUserPref("network.security.esni.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled.http2");
+ 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.bootstrapAddress");
+ prefs.clearUserPref("network.trr.request-timeout");
+ prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
+ prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ prefs.clearUserPref("network.dns.echconfig.enabled");
+ prefs.clearUserPref("network.dns.echconfig.fallback_to_origin");
+ if (trrServer) {
+ await trrServer.stop();
+ }
+});
+
+class DNSListener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+ // So we can await this as a promise.
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+
+DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIDNSListener",
+]);
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).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 =
+ "AFH+CABNAB1kZWxlZ2F0ZWQtZW5hYmxlZC5leGFtcGxlLmNvbQAgigdWOUn6xiMpNu1vNsT6c1kw7N6u9nNOMUrqw1pW/QoAIAAEAAEAAwAyAAA=";
+ 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(
+ "delegated-disabled.example.com",
+ "HTTPS",
+ [
+ {
+ 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", [
+ {
+ name: "delegated-disabled.example.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "delegated-disabled.example.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ let chan = makeChan(`https://delegated-disabled.example.com:8443`);
+ await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
+ let securityInfo = chan.securityInfo.QueryInterface(
+ Ci.nsITransportSecurityInfo
+ );
+
+ 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..8675b3e6bd
--- /dev/null
+++ b/netwerk/test/unit/test_httpsuspend.js
@@ -0,0 +1,84 @@
+// This file ensures that suspending a channel directly after opening it
+// suspends future notifications correctly.
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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..d0c77d87a1
--- /dev/null
+++ b/netwerk/test/unit/test_idn_blacklist.js
@@ -0,0 +1,173 @@
+// 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 = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ var oldProfile = pbi.getCharPref(
+ "network.IDN.restriction_profile",
+ "moderate"
+ );
+ var oldWhitelistCom = pbi.getBoolPref("network.IDN.whitelist.com", false);
+ var idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
+ Ci.nsIIDNService
+ );
+
+ pbi.setCharPref("network.IDN.restriction_profile", "moderate");
+ pbi.setBoolPref("network.IDN.whitelist.com", false);
+
+ 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.setBoolPref("network.IDN.whitelist.com", oldWhitelistCom);
+ pbi.setCharPref("network.IDN.restriction_profile", oldProfile);
+}
diff --git a/netwerk/test/unit/test_idn_urls.js b/netwerk/test/unit/test_idn_urls.js
new file mode 100644
index 0000000000..2e00ca6932
--- /dev/null
+++ b/netwerk/test/unit/test_idn_urls.js
@@ -0,0 +1,441 @@
+// 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 = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ var oldProfile = pbi.getCharPref(
+ "network.IDN.restriction_profile",
+ "moderate"
+ );
+ var oldWhitelistCom = pbi.getBoolPref("network.IDN.whitelist.com", false);
+ 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]);
+ pbi.setBoolPref("network.IDN.whitelist.com", false);
+
+ 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.setBoolPref("network.IDN.whitelist.com", oldWhitelistCom);
+ 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..50b7967a6a
--- /dev/null
+++ b/netwerk/test/unit/test_immutable.js
@@ -0,0 +1,167 @@
+"use strict";
+
+var prefs;
+var spdypref;
+var http2pref;
+var origin;
+var rcwnpref;
+
+function run_test() {
+ var env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ var h2Port = 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ spdypref = prefs.getBoolPref("network.http.spdy.enabled");
+ http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2");
+ rcwnpref = prefs.getBoolPref("network.http.rcwn.enabled");
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", 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.spdy.enabled", spdypref);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", http2pref);
+ prefs.setBoolPref("network.http.rcwn.enabled", rcwnpref);
+ prefs.clearUserPref("network.dns.localDomains");
+}
+
+function makeChan(origin, 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(origin, "/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(origin, "/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(origin, "/immutable-test-without-attribute");
+ var listener = new Listener();
+ nextTest = doTest4;
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ chan.asyncOpen(listener);
+}
+
+function doTest4() {
+ dump("execute doTest1 - resource with immutable. initial request\n");
+ do_test_pending();
+ expectConditional = false;
+ var chan = makeChan(origin, "/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(origin, "/immutable-test-with-attribute");
+ var listener = new Listener();
+ nextTest = doTest6;
+ chan.loadFlags = Ci.nsIRequest.VALIDATE_ALWAYS;
+ chan.asyncOpen(listener);
+}
+
+function doTest6() {
+ dump("execute doTest3 - resource with immutable. shift reload\n");
+ do_test_pending();
+ expectConditional = false;
+ var chan = makeChan(origin, "/immutable-test-with-attribute");
+ var listener = new Listener();
+ nextTest = testsDone;
+ chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ 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..1af75aaa33
--- /dev/null
+++ b/netwerk/test/unit/test_inhibit_caching.js
@@ -0,0 +1,92 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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);
+}
+
+XPCOMUtils.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..492a03047f
--- /dev/null
+++ b/netwerk/test/unit/test_large_port.js
@@ -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/. */
+
+// 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..accfede36a
--- /dev/null
+++ b/netwerk/test/unit/test_loadgroup_cancel.js
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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.import("resource://testing-common/httpd.js");
+
+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_localstreams.js b/netwerk/test/unit/test_localstreams.js
new file mode 100644
index 0000000000..a4b76a7668
--- /dev/null
+++ b/netwerk/test/unit/test_localstreams.js
@@ -0,0 +1,90 @@
+// 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 ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ var uri = ios.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..5b59464b53
--- /dev/null
+++ b/netwerk/test/unit/test_mismatch_last-modified.js
@@ -0,0 +1,155 @@
+"use strict";
+
+const BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+var httpserver = new HttpServer();
+
+var ios;
+
+// 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);
+ },
+};
+
+XPCOMUtils.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);
+ },
+ };
+});
+
+XPCOMUtils.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();
+ ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ 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..620d527e1c
--- /dev/null
+++ b/netwerk/test/unit/test_mozTXTToHTMLConv.js
@@ -0,0 +1,395 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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: ["moz-smiley-s1"],
+ },
+ {
+ input: "this is a smiley :-)",
+ results: ["moz-smiley-s1"],
+ },
+ {
+ input: "this is a smiley :-(",
+ results: ["moz-smiley-s2"],
+ },
+ ];
+
+ 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..122d32e7d5
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_byteranges.js
@@ -0,0 +1,141 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpserver = null;
+
+XPCOMUtils.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..e8921fd2c4
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv-byte-by-byte.js
@@ -0,0 +1,113 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpserver = null;
+
+XPCOMUtils.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..421a8465cf
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv.js
@@ -0,0 +1,98 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpserver = null;
+
+XPCOMUtils.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..68bc5e6be8
--- /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, status) {
+ resolve([status, 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..ebe61854ae
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv_missing_boundary_lead_dashes.js
@@ -0,0 +1,90 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpserver = null;
+
+XPCOMUtils.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..1367a3499f
--- /dev/null
+++ b/netwerk/test/unit/test_multipart_streamconv_missing_lead_boundary.js
@@ -0,0 +1,90 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpserver = null;
+
+XPCOMUtils.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..f7d8881ce1
--- /dev/null
+++ b/netwerk/test/unit/test_nestedabout_serialize.js
@@ -0,0 +1,41 @@
+"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");
+
+const kNestedAboutCID = "{2f277c00-0eaf-4ddb-b936-41326ba48aae}";
+
+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..5cbc3c7baf
--- /dev/null
+++ b/netwerk/test/unit/test_net_addr.js
@@ -0,0 +1,222 @@
+"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;
+
+/**
+ * Connections have 5 seconds to be made, or a timeout function fails this
+ * test. This prevents the test from hanging and bringing down the entire
+ * xpcshell test chain.
+ */
+var connectTimeout = 5 * 1000;
+
+/**
+ * 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
+ );
+ /*
+ * 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_activity.js b/netwerk/test/unit/test_network_activity.js
new file mode 100644
index 0000000000..77058dbf77
--- /dev/null
+++ b/netwerk/test/unit/test_network_activity.js
@@ -0,0 +1,61 @@
+// test for networkactivity
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpserver = new HttpServer();
+var results = [];
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+
+function createChannel() {
+ var chan = NetUtil.newChannel({
+ uri: "http://localhost:" + httpserver.identity.primaryPort + "/ok",
+ loadUsingSystemPrincipal: true,
+ });
+ var httpChan = chan.QueryInterface(Ci.nsIHttpChannel);
+ return httpChan;
+}
+
+function handler(metadata, response) {
+ var body = "meh";
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setStatusLine(metadata.httpVersion, 200, "OK");
+ response.bodyOutputStream.write(body, body.length);
+}
+
+async function checkValueAndTrigger(request, data) {
+ // give Firefox 150 ms to send notifications out
+ do_timeout(150, doTest);
+}
+
+function doTest() {
+ ok(results.length > 0);
+ ok(results[0].host == "127.0.0.1");
+ ok(results[0].rx > 0 || results[0].tx > 0);
+ httpserver.stop(do_test_finished);
+}
+
+function run_test() {
+ // setting up an observer
+ let networkActivity = function(subject, topic, value) {
+ subject.QueryInterface(Ci.nsIMutableArray);
+ for (let data of subject.enumerate()) {
+ results.push(data);
+ }
+ };
+
+ Services.obs.addObserver(networkActivity, "network-activity");
+
+ // why do I have to do this ??
+ Services.obs.notifyObservers(null, "profile-initial-state");
+
+ do_test_pending();
+ httpserver.registerPathHandler("/ok", handler);
+ httpserver.start(-1);
+ var channel = createChannel();
+ channel.asyncOpen(new ChannelListener(checkValueAndTrigger, null));
+}
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..ef66823f55
--- /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.import("resource://testing-common/httpd.js");
+
+/**
+ * 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, topic, data) {
+ let matches = typeof matchFunc != "function" || matchFunc(subject, data);
+ if (!matches) {
+ return;
+ }
+ Services.obs.removeObserver(observe, topic);
+ resolve({ subject, data });
+ }, topic);
+ });
+}
+
+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;
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort + "/content";
+});
+
+XPCOMUtils.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 DEFAULT_WAIT_TIME = 200; // ms
+
+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_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..06bdd29515
--- /dev/null
+++ b/netwerk/test/unit/test_no_cookies_after_last_pb_exit.js
@@ -0,0 +1,134 @@
+/* globals ChromeUtils, Assert, add_task */
+"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.import("resource://testing-common/httpd.js");
+
+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 = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+ 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 == 0) {
+ 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..6af8f46ebf
--- /dev/null
+++ b/netwerk/test/unit/test_nojsredir.js
@@ -0,0 +1,65 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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_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_obs-fold.js b/netwerk/test/unit/test_obs-fold.js
new file mode 100644
index 0000000000..84915aa748
--- /dev/null
+++ b/netwerk/test/unit/test_obs-fold.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/. */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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..292fd68a9d
--- /dev/null
+++ b/netwerk/test/unit/test_offline_status.js
@@ -0,0 +1,19 @@
+"use strict";
+
+function run_test() {
+ var ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+
+ 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(ioService.offline, linkService.isLinkUp);
+ } catch (e) {
+ // The network link service might not be available
+ Assert.equal(ioService.offline, false);
+ }
+}
diff --git a/netwerk/test/unit/test_offlinecache_custom-directory.js b/netwerk/test/unit/test_offlinecache_custom-directory.js
new file mode 100644
index 0000000000..495399da95
--- /dev/null
+++ b/netwerk/test/unit/test_offlinecache_custom-directory.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/. */
+
+/**
+ * This test executes nsIOfflineCacheUpdateService.scheduleAppUpdate API
+ * 1. preloads an app with a manifest to a custom sudir in the profile (for simplicity)
+ * 2. observes progress and completion of the update
+ * 3. checks presence of index.sql and files in the expected location
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpServer = null;
+var cacheUpdateObserver = null;
+var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ });
+}
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.newURI(url);
+}
+
+// start the test with loading this master entry referencing the manifest
+function masterEntryHandler(metadata, response) {
+ var masterEntryContent = "<html manifest='/manifest'></html>";
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(
+ masterEntryContent,
+ masterEntryContent.length
+ );
+}
+
+// manifest defines fallback namespace from any /redirect path to /content
+function manifestHandler(metadata, response) {
+ var manifestContent = "CACHE MANIFEST\n";
+ response.setHeader("Content-Type", "text/cache-manifest");
+ response.bodyOutputStream.write(manifestContent, manifestContent.length);
+}
+
+// finally check we got fallback content
+function finish_test(customDir) {
+ var offlineCacheDir = customDir.clone();
+ offlineCacheDir.append("OfflineCache");
+
+ var indexSqlFile = offlineCacheDir.clone();
+ indexSqlFile.append("index.sqlite");
+ Assert.equal(indexSqlFile.exists(), true);
+
+ var file1 = offlineCacheDir.clone();
+ file1.append("2");
+ file1.append("E");
+ file1.append("2C99DE6E7289A5-0");
+ Assert.equal(file1.exists(), true);
+
+ var file2 = offlineCacheDir.clone();
+ file2.append("8");
+ file2.append("6");
+ file2.append("0B457F75198B29-0");
+ Assert.equal(file2.exists(), true);
+
+ // This must not throw an exception. After the update has finished
+ // the index file can be freely removed. This way we check this process
+ // is no longer keeping the file open. Check like this will probably
+ // work only Windows systems.
+
+ // This test could potentially randomaly fail when we start closing
+ // the offline cache database off the main thread. Tries in a loop
+ // may be a solution then.
+ try {
+ indexSqlFile.remove(false);
+ Assert.ok(true);
+ } catch (ex) {
+ do_throw(
+ "Could not remove the sqlite.index file, we still keep it open \n" +
+ ex +
+ "\n"
+ );
+ }
+
+ httpServer.stop(do_test_finished);
+}
+
+function run_test() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/masterEntry", masterEntryHandler);
+ httpServer.registerPathHandler("/manifest", manifestHandler);
+ httpServer.start(4444);
+
+ var profileDir = do_get_profile();
+ var customDir = profileDir.clone();
+ customDir.append("customOfflineCacheDir" + Math.random());
+
+ var pm = Cc["@mozilla.org/permissionmanager;1"].getService(
+ Ci.nsIPermissionManager
+ );
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ var uri = make_uri("http://localhost:4444");
+ var principal = ssm.createContentPrincipal(uri, {});
+
+ if (pm.testPermissionFromPrincipal(principal, "offline-app") != 0) {
+ dump(
+ "Previous test failed to clear offline-app permission! Expect failures.\n"
+ );
+ }
+ pm.addFromPrincipal(
+ principal,
+ "offline-app",
+ Ci.nsIPermissionManager.ALLOW_ACTION
+ );
+
+ var ps = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setBoolPref("browser.cache.offline.storage.enable", true);
+ // Set this pref to mimic the default browser behavior.
+ ps.setComplexValue(
+ "browser.cache.offline.parent_directory",
+ Ci.nsIFile,
+ profileDir
+ );
+
+ var us = Cc["@mozilla.org/offlinecacheupdate-service;1"].getService(
+ Ci.nsIOfflineCacheUpdateService
+ );
+ var update = us.scheduleAppUpdate(
+ make_uri("http://localhost:4444/manifest"),
+ make_uri("http://localhost:4444/masterEntry"),
+ systemPrincipal,
+ customDir
+ );
+
+ var expectedStates = [
+ Ci.nsIOfflineCacheUpdateObserver.STATE_DOWNLOADING,
+ Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMSTARTED,
+ Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMPROGRESS,
+ Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMCOMPLETED,
+ Ci.nsIOfflineCacheUpdateObserver.STATE_FINISHED,
+ ];
+
+ update.addObserver({
+ updateStateChanged(update, state) {
+ Assert.equal(state, expectedStates.shift());
+
+ if (state == Ci.nsIOfflineCacheUpdateObserver.STATE_FINISHED) {
+ finish_test(customDir);
+ }
+ },
+
+ applicationCacheAvailable(appCache) {},
+ });
+
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_origin.js b/netwerk/test/unit/test_origin.js
new file mode 100644
index 0000000000..a14fff8308
--- /dev/null
+++ b/netwerk/test/unit/test_origin.js
@@ -0,0 +1,330 @@
+"use strict";
+
+var h2Port;
+var prefs;
+var spdypref;
+var http2pref;
+var extpref;
+var loadGroup;
+
+function run_test() {
+ var env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ spdypref = prefs.getBoolPref("network.http.spdy.enabled");
+ http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2");
+ extpref = prefs.getBoolPref("network.http.originextension");
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", 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.spdy.enabled", spdypref);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", 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..f2851c725b
--- /dev/null
+++ b/netwerk/test/unit/test_original_sent_received_head.js
@@ -0,0 +1,246 @@
+//
+// 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.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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");
+ }
+
+ try {
+ var etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {
+ var 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_parse_content_type.js b/netwerk/test/unit/test_parse_content_type.js
new file mode 100644
index 0000000000..4abf5e4763
--- /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 = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
+
+ 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..bbef41218f
--- /dev/null
+++ b/netwerk/test/unit/test_partial_response_entry_size_smart_shrink.js
@@ -0,0 +1,104 @@
+/*
+
+ 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.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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();
+ var slice = responseBody.slice(0, 100);
+ response.bodyOutputStream.write(slice, slice.length);
+ response.finish();
+ } else {
+ var 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..4936e367f9
--- /dev/null
+++ b/netwerk/test/unit/test_permmgr.js
@@ -0,0 +1,131 @@
+// 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 = Cc["@mozilla.org/permissionmanager;1"].getService(
+ Ci.nsIPermissionManager
+ );
+
+ var ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+
+ var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+
+ // nsIPermissionManager implementation is an extension; don't fail if it's not there
+ if (!pm) {
+ return;
+ }
+
+ // put a few hosts in
+ for (var 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 (var 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 (var j = 0; j < perms.length; ++j) {
+ pm.removePermission(perms[j]);
+ }
+
+ // ... ensure each and every element is equal ...
+ for (var i = 0; i < hosts.length; ++i) {
+ for (var 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..60d947a31f
--- /dev/null
+++ b/netwerk/test/unit/test_ping_aboutnetworking.js
@@ -0,0 +1,105 @@
+/* -*- 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].tcp, 1);
+
+ do_test_finished();
+ });
+}
+
+function run_test() {
+ var ps = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ // 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_pinned_app_cache.js b/netwerk/test/unit/test_pinned_app_cache.js
new file mode 100644
index 0000000000..db8d9d6f04
--- /dev/null
+++ b/netwerk/test/unit/test_pinned_app_cache.js
@@ -0,0 +1,302 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/*
+ * This testcase performs 3 requests against the offline cache. They
+ * are
+ *
+ * - start_cache_nonpinned_app1()
+ *
+ * - Request nsOfflineCacheService to skip pages (4) of app1 on
+ * the cache storage.
+ *
+ * - The offline cache storage is empty at this monent.
+ *
+ * - start_cache_nonpinned_app2_for_partial()
+ *
+ * - Request nsOfflineCacheService to skip pages of app2 on the
+ * cache storage.
+ *
+ * - The offline cache storage has only enough space for one more
+ * additional page. Only first of pages is really in the cache.
+ *
+ * - start_cache_pinned_app2_for_success()
+ *
+ * - Request nsOfflineCacheService to skip pages of app2 on the
+ * cache storage.
+ *
+ * - The offline cache storage has only enough space for one
+ * additional page. But, this is a pinned request,
+ * nsOfflineCacheService will make more space for this request
+ * by discarding app1 (non-pinned)
+ *
+ */
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+const { PermissionTestUtils } = ChromeUtils.import(
+ "resource://testing-common/PermissionTestUtils.jsm"
+);
+
+// const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+const kNS_OFFLINECACHEUPDATESERVICE_CONTRACTID =
+ "@mozilla.org/offlinecacheupdate-service;1";
+
+const kManifest1 =
+ "CACHE MANIFEST\n" +
+ "/pages/foo1\n" +
+ "/pages/foo2\n" +
+ "/pages/foo3\n" +
+ "/pages/foo4\n";
+const kManifest2 =
+ "CACHE MANIFEST\n" +
+ "/pages/foo5\n" +
+ "/pages/foo6\n" +
+ "/pages/foo7\n" +
+ "/pages/foo8\n";
+
+const kDataFileSize = 1024; // file size for each content page
+const kCacheSize = kDataFileSize * 5; // total space for offline cache storage
+
+XPCOMUtils.defineLazyGetter(this, "kHttpLocation", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort + "/";
+});
+
+XPCOMUtils.defineLazyGetter(this, "kHttpLocation_ip", function() {
+ return "http://127.0.0.1:" + httpServer.identity.primaryPort + "/";
+});
+
+function manifest1_handler(metadata, response) {
+ info("manifest1\n");
+ response.setHeader("content-type", "text/cache-manifest");
+
+ response.write(kManifest1);
+}
+
+function manifest2_handler(metadata, response) {
+ info("manifest2\n");
+ response.setHeader("content-type", "text/cache-manifest");
+
+ response.write(kManifest2);
+}
+
+function app_handler(metadata, response) {
+ info("app_handler\n");
+ response.setHeader("content-type", "text/html");
+
+ response.write("<html></html>");
+}
+
+function datafile_handler(metadata, response) {
+ info("datafile_handler\n");
+ let data = "";
+
+ while (data.length < kDataFileSize) {
+ data =
+ data +
+ Math.random()
+ .toString(36)
+ .substring(2, 15);
+ }
+
+ response.setHeader("content-type", "text/plain");
+ response.write(data.substring(0, kDataFileSize));
+}
+
+var httpServer;
+
+function init_profile() {
+ var ps = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ dump(ps.getBoolPref("browser.cache.offline.enable"));
+ ps.setBoolPref("browser.cache.offline.enable", true);
+ ps.setBoolPref("browser.cache.offline.storage.enable", true);
+ ps.setComplexValue(
+ "browser.cache.offline.parent_directory",
+ Ci.nsIFile,
+ do_get_profile()
+ );
+}
+
+function init_http_server() {
+ httpServer = new HttpServer();
+ httpServer.registerPathHandler("/app1", app_handler);
+ httpServer.registerPathHandler("/app2", app_handler);
+ httpServer.registerPathHandler("/app1.appcache", manifest1_handler);
+ httpServer.registerPathHandler("/app2.appcache", manifest2_handler);
+ for (let i = 1; i <= 8; i++) {
+ httpServer.registerPathHandler("/pages/foo" + i, datafile_handler);
+ }
+ httpServer.start(-1);
+}
+
+function init_cache_capacity() {
+ let prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ prefs.setIntPref("browser.cache.offline.capacity", kCacheSize / 1024);
+}
+
+function clean_app_cache() {
+ evict_cache_entries("appcache");
+}
+
+function do_app_cache(manifestURL, pageURL, pinned) {
+ let update_service = Cc[kNS_OFFLINECACHEUPDATESERVICE_CONTRACTID].getService(
+ Ci.nsIOfflineCacheUpdateService
+ );
+
+ PermissionTestUtils.add(
+ manifestURL,
+ "pin-app",
+ pinned
+ ? Ci.nsIPermissionManager.ALLOW_ACTION
+ : Ci.nsIPermissionManager.DENY_ACTION
+ );
+
+ let update = update_service.scheduleUpdate(
+ manifestURL,
+ pageURL,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null
+ ); /* no window */
+
+ return update;
+}
+
+function watch_update(update, stateChangeHandler, cacheAvailHandler) {
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI([]),
+
+ updateStateChanged: stateChangeHandler,
+ applicationCacheAvailable: cacheAvailHandler,
+ };
+ update.addObserver(observer);
+
+ return update;
+}
+
+function start_and_watch_app_cache(
+ manifestURL,
+ pageURL,
+ pinned,
+ stateChangeHandler,
+ cacheAvailHandler
+) {
+ let ioService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+ let update = do_app_cache(
+ ioService.newURI(manifestURL),
+ ioService.newURI(pageURL),
+ pinned
+ );
+ watch_update(update, stateChangeHandler, cacheAvailHandler);
+ return update;
+}
+
+const {
+ STATE_FINISHED: STATE_FINISHED,
+ STATE_CHECKING: STATE_CHECKING,
+ STATE_ERROR: STATE_ERROR,
+} = Ci.nsIOfflineCacheUpdateObserver;
+
+/*
+ * Start caching app1 as a non-pinned app.
+ */
+function start_cache_nonpinned_app() {
+ info("Start non-pinned App1");
+ start_and_watch_app_cache(
+ kHttpLocation + "app1.appcache",
+ kHttpLocation + "app1",
+ false,
+ function(update, state) {
+ switch (state) {
+ case STATE_FINISHED:
+ start_cache_nonpinned_app2_for_partial();
+ break;
+
+ case STATE_ERROR:
+ do_throw("App1 cache state = " + state);
+ break;
+ }
+ },
+ function(appcache) {
+ info("app1 avail " + appcache + "\n");
+ }
+ );
+}
+
+/*
+ * Start caching app2 as a non-pinned app.
+ *
+ * This cache request is supposed to be saved partially in the cache
+ * storage for running out of the cache storage. The offline cache
+ * storage can hold 5 files at most. (kDataFileSize bytes for each
+ * file)
+ */
+function start_cache_nonpinned_app2_for_partial() {
+ info("Start non-pinned App2 for partial\n");
+ start_and_watch_app_cache(
+ kHttpLocation_ip + "app2.appcache",
+ kHttpLocation_ip + "app2",
+ false,
+ function(update, state) {
+ switch (state) {
+ case STATE_FINISHED:
+ start_cache_pinned_app2_for_success();
+ break;
+
+ case STATE_ERROR:
+ do_throw("App2 cache state = " + state);
+ break;
+ }
+ },
+ function(appcahe) {}
+ );
+}
+
+/*
+ * Start caching app2 as a pinned app.
+ *
+ * This request use IP address (127.0.0.1) as the host name instead of
+ * the one used by app1. Because, app1 is also pinned when app2 is
+ * pinned if they have the same host name (localhost).
+ */
+function start_cache_pinned_app2_for_success() {
+ let error_count = [0];
+ info("Start pinned App2 for success\n");
+ start_and_watch_app_cache(
+ kHttpLocation_ip + "app2.appcache",
+ kHttpLocation_ip + "app2",
+ true,
+ function(update, state) {
+ switch (state) {
+ case STATE_FINISHED:
+ Assert.ok(error_count[0] == 0, "Do not discard app1?");
+ httpServer.stop(do_test_finished);
+ break;
+
+ case STATE_ERROR:
+ info("STATE_ERROR\n");
+ error_count[0]++;
+ break;
+ }
+ },
+ function(appcache) {
+ info("app2 avail " + appcache + "\n");
+ }
+ );
+}
+
+function run_test() {
+ if (typeof _XPCSHELL_PROCESS == "undefined" || _XPCSHELL_PROCESS != "child") {
+ init_profile();
+ init_cache_capacity();
+ clean_app_cache();
+ }
+
+ init_http_server();
+ start_cache_nonpinned_app();
+ do_test_pending();
+}
diff --git a/netwerk/test/unit/test_plaintext_sniff.js b/netwerk/test/unit/test_plaintext_sniff.js
new file mode 100644
index 0000000000..fb9620e04c
--- /dev/null
+++ b/netwerk/test/unit/test_plaintext_sniff.js
@@ -0,0 +1,209 @@
+// Test the plaintext-or-binary sniffer
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+// 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..537d8d6f46
--- /dev/null
+++ b/netwerk/test/unit/test_port_remapping.js
@@ -0,0 +1,48 @@
+// 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.import("resource://testing-common/httpd.js");
+
+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..9424d53538
--- /dev/null
+++ b/netwerk/test/unit/test_post.js
@@ -0,0 +1,139 @@
+//
+// POST test
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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..9f9474a6cb
--- /dev/null
+++ b/netwerk/test/unit/test_predictor.js
@@ -0,0 +1,771 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+var running_single_process = false;
+
+var predictor = null;
+
+function is_child_process() {
+ return (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .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, appCache) {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable(entry, isnew, appCache, 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, appCache) {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+
+ onCacheEntryAvailable(entry, isNew, appCache, 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,
+ false
+ );
+
+ 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
+ );
+ });
+ });
+ });
+ });
+ });
+ });
+}
+
+function test_redirect() {
+ open_and_continue([redirect_inituri, redirect_targeturi], function() {
+ if (running_single_process) {
+ continue_test_redirect();
+ } else {
+ sendCommand("continue_test_redirect();");
+ }
+ });
+}
+
+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 origin = extract_origin(sruri);
+ if (!preconns.includes(origin)) {
+ preconns.push(origin);
+ }
+
+ sruri = newURI(subresources[2]);
+ 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);
+ }
+
+ 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 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,
+ // 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..baf79ecbf5
--- /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..bf8bae9201
--- /dev/null
+++ b/netwerk/test/unit/test_private_necko_channel.js
@@ -0,0 +1,56 @@
+//
+// Private channel test
+//
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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(
+ count
+ ) {
+ Assert.equal(count, 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..4aa8256f22
--- /dev/null
+++ b/netwerk/test/unit/test_progress.js
@@ -0,0 +1,132 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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 = 0x804b0006;
+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 = {
+ _listener: null,
+ _got_onstartrequest: false,
+ _got_onstatus_after_onstartrequest: false,
+ _last_callback_handled: 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;
+ },
+
+ 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, "localhost");
+ this.mStatus = status;
+ },
+
+ mStatus: 0,
+};
+
+function run_test() {
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+ var channel = setupChannel(testpath);
+ channel.asyncOpen(progressCallback);
+ do_test_pending();
+}
+
+function setupChannel(path) {
+ var chan = NetUtil.newChannel({
+ uri: URL + path,
+ loadUsingSystemPrincipal: true,
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ chan.notificationCallbacks = progressCallback;
+ return chan;
+}
+
+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);
+ httpserver.stop(do_test_finished);
+}
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..cf41769486
--- /dev/null
+++ b/netwerk/test/unit/test_protocolproxyservice-async-filters.js
@@ -0,0 +1,440 @@
+/* -*- 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 ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+
+/**
+ * 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(delegate, iid) {
+ return new TestProtocolHandler().QueryInterface(iid);
+ },
+ lockFactory(lock) {},
+};
+
+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..d72d552fc8
--- /dev/null
+++ b/netwerk/test/unit/test_protocolproxyservice.js
@@ -0,0 +1,1049 @@
+/* -*- 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 = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+
+/**
+ * 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(delegate, iid) {
+ return new TestProtocolHandler().QueryInterface(iid);
+ },
+ lockFactory(lock) {},
+};
+
+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 protocol_handler_test_1(pi) {
+ Assert.equal(pi, null);
+ prefs.setCharPref("network.proxy.autoconfig_url", "");
+ prefs.setIntPref("network.proxy.type", 0);
+
+ run_pac_cancel_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 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;
+
+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 chan = NetUtil.newChannel({
+ uri: "http://127.0.0.1:7247",
+ loadUsingSystemPrincipal: true,
+ });
+ 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
+ ) {
+ 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);
+ do_test_finished();
+}
+
+function run_test() {
+ register_test_protocol_handler();
+
+ // 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..fea774ba74
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-failover_canceled.js
@@ -0,0 +1,58 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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 prefserv = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefService
+ );
+ var prefs = prefserv.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..47a41ab2ff
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-failover_passing.js
@@ -0,0 +1,46 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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 prefserv = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefService
+ );
+ var prefs = prefserv.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..586ddb2d35
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-replace_canceled.js
@@ -0,0 +1,58 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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 prefserv = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefService
+ );
+ var prefs = prefserv.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..d27394d339
--- /dev/null
+++ b/netwerk/test/unit/test_proxy-replace_passing.js
@@ -0,0 +1,46 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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 prefserv = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefService
+ );
+ var prefs = prefserv.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_proxyconnect.js b/netwerk/test/unit/test_proxyconnect.js
new file mode 100644
index 0000000000..71120232c5
--- /dev/null
+++ b/netwerk/test/unit/test_proxyconnect.js
@@ -0,0 +1,358 @@
+/* -*- 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(socket, 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);
+
+ streamIn.asyncWait(connectHandler, 0, 0, threadManager.mainThread);
+ } catch (e) {
+ transport.close(Cr.NS_BINDING_ABORTED);
+ do_throw(e);
+ }
+}
+
+function stopListening(socket, status) {
+ 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 == 0) {
+ 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..e85301317b
--- /dev/null
+++ b/netwerk/test/unit/test_psl.js
@@ -0,0 +1,45 @@
+"use strict";
+
+var etld = Cc["@mozilla.org/network/effective-tld-service;1"].getService(
+ Ci.nsIEffectiveTLDService
+);
+
+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 ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ var uri = ios.newFileURI(file);
+ var scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(
+ Ci.mozIJSSubScriptLoader
+ );
+ var srvScope = {};
+ scriptLoader.loadSubScript(uri.spec, srvScope);
+}
+
+function checkPublicSuffix(host, expectedSuffix) {
+ var actualSuffix = null;
+ try {
+ actualSuffix = 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..9944988dda
--- /dev/null
+++ b/netwerk/test/unit/test_race_cache_with_network.js
@@ -0,0 +1,284 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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");
+
+ try {
+ var etag = metadata.getHeader("If-None-Match");
+ } catch (ex) {
+ var 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.
+ var 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.
+ var 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.
+ var 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.
+ var 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.
+ var channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ // Trigger network after 50 ms.
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
+ 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.
+ var 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_triggerNetwork(0);
+ executeSoon(() => {
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_triggerDelayedOpenCacheEntry();
+ });
+ });
+ 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.
+ var 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();
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(0);
+ });
+ yield undefined;
+ equal(gResponseCounter, 7);
+ equal(g200Counter, 3, "check number of 200 responses");
+ equal(g304Counter, 4, "check number of 304 responses");
+
+ // Load the cached handler so we don't need to revalidate
+ var 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
+ var 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;
+ var channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ // trigger network after 50 ms
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
+ 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.
+ var channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ do_timeout(50, function() {
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(0);
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_triggerDelayedOpenCacheEntry();
+ });
+ 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++) {
+ var channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(10);
+ // This may be racy. The delay was chosen because the distribution of net-cache
+ // results was around 25-25 on my machine.
+ do_timeout(i * 100, function() {
+ try {
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_triggerDelayedOpenCacheEntry();
+ } catch (e) {}
+ });
+
+ 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..a6c50287c0
--- /dev/null
+++ b/netwerk/test/unit/test_range_requests.js
@@ -0,0 +1,527 @@
+//
+// 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.import("resource://testing-common/httpd.js");
+
+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 decodedBody = [
+ 0x54,
+ 0x68,
+ 0x69,
+ 0x73,
+ 0x20,
+ 0x69,
+ 0x73,
+ 0x20,
+ 0x61,
+ 0x20,
+ 0x73,
+ 0x6c,
+ 0x69,
+ 0x67,
+ 0x68,
+ 0x74,
+ 0x6c,
+ 0x79,
+ 0x20,
+ 0x6c,
+ 0x6f,
+ 0x6e,
+ 0x67,
+ 0x65,
+ 0x72,
+ 0x20,
+ 0x74,
+ 0x65,
+ 0x73,
+ 0x74,
+ 0x0a,
+ 0x0a,
+];
+
+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) {
+ 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) {},
+
+ 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
+ var 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
+ var 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
+ var chan = make_channel("http://localhost:" + port + "/test_4");
+ chan.asyncOpen(new MyListener(received_partial_4));
+
+ // Case 5: conditional request-header set by client
+ var 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)
+ var chan = make_channel("http://localhost:" + port + "/test_6");
+ chan.asyncOpen(new MyListener(received_partial_6));
+
+ // Case 7: a basic positive test
+ var 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
+ var 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
+ var 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..b8cc069126
--- /dev/null
+++ b/netwerk/test/unit/test_rcwn_always_cache_new_content.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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");
+}
+
+let gIsFromCache = 0;
+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.
+ var 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.
+ var channel = make_channel("http://localhost:" + PORT + "/rcwn");
+ channel
+ .QueryInterface(Ci.nsIRaceCacheWithNetwork)
+ .test_delayCacheEntryOpeningBy(100000);
+ channel.asyncOpen(new ChannelListener(checkContent, null));
+ // Trigger network after 50 ms.
+ channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
+ yield undefined;
+ equal(gRequestCounter, 2);
+
+ // Simulate navigation back by specifying VALIDATE_NEVER flag.
+ var 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_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..c6a817ed5a
--- /dev/null
+++ b/netwerk/test/unit/test_redirect-caching_canceled.js
@@ -0,0 +1,62 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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();
+
+XPCOMUtils.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..2fe3643a8b
--- /dev/null
+++ b/netwerk/test/unit/test_redirect-caching_failure.js
@@ -0,0 +1,82 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+/*
+ * 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 (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
+ );
+}
+
+XPCOMUtils.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();
+
+XPCOMUtils.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..fb3cb311ab
--- /dev/null
+++ b/netwerk/test/unit/test_redirect-caching_passing.js
@@ -0,0 +1,54 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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();
+
+XPCOMUtils.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..fd3c47197c
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_baduri.js
@@ -0,0 +1,42 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+/*
+ * Test whether we fail bad URIs in HTTP redirect as CORRUPTED_CONTENT.
+ */
+
+var httpServer = null;
+
+var BadRedirectPath = "/BadRedirect";
+XPCOMUtils.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..fc0f0f268a
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_canceled.js
@@ -0,0 +1,48 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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();
+
+XPCOMUtils.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..baafdf021a
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_different-protocol.js
@@ -0,0 +1,54 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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();
+
+XPCOMUtils.defineLazyGetter(this, "randomURI", function() {
+ return URL + randomPath;
+});
+
+function inChildProcess() {
+ return (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
+ );
+}
+
+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..e9b85d1f46
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_failure.js
@@ -0,0 +1,60 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+/*
+ * 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 (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
+ );
+}
+
+XPCOMUtils.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();
+
+XPCOMUtils.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..7fc63a40ae
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_from_script.js
@@ -0,0 +1,249 @@
+/*
+ * 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.import("resource://testing-common/httpd.js");
+
+// 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;
+
+XPCOMUtils.defineLazyGetter(this, "port1", function() {
+ return httpServer.identity.primaryPort;
+});
+
+XPCOMUtils.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";
+XPCOMUtils.defineLazyGetter(this, "baitURI", function() {
+ return "http://localhost:" + port1 + baitPath;
+});
+var baitText = "you got the worm";
+
+var redirectedPath = "/switch";
+XPCOMUtils.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";
+XPCOMUtils.defineLazyGetter(this, "bait2URI", function() {
+ return "http://localhost:" + port1 + bait2Path;
+});
+
+XPCOMUtils.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";
+XPCOMUtils.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";
+XPCOMUtils.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() {
+ Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService)
+ .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 ioservice = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+ 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 = ioservice.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);
+ });
+}
+
+var redirector = 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..586a6c0966
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_from_script_after-open_passing.js
@@ -0,0 +1,249 @@
+/*
+ * 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.import("resource://testing-common/httpd.js");
+
+// 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;
+
+XPCOMUtils.defineLazyGetter(this, "port1", function() {
+ return httpServer.identity.primaryPort;
+});
+
+XPCOMUtils.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";
+XPCOMUtils.defineLazyGetter(this, "baitURI", function() {
+ return "http://localhost:" + port1 + baitPath;
+});
+var baitText = "you got the worm";
+
+var redirectedPath = "/switch";
+XPCOMUtils.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";
+XPCOMUtils.defineLazyGetter(this, "bait2URI", function() {
+ return "http://localhost:" + port1 + bait2Path;
+});
+
+XPCOMUtils.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";
+XPCOMUtils.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";
+XPCOMUtils.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() {
+ Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService)
+ .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 ioservice = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+ 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 = ioservice.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);
+ });
+}
+
+var redirector = 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..a4d7974115
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_history.js
@@ -0,0 +1,74 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+const ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+var randomPath = "/redirect/" + Math.random();
+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..08ef96a2cb
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_loop.js
@@ -0,0 +1,86 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+/*
+ * 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..3f4178663a
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_passing.js
@@ -0,0 +1,53 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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();
+
+XPCOMUtils.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..c127f7ffe1
--- /dev/null
+++ b/netwerk/test/unit/test_redirect_protocol_telemetry.js
@@ -0,0 +1,66 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+const { TelemetryTestUtils } = ChromeUtils.import(
+ "resource://testing-common/TelemetryTestUtils.jsm"
+);
+
+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_reentrancy.js b/netwerk/test/unit/test_reentrancy.js
new file mode 100644
index 0000000000..f06b9b2c44
--- /dev/null
+++ b/netwerk/test/unit/test_reentrancy.js
@@ -0,0 +1,107 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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..ecb67983bc
--- /dev/null
+++ b/netwerk/test/unit/test_referrer.js
@@ -0,0 +1,247 @@
+"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 = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+
+ 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), referer_uri);
+
+ // 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..c6b69a1050
--- /dev/null
+++ b/netwerk/test/unit/test_referrer_cross_origin.js
@@ -0,0 +1,319 @@
+/* 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);
+ }
+
+ 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");
+}
diff --git a/netwerk/test/unit/test_referrer_policy.js b/netwerk/test/unit/test_referrer_policy.js
new file mode 100644
index 0000000000..987d3b4bc1
--- /dev/null
+++ b/netwerk/test/unit/test_referrer_policy.js
@@ -0,0 +1,143 @@
+"use strict";
+
+const ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+function test_policy(test) {
+ info("Running test: " + test.toSource());
+
+ var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+ );
+ if (test.defaultReferrerPolicyPref !== undefined) {
+ prefs.setIntPref(
+ "network.http.referer.defaultPolicy",
+ test.defaultReferrerPolicyPref
+ );
+ } else {
+ prefs.setIntPref("network.http.referer.defaultPolicy", 3);
+ }
+
+ 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));
+}
diff --git a/netwerk/test/unit/test_reopen.js b/netwerk/test/unit/test_reopen.js
new file mode 100644
index 0000000000..74c20e9947
--- /dev/null
+++ b/netwerk/test/unit/test_reopen.js
@@ -0,0 +1,147 @@
+// This testcase verifies that channels can't be reopened
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=372486
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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,
+ // Commented by default as it relies on external ressources
+ //test_ftp_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) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return NetUtil.newChannel({
+ uri: ios.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);
+ });
+}
+
+// Uncomment test_ftp_channel in test_array to test this
+function test_ftp_channel() {
+ test_channel(function() {
+ return makeChan("ftp://ftp.mozilla.org/pub/mozilla.org/README");
+ });
+}
+
+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..2ad2c9a150
--- /dev/null
+++ b/netwerk/test/unit/test_reply_without_content_type.js
@@ -0,0 +1,150 @@
+//
+// 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.import("resource://testing-common/httpd.js");
+
+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 buffer = "";
+
+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..68ebf5d404
--- /dev/null
+++ b/netwerk/test/unit/test_resumable_channel.js
@@ -0,0 +1,424 @@
+/* Tests various aspects of nsIResumableChannel in combination with HTTP */
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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 0x80004001;
+ },
+};
+
+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..72785a5b4b
--- /dev/null
+++ b/netwerk/test/unit/test_resumable_truncate.js
@@ -0,0 +1,93 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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_safeoutputstream.js b/netwerk/test/unit/test_safeoutputstream.js
new file mode 100644
index 0000000000..e9aec3beed
--- /dev/null
+++ b/netwerk/test/unit/test_safeoutputstream.js
@@ -0,0 +1,72 @@
+/* -*- 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 = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .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..4ee0b08be2
--- /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.cookiemgr.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.cookiemgr.countCookiesFromHost("foo.com"), 20);
+
+ // 2) All expired, unique cookies exist.
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
+
+ // 3) Only one cookie remains, and it's the one with the highest expiration
+ // time.
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("baz.com"), 1);
+ let cookies = Services.cookiemgr.getCookiesFromHost("baz.com", {});
+ let cookie = cookies[0];
+ Assert.equal(cookie.expiry, futureExpiry + 40);
+
+ finish_test();
+}
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..3d9698060a
--- /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.cookiemgr.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.cookiemgr.countCookiesFromHost("foo.com"), 20);
+
+ // 2) All expired, unique cookies exist.
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
+
+ // 3) Only one cookie remains, and it's the one with the highest expiration
+ // time.
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("baz.com"), 1);
+ let cookies = Services.cookiemgr.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) {
+ let cookie = new Cookie(
+ "oh" + i,
+ "hai",
+ "foo.com",
+ "/",
+ futureExpiry,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema2db.insertCookie(cookie);
+ }
+ for (let i = 80; i < 100; ++i) {
+ let cookie = new Cookie(
+ "oh" + i,
+ "hai",
+ "cat.com",
+ "/",
+ futureExpiry,
+ now,
+ now + i,
+ false,
+ false,
+ false
+ );
+
+ schema2db.insertCookie(cookie);
+ }
+
+ // 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.cookiemgr.countCookiesFromHost("foo.com"), 40);
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("baz.com"), 1);
+ Assert.equal(Services.cookiemgr.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.cookiemgr.countCookiesFromHost("foo.com"), 40);
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("baz.com"), 1);
+ Assert.equal(Services.cookiemgr.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.cookiemgr.countCookiesFromHost("foo.com"), 40);
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("baz.com"), 1);
+ Assert.equal(Services.cookiemgr.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..46230b403f
--- /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.cookiemgr.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.cookiemgr.countCookiesFromHost("foo.com"), 20);
+
+ // 2) All expired, unique cookies exist.
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
+
+ // 3) Only one cookie remains, and it's the one with the highest expiration
+ // time.
+ Assert.equal(Services.cookiemgr.countCookiesFromHost("baz.com"), 1);
+ let cookies = Services.cookiemgr.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..6c3d777a59
--- /dev/null
+++ b/netwerk/test/unit/test_separate_connections.js
@@ -0,0 +1,102 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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_signature_extraction.js b/netwerk/test/unit/test_signature_extraction.js
new file mode 100644
index 0000000000..c2da8b568a
--- /dev/null
+++ b/netwerk/test/unit/test_signature_extraction.js
@@ -0,0 +1,215 @@
+/* -*- 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.defineModuleGetter(
+ this,
+ "FileUtils",
+ "resource://gre/modules/FileUtils.jsm"
+);
+ChromeUtils.defineModuleGetter(
+ this,
+ "NetUtil",
+ "resource://gre/modules/NetUtil.jsm"
+);
+ChromeUtils.defineModuleGetter(
+ this,
+ "FileTestUtils",
+ "resource://testing-common/FileTestUtils.jsm"
+);
+
+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(aSaver, aTarget) {
+ if (aOnTargetChangeFn) {
+ aOnTargetChangeFn(aTarget);
+ }
+ },
+ onSaveComplete: function BFSO_onSaveComplete(aSaver, 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..30fcddebb3
--- /dev/null
+++ b/netwerk/test/unit/test_simple.js
@@ -0,0 +1,69 @@
+//
+// Simple HTTP test: fetches page
+//
+
+// Note: sets Cc and Ci variables
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var httpserver = new HttpServer();
+var testpath = "/simple";
+var httpbody = "0123456789";
+var buffer = "";
+
+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..5df48b1f76
--- /dev/null
+++ b/netwerk/test/unit/test_socks.js
@@ -0,0 +1,519 @@
+"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 script = do_get_file(script);
+ var proc = new Process(bin);
+ var args = [script.path].concat(args);
+
+ proc.run(false, args, args.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 == 0) {
+ 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);
+ },
+
+ 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..73024dfa0b
--- /dev/null
+++ b/netwerk/test/unit/test_speculative_connect.js
@@ -0,0 +1,368 @@
+/* -*- 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_speculative_connect,
+ test_hostnames_resolving_to_local_addresses,
+ test_proxies_with_local_addresses,
+];
+
+var testDescription = [
+ "Expect pass with localhost",
+ "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_speculative_connect
+ *
+ * Tests a basic positive case using nsIOService.SpeculativeConnect:
+ * connecting to localhost.
+ */
+function test_speculative_connect() {
+ serv = new TestServer();
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ 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);
+}
+
+/* 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);
+ 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 = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+ );
+ 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);
+ 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 = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+ );
+ 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 = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+ 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..3c8b73cee2
--- /dev/null
+++ b/netwerk/test/unit/test_stale-while-revalidate_loop.js
@@ -0,0 +1,46 @@
+/*
+
+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();
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ const PORT = 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..110086faa1
--- /dev/null
+++ b/netwerk/test/unit/test_stale-while-revalidate_max-age-0.js
@@ -0,0 +1,111 @@
+/*
+
+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.import("resource://testing-common/httpd.js");
+
+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..5cc8278723
--- /dev/null
+++ b/netwerk/test/unit/test_stale-while-revalidate_negative.js
@@ -0,0 +1,90 @@
+/*
+
+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.import("resource://testing-common/httpd.js");
+
+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..d39d96087b
--- /dev/null
+++ b/netwerk/test/unit/test_stale-while-revalidate_positive.js
@@ -0,0 +1,111 @@
+/*
+
+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.import("resource://testing-common/httpd.js");
+
+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..f6d54bc9b1
--- /dev/null
+++ b/netwerk/test/unit/test_standardurl.js
@@ -0,0 +1,1299 @@
+"use strict";
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+const gPrefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+
+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 = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ .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 = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefService
+ );
+ 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() {
+ var 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 escape \r\n\t, not filter them.
+ var url = stringToURL("http://test.com/path?query#hash");
+ url = url
+ .mutate()
+ .setFilePath("pa\r\n\tth")
+ .finalize();
+ Assert.equal(url.spec, "http://test.com/pa%0D%0A%09th?query#hash");
+ url = url
+ .mutate()
+ .setQuery("que\r\n\try")
+ .finalize();
+ Assert.equal(url.spec, "http://test.com/pa%0D%0A%09th?query#hash");
+ url = url
+ .mutate()
+ .setRef("ha\r\n\tsh")
+ .finalize();
+ Assert.equal(url.spec, "http://test.com/pa%0D%0A%09th?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;
+ }
+ var 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.
+ var 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);
+
+ var 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://0xfffffffff/",
+ "http://0x100000000/",
+ "http://4294967296/",
+ "http://1.2.0x10000/",
+ "http://1.0x1000000/",
+ "http://256.0.0.1/",
+ "http://1.256.1/",
+ "http://-1.0.0.0/",
+ "http://1.2.3.4.5/",
+ "http://010000000000000000/",
+ "http://2+3/",
+ "http://0.0.0.-1/",
+ "http://1.2.3.4../",
+ "http://1..2/",
+ "http://.1.2.3.4/",
+ "resource://123/",
+ "resource://4294967296/",
+ ];
+ var spec;
+ for (spec of nonIPv4s) {
+ url = stringToURL(spec);
+ Assert.equal(url.spec, spec);
+ }
+
+ var 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++) {
+ 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_UNEXPECTED/,
+ "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_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..d6f9959450
--- /dev/null
+++ b/netwerk/test/unit/test_standardurl_default_port.js
@@ -0,0 +1,61 @@
+/* -*- 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..d8e11df90b
--- /dev/null
+++ b/netwerk/test/unit/test_standardurl_port.js
@@ -0,0 +1,71 @@
+"use strict";
+
+function run_test() {
+ function makeURI(aURLSpec, aCharset) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+ );
+ return ios.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..655512409a
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_before_connect.js
@@ -0,0 +1,99 @@
+"use strict";
+
+var CC = Components.Constructor;
+
+const ServerSocket = CC(
+ "@mozilla.org/network/server-socket;1",
+ "nsIServerSocket",
+ "init"
+);
+
+var obs = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+);
+
+var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+// 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();
+ var obs = Cc["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Ci.nsIObserverService);
+ 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..bd9a6626cd
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_on_authRetry.js
@@ -0,0 +1,276 @@
+// This file tests async handling of a channel suspended in DoAuthRetry
+// notifying http-on-modify-request and http-on-before-connect observers.
+"use strict";
+
+var CC = Components.Constructor;
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+// Turn off the authentication dialog blocking for this test.
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserv.identity.primaryPort;
+});
+
+XPCOMUtils.defineLazyGetter(this, "PORT", function() {
+ return httpserv.identity.primaryPort;
+});
+
+var obs = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+);
+
+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) {
+ var 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) {
+ var 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 ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ var principal = ssm.createContentPrincipal(ios.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..05efb846e5
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_on_examine.js
@@ -0,0 +1,77 @@
+// This file tests async handling of a channel suspended in http-on-modify-request.
+"use strict";
+
+var CC = Components.Constructor;
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var obs = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+);
+
+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) {
+ var obs = Cc["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Ci.nsIObserverService);
+ obs.removeObserver(this, "http-on-examine-response");
+ callback(subject.QueryInterface(Ci.nsIHttpChannel));
+ },
+ },
+ "http-on-examine-response"
+ );
+}
+
+function startChannelRequest(baseUrl, flags, callback) {
+ var chan = NetUtil.newChannel({
+ uri: baseUrl,
+ 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");
+
+ var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+ Assert.equal(cm.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..d01bd4f2ba
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_on_examine_merged_response.js
@@ -0,0 +1,212 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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.import("resource://testing-common/httpd.js");
+
+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);
+
+ var obs = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ 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 {
+ var obs = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+ );
+ 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..ca82897dc1
--- /dev/null
+++ b/netwerk/test/unit/test_suspend_channel_on_modified.js
@@ -0,0 +1,181 @@
+// This file tests async handling of a channel suspended in http-on-modify-request.
+"use strict";
+
+var CC = Components.Constructor;
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+var obs = Cc["@mozilla.org/observer-service;1"].getService(
+ Ci.nsIObserverService
+);
+
+var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+
+// 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) {
+ var obs = Cc["@mozilla.org/observer-service;1"].getService();
+ obs = obs.QueryInterface(Ci.nsIObserverService);
+ obs.removeObserver(this, "http-on-modify-request");
+ callback(subject.QueryInterface(Ci.nsIHttpChannel));
+ },
+ },
+ "http-on-modify-request"
+ );
+}
+
+function startChannelRequest(baseUrl, flags, expectedResponse = null) {
+ var chan = NetUtil.newChannel({
+ uri: baseUrl,
+ 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..91a491388e
--- /dev/null
+++ b/netwerk/test/unit/test_synthesized_response.js
@@ -0,0 +1,294 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpServer.identity.primaryPort;
+});
+
+var httpServer = null;
+
+function make_uri(url) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.newURI(url);
+}
+
+function isParentProcess() {
+ let appInfo = Cc["@mozilla.org/xre/app-info;1"];
+ return (
+ !appInfo ||
+ appInfo.getService(Ci.nsIXULRuntime).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.
+ Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(
+ Ci.nsICacheStorageService
+ );
+}
+
+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(chan) {
+ chan.resetInterception();
+ });
+ 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(chan) {
+ chan.resetInterception();
+ });
+ 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(chan) {
+ do_timeout(100, function() {
+ chan.resetInterception();
+ });
+ });
+ 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(chan) {
+ chan.resetInterception();
+ do_timeout(0, function() {
+ var gotexception = false;
+ try {
+ chan.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(chan) {
+ throw "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..c48d752091
--- /dev/null
+++ b/netwerk/test/unit/test_throttlechannel.js
@@ -0,0 +1,46 @@
+// Test nsIThrottledInputChannel interface.
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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..6e34cb668c
--- /dev/null
+++ b/netwerk/test/unit/test_throttling.js
@@ -0,0 +1,64 @@
+// Test nsIThrottledInputChannel interface.
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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..b00f2e92b6
--- /dev/null
+++ b/netwerk/test/unit/test_tldservice_nextsubdomain.js
@@ -0,0 +1,28 @@
+"use strict";
+
+function run_test() {
+ var tld = Cc["@mozilla.org/network/effective-tld-service;1"].getService(
+ Ci.nsIEffectiveTLDService
+ );
+
+ 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 = tld.getNextSubDomain(test.data);
+ Assert.equal(r, test.result);
+ } catch (e) {
+ Assert.ok(test.throw);
+ }
+ });
+}
diff --git a/netwerk/test/unit/test_tls_flags.js b/netwerk/test/unit/test_tls_flags.js
new file mode 100644
index 0000000000..669465560a
--- /dev/null
+++ b/netwerk/test/unit/test_tls_flags.js
@@ -0,0 +1,274 @@
+// -*- 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);
+
+function getCert() {
+ return new Promise((resolve, reject) => {
+ let certService = Cc[
+ "@mozilla.org/security/local-cert-service;1"
+ ].getService(Ci.nsILocalCertService);
+ certService.getOrCreateCert("tlsflags-test", {
+ handleCert(c, rv) {
+ if (rv) {
+ reject(rv);
+ return;
+ }
+ resolve(c);
+ },
+ });
+ });
+}
+
+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.securityInfo.QueryInterface(
+ 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);
+ let overrideBits =
+ Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+ Ci.nsICertOverrideService.ERROR_MISMATCH;
+ certOverrideService.rememberValidityOverride(
+ hostname,
+ port,
+ cert,
+ overrideBits,
+ 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 = await getCert();
+
+ // 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..1c629add35
--- /dev/null
+++ b/netwerk/test/unit/test_tls_flags_separate_connections.js
@@ -0,0 +1,119 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.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..e9d2bc38ce
--- /dev/null
+++ b/netwerk/test/unit/test_tls_server.js
@@ -0,0 +1,301 @@
+/* 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 { PromiseUtils } = ChromeUtils.import(
+ "resource://gre/modules/PromiseUtils.jsm"
+);
+const certService = Cc["@mozilla.org/security/local-cert-service;1"].getService(
+ Ci.nsILocalCertService
+);
+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 = Cc["@mozilla.org/preferences-service;1"].getService(
+ Ci.nsIPrefBranch
+);
+
+function getCert() {
+ return new Promise((resolve, reject) => {
+ certService.getOrCreateCert("tls-test", {
+ handleCert(c, rv) {
+ if (rv) {
+ reject(rv);
+ return;
+ }
+ resolve(c);
+ },
+ });
+ });
+}
+
+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.securityInfo.QueryInterface(
+ 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(status.peerCert.equals(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_ECDSA_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(input) {
+ NetUtil.asyncCopy(input, 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) {
+ let overrideBits =
+ Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+ Ci.nsICertOverrideService.ERROR_MISMATCH;
+ certOverrideService.rememberValidityOverride(
+ "127.0.0.1",
+ port,
+ cert,
+ overrideBits,
+ true
+ );
+}
+
+function startClient(port, cert, expectingAlert, tlsVersion) {
+ 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
+ );
+ let input;
+ let output;
+
+ let inputDeferred = PromiseUtils.defer();
+ let outputDeferred = PromiseUtils.defer();
+
+ let handler = {
+ onTransportStatus(transport, status) {
+ if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+ output.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ }
+ },
+
+ onInputStreamReady(input) {
+ try {
+ let data = NetUtil.readInputStreamToString(input, input.available());
+ equal(data, "HELLO", "Echoed data received");
+ input.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");
+ input.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");
+ input.close();
+ output.close();
+ inputDeferred.resolve();
+ return;
+ }
+ }
+ inputDeferred.reject(e);
+ }
+ },
+
+ onOutputStreamReady(output) {
+ try {
+ // Set the client certificate as appropriate.
+ if (cert) {
+ let clientSecInfo = transport.securityInfo;
+ let tlsControl = clientSecInfo.QueryInterface(Ci.nsISSLSocketControl);
+ tlsControl.clientCert = cert;
+ }
+
+ output.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.
+do_load_manifest("client_cert_chooser.manifest");
+
+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 = await getCert();
+ 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 ? cert : null,
+ 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..d1fad1ba68
--- /dev/null
+++ b/netwerk/test/unit/test_tls_server_multiple_clients.js
@@ -0,0 +1,152 @@
+/* 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 { PromiseUtils } = ChromeUtils.import(
+ "resource://gre/modules/PromiseUtils.jsm"
+);
+const certService = Cc["@mozilla.org/security/local-cert-service;1"].getService(
+ Ci.nsILocalCertService
+);
+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 getCert() {
+ return new Promise((resolve, reject) => {
+ certService.getOrCreateCert("tls-test", {
+ handleCert(c, rv) {
+ if (rv) {
+ reject(rv);
+ return;
+ }
+ resolve(c);
+ },
+ });
+ });
+}
+
+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.securityInfo.QueryInterface(
+ 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(input) {
+ NetUtil.asyncCopy(input, output);
+ },
+ },
+ 0,
+ 0,
+ Services.tm.currentThread
+ );
+ },
+ onStopListening() {},
+ };
+
+ tlsServer.setSessionTickets(false);
+
+ tlsServer.asyncListen(listener);
+
+ return tlsServer.port;
+}
+
+function storeCertOverride(port, cert) {
+ let overrideBits =
+ Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+ Ci.nsICertOverrideService.ERROR_MISMATCH;
+ certOverrideService.rememberValidityOverride(
+ "127.0.0.1",
+ port,
+ cert,
+ overrideBits,
+ true
+ );
+}
+
+function startClient(port) {
+ let transport = socketTransportService.createTransport(
+ ["ssl"],
+ "127.0.0.1",
+ port,
+ null
+ );
+ let input;
+ let output;
+
+ let inputDeferred = PromiseUtils.defer();
+ let outputDeferred = PromiseUtils.defer();
+
+ let handler = {
+ onTransportStatus(transport, status) {
+ if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+ output.asyncWait(handler, 0, 0, Services.tm.currentThread);
+ }
+ },
+
+ onInputStreamReady(input) {
+ try {
+ let data = NetUtil.readInputStreamToString(input, input.available());
+ equal(data, "HELLO", "Echoed data received");
+ input.close();
+ output.close();
+ inputDeferred.resolve();
+ } catch (e) {
+ inputDeferred.reject(e);
+ }
+ },
+
+ onOutputStreamReady(output) {
+ try {
+ output.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 = await getCert();
+ 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..8dc6ed95bb
--- /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.import("resource://testing-common/httpd.js");
+
+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() {
+ Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService)
+ .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..5c31b0b0ef
--- /dev/null
+++ b/netwerk/test/unit/test_trackingProtection_annotateChannels.js
@@ -0,0 +1,388 @@
+"use strict";
+
+const { UrlClassifierTestUtils } = ChromeUtils.import(
+ "resource://testing-common/UrlClassifierTestUtils.jsm"
+);
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+// 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..fa2d915c20
--- /dev/null
+++ b/netwerk/test/unit/test_trr.js
@@ -0,0 +1,2136 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const { MockRegistrar } = ChromeUtils.import(
+ "resource://testing-common/MockRegistrar.jsm"
+);
+const mainThread = Services.tm.currentThread;
+
+const gDefaultPref = Services.prefs.getDefaultBranch("");
+const gOverride = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+
+const defaultOriginAttributes = {};
+let h2Port = null;
+
+async function SetParentalControlEnabled(aEnabled) {
+ let parentalControlsService = {
+ parentalControlsEnabled: aEnabled,
+ QueryInterface: ChromeUtils.generateQI(["nsIParentalControlsService"]),
+ };
+ let cid = MockRegistrar.register(
+ "@mozilla.org/parental-controls-service;1",
+ parentalControlsService
+ );
+ dns.reloadParentalControlEnabled();
+ MockRegistrar.unregister(cid);
+}
+
+function setup() {
+ dump("start!\n");
+
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = 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.spdy.enabled", true);
+ Services.prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ // the TRR server is on 127.0.0.1
+ Services.prefs.setCharPref("network.trr.bootstrapAddress", "127.0.0.1");
+
+ // use the h2 server as DOH provider
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh`
+ );
+ // make all native resolve calls "secretly" resolve localhost instead
+ Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
+
+ // 0 - off, 1 - reserved, 2 - TRR first, 3 - TRR only, 4 - reserved
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR first
+ Services.prefs.setBoolPref("network.trr.wait-for-portal", false);
+ // By default wait for all responses before notifying the listeners.
+ Services.prefs.setBoolPref("network.trr.wait-for-A-and-AAAA", true);
+ // 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");
+
+ SetParentalControlEnabled(false);
+}
+
+setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+// This is an IP that is local, so we don't crash when connecting to it,
+// but also connecting to it fails, so we attempt to retry with regular DNS.
+const BAD_IP = (() => {
+ if (mozinfo.os == "linux" || mozinfo.os == "android") {
+ return "127.9.9.9";
+ }
+ return "0.0.0.0";
+})();
+
+class DNSListener {
+ constructor(
+ name,
+ expectedAnswer,
+ expectedSuccess = true,
+ delay,
+ trrServer = "",
+ expectEarlyFail = false,
+ flags = 0
+ ) {
+ this.name = name;
+ this.expectedAnswer = expectedAnswer;
+ this.expectedSuccess = expectedSuccess;
+ this.delay = delay;
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+
+ let resolverInfo =
+ trrServer == "" ? null : dns.newTRRResolverInfo(trrServer);
+ try {
+ this.request = dns.asyncResolve(
+ name,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ flags,
+ resolverInfo,
+ this,
+ mainThread,
+ defaultOriginAttributes
+ );
+ Assert.ok(!expectEarlyFail);
+ } catch (e) {
+ Assert.ok(expectEarlyFail);
+ this.resolve([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");
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ let answer = inRecord.getNextAddrAsString();
+ Assert.equal(
+ answer,
+ this.expectedAnswer,
+ `Checking result for ${this.name}`
+ );
+
+ 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);
+ }
+}
+
+add_task(async function test0_nodeExecute() {
+ // 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"));
+});
+
+function makeChan(url, mode) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ chan.setTRRMode(mode);
+ return chan;
+}
+
+add_task(
+ { skip_if: () => mozinfo.os == "mac" },
+ async function test_trr_flags() {
+ Services.prefs.setBoolPref("network.trr.fallback-on-zero-response", true);
+ 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}/`;
+
+ dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://localhost:${h2Port}/doh?responseIP=${BAD_IP}`
+ );
+
+ Services.prefs.setIntPref("network.trr.mode", 0);
+ dns.clearCache(true);
+ let chan = makeChan(URL, Ci.nsIRequest.TRR_DEFAULT_MODE);
+ await new Promise(resolve => chan.asyncOpen(new ChannelListener(resolve)));
+ equal(chan.getTRRMode(), Ci.nsIRequest.TRR_DEFAULT_MODE);
+ dns.clearCache(true);
+ chan = makeChan(URL, Ci.nsIRequest.TRR_DISABLED_MODE);
+ await new Promise(resolve => chan.asyncOpen(new ChannelListener(resolve)));
+ equal(chan.getTRRMode(), Ci.nsIRequest.TRR_DISABLED_MODE);
+ dns.clearCache(true);
+ chan = makeChan(URL, Ci.nsIRequest.TRR_FIRST_MODE);
+ await new Promise(resolve => chan.asyncOpen(new ChannelListener(resolve)));
+ equal(chan.getTRRMode(), Ci.nsIRequest.TRR_FIRST_MODE);
+ dns.clearCache(true);
+ chan = makeChan(
+ `http://example.com:${httpserv.identity.primaryPort}/`,
+ Ci.nsIRequest.TRR_ONLY_MODE
+ );
+ // Should fail as it tries to connect to local but unavailable IP
+ await new Promise(resolve =>
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE))
+ );
+ equal(chan.getTRRMode(), Ci.nsIRequest.TRR_ONLY_MODE);
+
+ dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://localhost:${h2Port}/doh?responseIP=${BAD_IP}`
+ );
+ Services.prefs.setIntPref("network.trr.mode", 2);
+
+ dns.clearCache(true);
+ chan = makeChan(URL, Ci.nsIRequest.TRR_DEFAULT_MODE);
+ // Does get the IP from TRR, but failure means it falls back to DNS.
+ await new Promise(resolve => chan.asyncOpen(new ChannelListener(resolve)));
+ dns.clearCache(true);
+ chan = makeChan(URL, Ci.nsIRequest.TRR_DISABLED_MODE);
+ await new Promise(resolve => chan.asyncOpen(new ChannelListener(resolve)));
+ dns.clearCache(true);
+ // Does get the IP from TRR, but failure means it falls back to DNS.
+ chan = makeChan(URL, Ci.nsIRequest.TRR_FIRST_MODE);
+ await new Promise(resolve => chan.asyncOpen(new ChannelListener(resolve)));
+ dns.clearCache(true);
+ chan = makeChan(URL, Ci.nsIRequest.TRR_ONLY_MODE);
+ await new Promise(resolve =>
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE))
+ );
+
+ dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://localhost:${h2Port}/doh?responseIP=${BAD_IP}`
+ );
+ Services.prefs.setIntPref("network.trr.mode", 3);
+
+ dns.clearCache(true);
+ chan = makeChan(URL, Ci.nsIRequest.TRR_DEFAULT_MODE);
+ await new Promise(resolve =>
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE))
+ );
+ dns.clearCache(true);
+ chan = makeChan(URL, Ci.nsIRequest.TRR_DISABLED_MODE);
+ await new Promise(resolve => chan.asyncOpen(new ChannelListener(resolve)));
+ dns.clearCache(true);
+ chan = makeChan(URL, Ci.nsIRequest.TRR_FIRST_MODE);
+ await new Promise(resolve => chan.asyncOpen(new ChannelListener(resolve)));
+ dns.clearCache(true);
+ chan = makeChan(URL, Ci.nsIRequest.TRR_ONLY_MODE);
+ await new Promise(resolve =>
+ chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE))
+ );
+
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 5);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://localhost:${h2Port}/doh?responseIP=1.1.1.1`
+ );
+
+ dns.clearCache(true);
+ chan = makeChan(URL, Ci.nsIRequest.TRR_DEFAULT_MODE);
+ await new Promise(resolve => chan.asyncOpen(new ChannelListener(resolve)));
+ dns.clearCache(true);
+ chan = makeChan(URL, Ci.nsIRequest.TRR_DISABLED_MODE);
+ await new Promise(resolve => chan.asyncOpen(new ChannelListener(resolve)));
+ dns.clearCache(true);
+ chan = makeChan(URL, Ci.nsIRequest.TRR_FIRST_MODE);
+ await new Promise(resolve => chan.asyncOpen(new ChannelListener(resolve)));
+ dns.clearCache(true);
+ chan = makeChan(URL, Ci.nsIRequest.TRR_ONLY_MODE);
+ await new Promise(resolve => chan.asyncOpen(new ChannelListener(resolve)));
+
+ await new Promise(resolve => httpserv.stop(resolve));
+ Services.prefs.clearUserPref("network.trr.fallback-on-zero-response");
+ }
+);
+
+// verify basic A record
+add_task(async function test1() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+
+ await new DNSListener("bar.example.com", "2.2.2.2");
+});
+
+// verify basic A record - without bootstrapping
+add_task(async function test1b() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+ Services.prefs.clearUserPref("network.trr.bootstrapAddress");
+ Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+
+ await new DNSListener("bar.example.com", "3.3.3.3");
+
+ Services.prefs.setCharPref("network.trr.bootstrapAddress", "127.0.0.1");
+ Services.prefs.clearUserPref("network.dns.localDomains");
+});
+
+// verify that the name was put in cache - it works with bad DNS URI
+add_task(async function test2() {
+ // Don't clear the cache. That is what we're checking.
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/404`
+ );
+
+ await new DNSListener("bar.example.com", "3.3.3.3");
+});
+
+// verify working credentials in DOH request
+add_task(async function test3() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=4.4.4.4&auth=true`
+ );
+ Services.prefs.setCharPref("network.trr.credentials", "user:password");
+
+ await new DNSListener("bar.example.com", "4.4.4.4");
+});
+
+// verify failing credentials in DOH request
+add_task(async function test4() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=4.4.4.4&auth=true`
+ );
+ Services.prefs.setCharPref("network.trr.credentials", "evil:person");
+
+ let [, , inStatus] = await new DNSListener(
+ "wrong.example.com",
+ undefined,
+ false
+ );
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+});
+
+// verify DOH push, part A
+add_task(async function test5() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=5.5.5.5&push=true`
+ );
+
+ await new DNSListener("first.example.com", "5.5.5.5");
+});
+
+add_task(async function test5b() {
+ // 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.
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/404`
+ );
+ dump("test5b - resolve push.example.org please\n");
+
+ await new DNSListener("push.example.org", "2018::2018");
+});
+
+// verify AAAA entry
+add_task(async function test6() {
+ Services.prefs.setBoolPref("network.trr.wait-for-A-and-AAAA", true);
+
+ dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.early-AAAA", true); // ignored when wait-for-A-and-AAAA is true
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2020:2020::2020&delayIPv4=100`
+ );
+ await new DNSListener("aaaa.example.com", "2020:2020::2020");
+
+ dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.early-AAAA", false); // ignored when wait-for-A-and-AAAA is true
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2020:2020::2030&delayIPv4=100`
+ );
+ await new DNSListener("aaaa.example.com", "2020:2020::2030");
+
+ dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.early-AAAA", true); // ignored when wait-for-A-and-AAAA is true
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2020:2020::2020&delayIPv6=100`
+ );
+ await new DNSListener("aaaa.example.com", "2020:2020::2020");
+
+ dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.early-AAAA", false); // ignored when wait-for-A-and-AAAA is true
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2020:2020::2030&delayIPv6=100`
+ );
+ await new DNSListener("aaaa.example.com", "2020:2020::2030");
+
+ dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.early-AAAA", true); // ignored when wait-for-A-and-AAAA is true
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2020:2020::2020`
+ );
+ await new DNSListener("aaaa.example.com", "2020:2020::2020");
+
+ dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.early-AAAA", false); // ignored when wait-for-A-and-AAAA is true
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2020:2020::2030`
+ );
+ await new DNSListener("aaaa.example.com", "2020:2020::2030");
+
+ Services.prefs.setBoolPref("network.trr.wait-for-A-and-AAAA", false);
+
+ dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.early-AAAA", true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2020:2020::2020&delayIPv4=100`
+ );
+ await new DNSListener("aaaa.example.com", "2020:2020::2020");
+
+ dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.early-AAAA", false);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2020:2020::2030&delayIPv4=100`
+ );
+ await new DNSListener("aaaa.example.com", "2020:2020::2030");
+
+ dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.early-AAAA", true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2020:2020::2020&delayIPv6=100`
+ );
+ await new DNSListener("aaaa.example.com", "2020:2020::2020");
+
+ dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.early-AAAA", false);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2020:2020::2030&delayIPv6=100`
+ );
+ await new DNSListener("aaaa.example.com", "2020:2020::2030");
+
+ dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.early-AAAA", true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2020:2020::2020`
+ );
+ await new DNSListener("aaaa.example.com", "2020:2020::2020");
+
+ dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.early-AAAA", false);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2020:2020::2030`
+ );
+ await new DNSListener("aaaa.example.com", "2020:2020::2030");
+
+ Services.prefs.clearUserPref("network.trr.early-AAAA");
+ Services.prefs.clearUserPref("network.trr.wait-for-A-and-AAAA");
+});
+
+// verify RFC1918 address from the server is rejected
+add_task(async function test7() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=192.168.0.1`
+ );
+ let [, , inStatus] = await new DNSListener(
+ "rfc1918.example.com",
+ undefined,
+ false
+ );
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=::ffff:192.168.0.1`
+ );
+ [, , inStatus] = await new DNSListener(
+ "rfc1918-ipv6.example.com",
+ undefined,
+ false
+ );
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+});
+
+// verify RFC1918 address from the server is fine when told so
+add_task(async function test8() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=192.168.0.1`
+ );
+ Services.prefs.setBoolPref("network.trr.allow-rfc1918", true);
+ await new DNSListener("rfc1918.example.com", "192.168.0.1");
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=::ffff:192.168.0.1`
+ );
+ await new DNSListener("rfc1918-ipv6.example.com", "::ffff:192.168.0.1");
+});
+
+// use GET and disable ECS (makes a larger request)
+// verify URI template cutoff
+add_task(async function test8b() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh{?dns}`
+ );
+ Services.prefs.clearUserPref("network.trr.allow-rfc1918");
+ Services.prefs.setBoolPref("network.trr.useGET", true);
+ Services.prefs.setBoolPref("network.trr.disable-ECS", true);
+ await new DNSListener("ecs.example.com", "5.5.5.5");
+});
+
+// use GET
+add_task(async function test9() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh`
+ );
+ Services.prefs.clearUserPref("network.trr.allow-rfc1918");
+ Services.prefs.setBoolPref("network.trr.useGET", true);
+ Services.prefs.setBoolPref("network.trr.disable-ECS", false);
+ await new DNSListener("get.example.com", "5.5.5.5");
+});
+
+// confirmationNS set without confirmed NS yet
+add_task(async function test10() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.clearUserPref("network.trr.useGET");
+ Services.prefs.clearUserPref("network.trr.disable-ECS");
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=1::ffff`
+ );
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+
+ await new DNSListener("skipConfirmationForMode3.example.com", "1::ffff");
+});
+
+// use a slow server and short timeout!
+add_task(async function test11() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/dns-750ms`
+ );
+ Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 10);
+ let [, , inStatus] = await new DNSListener(
+ "test11.example.com",
+ undefined,
+ false
+ );
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+});
+
+// gets an no answers back from DoH. Falls back to regular DNS
+add_task(async function test12() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setIntPref("network.trr.request_timeout_ms", 10);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=none`
+ );
+ Services.prefs.clearUserPref("network.trr.request_timeout_ms");
+ Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
+ await new DNSListener("confirm.example.com", "127.0.0.1");
+});
+
+// TRR-first gets a 404 back from DOH
+add_task(async function test13() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/404`
+ );
+ await new DNSListener("test13.example.com", "127.0.0.1");
+});
+
+// Test that MODE_RESERVED4 (previously MODE_SHADOW) is treated as TRR off.
+add_task(async function test14() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 4); // MODE_RESERVED4. Interpreted as TRR off.
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/404`
+ );
+ await new DNSListener("test14.example.com", "127.0.0.1");
+
+ Services.prefs.setIntPref("network.trr.mode", 4); // MODE_RESERVED4. Interpreted as TRR off.
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+ await new DNSListener("bar.example.com", "127.0.0.1");
+});
+
+// Test that MODE_RESERVED4 (previously MODE_SHADOW) is treated as TRR off.
+add_task(async function test15() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 4); // MODE_RESERVED4. Interpreted as TRR off.
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/dns-750ms`
+ );
+ Services.prefs.setIntPref("network.trr.request_timeout_ms", 10);
+ Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 10);
+ await new DNSListener("test15.example.com", "127.0.0.1");
+
+ Services.prefs.setIntPref("network.trr.mode", 4); // MODE_RESERVED4. Interpreted as TRR off.
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+ await new DNSListener("bar.example.com", "127.0.0.1");
+});
+
+// TRR-first and timed out TRR
+add_task(async function test16() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/dns-750ms`
+ );
+ Services.prefs.setIntPref("network.trr.request_timeout_ms", 10);
+ Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 10);
+ await new DNSListener("test16.example.com", "127.0.0.1");
+});
+
+// TRR-only and chase CNAME
+add_task(async function test17() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/dns-cname`
+ );
+ Services.prefs.clearUserPref("network.trr.request_timeout_ms");
+ Services.prefs.clearUserPref("network.trr.request_timeout_mode_trronly_ms");
+ await new DNSListener("cname.example.com", "99.88.77.66");
+});
+
+// TRR-only and a CNAME loop
+add_task(async function test18() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=none&cnameloop=true`
+ );
+ let [, , inStatus] = await new DNSListener(
+ "test18.example.com",
+ undefined,
+ false
+ );
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+});
+
+// Test that MODE_RESERVED1 (previously MODE_PARALLEL) is treated as TRR off.
+add_task(async function test19() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 1); // MODE_RESERVED1. Interpreted as TRR off.
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=none&cnameloop=true`
+ );
+ await new DNSListener("test19.example.com", "127.0.0.1");
+
+ Services.prefs.setIntPref("network.trr.mode", 1); // MODE_RESERVED1. Interpreted as TRR off.
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+ await new DNSListener("bar.example.com", "127.0.0.1");
+});
+
+// TRR-first and a CNAME loop
+add_task(async function test20() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=none&cnameloop=true`
+ );
+ await new DNSListener("test20.example.com", "127.0.0.1");
+});
+
+// Test that MODE_RESERVED4 (previously MODE_SHADOW) is treated as TRR off.
+add_task(async function test21() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 4); // MODE_RESERVED4. Interpreted as TRR off.
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=none&cnameloop=true`
+ );
+ await new DNSListener("test21.example.com", "127.0.0.1");
+
+ Services.prefs.setIntPref("network.trr.mode", 4); // MODE_RESERVED4. Interpreted as TRR off.
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+ await new DNSListener("bar.example.com", "127.0.0.1");
+});
+
+// verify that basic A record name mismatch gets rejected.
+// Gets a response for bar.example.com instead of what it requested
+add_task(async function test22() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only to avoid native fallback
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?hostname=bar.example.com`
+ );
+ let [, , inStatus] = await new DNSListener(
+ "mismatch.example.com",
+ undefined,
+ false
+ );
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+});
+
+// TRR-only, with a CNAME response with a bundled A record for that CNAME!
+add_task(async function test23() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/dns-cname-a`
+ );
+ await new DNSListener("cname-a.example.com", "9.8.7.6");
+});
+
+// TRR-first check that TRR result is used
+add_task(async function test24() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref("network.trr.excluded-domains", "");
+ Services.prefs.setCharPref("network.trr.builtin-excluded-domains", "");
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=192.192.192.192`
+ );
+ await new DNSListener("bar.example.com", "192.192.192.192");
+});
+
+// TRR-first check that DNS result is used if domain is part of the excluded-domains pref
+add_task(async function test24b() {
+ dns.clearCache(true);
+ Services.prefs.setCharPref("network.trr.excluded-domains", "bar.example.com");
+ await new DNSListener("bar.example.com", "127.0.0.1");
+});
+
+// TRR-first check that DNS result is used if domain is part of the excluded-domains pref
+add_task(async function test24c() {
+ dns.clearCache(true);
+ Services.prefs.setCharPref("network.trr.excluded-domains", "example.com");
+ await new DNSListener("bar.example.com", "127.0.0.1");
+});
+
+// TRR-first check that DNS result is used if domain is part of the excluded-domains pref that contains things before it.
+add_task(async function test24d() {
+ dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.excluded-domains",
+ "foo.test.com, bar.example.com"
+ );
+ await new DNSListener("bar.example.com", "127.0.0.1");
+});
+
+// TRR-first check that DNS result is used if domain is part of the excluded-domains pref that contains things after it.
+add_task(async function test24e() {
+ dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.excluded-domains",
+ "bar.example.com, foo.test.com"
+ );
+ await new DNSListener("bar.example.com", "127.0.0.1");
+});
+
+function observerPromise(topic) {
+ return new Promise(resolve => {
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ dump(`observe: ${aSubject}, ${aTopic}, ${aData} \n`);
+ if (aTopic == topic) {
+ Services.obs.removeObserver(observer, topic);
+ resolve(aData);
+ }
+ },
+ };
+ Services.obs.addObserver(observer, topic);
+ });
+}
+
+// TRR-first check that captivedetect.canonicalURL is resolved via native DNS
+add_task(async function test24f() {
+ dns.clearCache(true);
+
+ 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 = observerPromise("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 DNSListener("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));
+});
+
+// TRR-first check that a domain is resolved via native DNS when parental control is enabled.
+add_task(async function test24g() {
+ dns.clearCache(true);
+ await SetParentalControlEnabled(true);
+ await new DNSListener("www.example.com", "127.0.0.1");
+ await SetParentalControlEnabled(false);
+});
+
+// TRR-first check that DNS result is used if domain is part of the builtin-excluded-domains pref
+add_task(async function test24h() {
+ dns.clearCache(true);
+ Services.prefs.setCharPref("network.trr.excluded-domains", "");
+ Services.prefs.setCharPref(
+ "network.trr.builtin-excluded-domains",
+ "bar.example.com"
+ );
+ await new DNSListener("bar.example.com", "127.0.0.1");
+});
+
+// TRR-first check that DNS result is used if domain is part of the builtin-excluded-domains pref
+add_task(async function test24i() {
+ dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.builtin-excluded-domains",
+ "example.com"
+ );
+ await new DNSListener("bar.example.com", "127.0.0.1");
+});
+
+// TRR-first check that DNS result is used if domain is part of the builtin-excluded-domains pref that contains things before it.
+add_task(async function test24j() {
+ dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.builtin-excluded-domains",
+ "foo.test.com, bar.example.com"
+ );
+ await new DNSListener("bar.example.com", "127.0.0.1");
+});
+
+// TRR-first check that DNS result is used if domain is part of the builtin-excluded-domains pref that contains things after it.
+add_task(async function test24k() {
+ dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.builtin-excluded-domains",
+ "bar.example.com, foo.test.com"
+ );
+ await new DNSListener("bar.example.com", "127.0.0.1");
+});
+
+// TRR-only that resolving excluded with TRR-only mode will use the remote
+// resolver if it's not in the excluded domains
+add_task(async function test25() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref("network.trr.excluded-domains", "");
+ Services.prefs.setCharPref("network.trr.builtin-excluded-domains", "");
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=192.192.192.192`
+ );
+
+ await new DNSListener("excluded", "192.192.192.192", true);
+});
+
+// TRR-only check that excluded goes directly to native lookup when in the excluded-domains
+add_task(async function test25b() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref("network.trr.excluded-domains", "excluded");
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=192.192.192.192`
+ );
+
+ await new DNSListener("excluded", "127.0.0.1");
+});
+
+// TRR-only check that test.local is resolved via native DNS
+add_task(async function test25c() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref("network.trr.excluded-domains", "excluded,local");
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=192.192.192.192`
+ );
+
+ await new DNSListener("test.local", "127.0.0.1");
+});
+
+// TRR-only check that .other is resolved via native DNS when the pref is set
+add_task(async function test25d() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.excluded-domains",
+ "excluded,local,other"
+ );
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=192.192.192.192`
+ );
+
+ await new DNSListener("domain.other", "127.0.0.1");
+});
+
+// TRR-only check that captivedetect.canonicalURL is resolved via native DNS
+add_task({ skip_if: () => true }, async function test25e() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/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 = observerPromise("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 DNSListener("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));
+});
+
+// TRR-only check that a domain is resolved via native DNS when parental control is enabled.
+add_task(async function test25f() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ await SetParentalControlEnabled(true);
+ await new DNSListener("www.example.com", "127.0.0.1");
+ await SetParentalControlEnabled(false);
+});
+
+// TRR-only check that excluded goes directly to native lookup when in the builtin-excluded-domains
+add_task(async function test25g() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref("network.trr.excluded-domains", "");
+ Services.prefs.setCharPref(
+ "network.trr.builtin-excluded-domains",
+ "excluded"
+ );
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=192.192.192.192`
+ );
+
+ await new DNSListener("excluded", "127.0.0.1");
+});
+
+// TRR-only check that test.local is resolved via native DNS
+add_task(async function test25h() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.builtin-excluded-domains",
+ "excluded,local"
+ );
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=192.192.192.192`
+ );
+
+ await new DNSListener("test.local", "127.0.0.1");
+});
+
+// TRR-only check that .other is resolved via native DNS when the pref is set
+add_task(async function test25i() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.builtin-excluded-domains",
+ "excluded,local,other"
+ );
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=192.192.192.192`
+ );
+
+ await new DNSListener("domain.other", "127.0.0.1");
+});
+
+// Check that none of the requests have set any cookies.
+add_task(async function count_cookies() {
+ let cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
+ Assert.equal(cm.countCookiesFromHost("example.com"), 0);
+ Assert.equal(cm.countCookiesFromHost("foo.example.com."), 0);
+});
+
+add_task(async function test_connection_closed() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ 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);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+ // bootstrap
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.setCharPref("network.trr.bootstrapAddress", "127.0.0.1");
+
+ await new DNSListener("bar.example.com", "2.2.2.2");
+
+ // makes the TRR connection shut down.
+ let [, , inStatus] = await new DNSListener("closeme.com", undefined, false);
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+ await new DNSListener("bar2.example.com", "2.2.2.2");
+});
+
+add_task(async function test_connection_closed_no_bootstrap() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref("network.trr.excluded-domains", "excluded,local");
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+ Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ Services.prefs.clearUserPref("network.trr.bootstrapAddress");
+
+ await new DNSListener("bar.example.com", "3.3.3.3");
+
+ // makes the TRR connection shut down.
+ let [, , inStatus] = await new DNSListener("closeme.com", undefined, false);
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+ await new DNSListener("bar2.example.com", "3.3.3.3");
+});
+
+add_task(async function test_connection_closed_no_bootstrap_localhost() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref("network.trr.excluded-domains", "excluded");
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://localhost:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.trr.bootstrapAddress");
+
+ await new DNSListener("bar.example.com", "3.3.3.3");
+
+ // makes the TRR connection shut down.
+ let [, , inStatus] = await new DNSListener("closeme.com", undefined, false);
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+ await new DNSListener("bar2.example.com", "3.3.3.3");
+});
+
+add_task(async function test_connection_closed_no_bootstrap_no_excluded() {
+ // This test makes 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.
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref("network.trr.excluded-domains", "");
+ Services.prefs.setCharPref("network.trr.builtin-excluded-domains", "");
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://localhost:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.trr.bootstrapAddress");
+
+ await new DNSListener("bar.example.com", "3.3.3.3");
+
+ // makes the TRR connection shut down.
+ let [, , inStatus] = await new DNSListener("closeme.com", undefined, false);
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+ dns.clearCache(true);
+ await new DNSListener("bar2.example.com", "3.3.3.3");
+});
+
+add_task(async function test_connection_closed_trr_first() {
+ // 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
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://localhost:${h2Port}/doh?responseIP=9.9.9.9`
+ );
+ Services.prefs.setCharPref("network.dns.localDomains", "closeme.com");
+ Services.prefs.clearUserPref("network.trr.bootstrapAddress");
+
+ await new DNSListener("bar.example.com", "9.9.9.9");
+
+ // makes the TRR connection shut down. Should fallback to DNS
+ await new DNSListener("closeme.com", "127.0.0.1");
+ // TRR should be back up again
+ await new DNSListener("bar2.example.com", "9.9.9.9");
+});
+
+add_task(async function test_clearCacheOnURIChange() {
+ dns.clearCache(true);
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://localhost:${h2Port}/doh?responseIP=7.7.7.7`
+ );
+
+ await new DNSListener("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 DNSListener("bar.example.com", "8.8.8.8");
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+});
+
+add_task(async function test_dnsSuffix() {
+ async function checkDnsSuffixInMode(mode) {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", mode);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=1.2.3.4&push=true`
+ );
+ await new DNSListener("example.org", "1.2.3.4");
+ await new DNSListener("push.example.org", "2018::2018");
+ await new DNSListener("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 DNSListener("test.com", "1.2.3.4");
+ if (Services.prefs.getBoolPref("network.trr.split_horizon_mitigations")) {
+ await new DNSListener("example.org", "127.0.0.1");
+ // Also test that we don't use the pushed entry.
+ await new DNSListener("push.example.org", "127.0.0.1");
+ } else {
+ await new DNSListener("example.org", "1.2.3.4");
+ await new DNSListener("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.bootstrapAddress", "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.bootstrapAddress");
+});
+
+// Test AsyncResoleWithTrrServer.
+add_task(async function test_async_resolve_with_trr_server_1() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 0); // TRR-disabled
+
+ await new DNSListener(
+ "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 DNSListener("bar_with_trr1.example.com", "127.0.0.1");
+});
+
+// Test AsyncResoleWithTrrServer.
+add_task(async function test_async_resolve_with_trr_server_2() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+
+ await new DNSListener(
+ "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 DNSListener("bar_with_trr2.example.com", "2.2.2.2");
+});
+
+// Test AsyncResoleWithTrrServer.
+add_task(async function test_async_resolve_with_trr_server_3() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+
+ await new DNSListener(
+ "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 DNSListener("bar_with_trr3.example.com", "2.2.2.2");
+});
+
+// Test AsyncResoleWithTrrServer.
+add_task(async function test_async_resolve_with_trr_server_5() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 5); // TRR-user-disabled
+
+ // When dns is resolved in socket process, we can't set |expectEarlyFail| to true.
+ let inSocketProcess = mozinfo.socketprocess_networking;
+ await new DNSListener(
+ "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 DNSListener("bar_with_trr3.example.com", "127.0.0.1");
+});
+
+// Test AsyncResoleWithTrrServer.
+add_task(async function test_async_resolve_with_trr_server_different_cache() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+
+ await new DNSListener("bar_with_trr4.example.com", "2.2.2.2", true);
+
+ // The record will be fetch again.
+ await new DNSListener(
+ "bar_with_trr4.example.com",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3`
+ );
+});
+
+// Test AsyncResoleWithTrrServer.
+add_task(async function test_async_resolve_with_trr_server_different_servers() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+
+ await new DNSListener("bar_with_trr5.example.com", "2.2.2.2", true);
+
+ // The record will be fetch again.
+ await new DNSListener(
+ "bar_with_trr5.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 DNSListener(
+ "bar_with_trr5.example.com",
+ "4.4.4.4",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=4.4.4.4`
+ );
+});
+
+// Test AsyncResoleWithTrrServer.
+// AsyncResoleWithTrrServer will failed.
+// There will be no fallback to native and the host will not be blacklisted.
+add_task(async function test_async_resolve_with_trr_server_no_blacklist() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+
+ let [, , inStatus] = await new DNSListener(
+ "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 DNSListener("bar_with_trr6.example.com", "2.2.2.2", true);
+});
+
+// verify that DOH push does not work with AsyncResoleWithTrrServer.
+add_task(async function test_async_resolve_with_trr_server_no_push() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+
+ await new DNSListener(
+ "bar_with_trr7.example.com",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3&push=true`
+ );
+});
+
+add_task(async function test_async_resolve_with_trr_server_no_push_part_2() {
+ // AsyncResoleWithTrrServer rejects server pushes and the entry for push.example.org
+ // shouldn't be neither in the default cache not in AsyncResoleWithTrrServer cache.
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/404`
+ );
+ dump(
+ "test_async_resolve_with_trr_server_no_push_part_2 - resolve push.example.org will not be in the cache.\n"
+ );
+
+ await new DNSListener(
+ "push.example.org",
+ "3.3.3.3",
+ true,
+ undefined,
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.3.3.3&push=true`
+ );
+
+ await new DNSListener("push.example.org", "127.0.0.1");
+});
+
+// Verify that AsyncResoleWithTrrServer is not block on confirmationNS of the defaut serveer.
+add_task(async function test_async_resolve_with_trr_server_confirmation_ns() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-only
+ Services.prefs.clearUserPref("network.trr.useGET");
+ Services.prefs.clearUserPref("network.trr.disable-ECS");
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=1::ffff`
+ );
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+
+ // AsyncResoleWithTrrServer will succeed
+ await new DNSListener(
+ "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");
+});
+
+// verify TRR timings
+add_task(async function test_fetch_time() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2&delayIPv4=20`
+ );
+
+ await new DNSListener("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.
+add_task(async function test_no_fetch_time_on_trr_error() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/404&delayIPv4=20`
+ );
+
+ await new DNSListener("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.
+add_task(async function test_no_fetch_time_for_excluded_domains() {
+ dns.clearCache(true);
+ Services.prefs.setCharPref(
+ "network.trr.excluded-domains",
+ "bar_time2.example.com"
+ );
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2&delayIPv4=20`
+ );
+
+ await new DNSListener("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.
+add_task(async function test_no_fetch_time_for_rfc1918_not_allowed() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=192.168.0.1&delayIPv4=20`
+ );
+ await new DNSListener("rfc1918_time.example.com", "127.0.0.1", true, 0);
+});
+
+add_task(async function test_content_encoding_gzip() {
+ dns.clearCache(true);
+ Services.prefs.setBoolPref(
+ "network.trr.send_empty_accept-encoding_headers",
+ false
+ );
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+
+ await new DNSListener("bar.example.com", "2.2.2.2");
+});
+
+add_task(async function test_redirect_get() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3); // TRR-only
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?redirect=4.4.4.4{&dns}`
+ );
+ Services.prefs.clearUserPref("network.trr.allow-rfc1918");
+ Services.prefs.setBoolPref("network.trr.useGET", true);
+ Services.prefs.setBoolPref("network.trr.disable-ECS", true);
+ await new DNSListener("ecs.example.com", "4.4.4.4");
+});
+
+// test redirect
+add_task(async function test_redirect_post() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setBoolPref("network.trr.useGET", false);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?redirect=4.4.4.4`
+ );
+
+ await new DNSListener("bar.example.com", "4.4.4.4");
+});
+
+// confirmationNS set without confirmed NS yet
+// checks that we properly fall back to DNS is confirmation is not ready yet
+add_task(async function test_resolve_not_confirmed() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=7.7.7.7`
+ );
+ Services.prefs.setCharPref(
+ "network.trr.confirmationNS",
+ "confirm.example.com"
+ );
+
+ await new DNSListener("example.org", "127.0.0.1");
+
+ // 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 DNSListener(
+ `ip${count}.example.org`,
+ undefined,
+ false
+ );
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ let responseIP = inRecord.getNextAddrAsString();
+ if (responseIP == "7.7.7.7") {
+ break;
+ }
+ count--;
+ }
+
+ Assert.greater(count, 0, "Finished confirmation before 100 iterations");
+
+ Services.prefs.setCharPref("network.trr.confirmationNS", "skip");
+});
+
+// Tests that we handle FQDN encoding and decoding properly
+add_task(async function test_fqdn() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=9.8.7.6`
+ );
+ await new DNSListener("fqdn.example.org.", "9.8.7.6");
+});
+
+add_task(async function test_fqdn_get() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setBoolPref("network.trr.useGET", true);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=9.8.7.6`
+ );
+ await new DNSListener("fqdn_get.example.org.", "9.8.7.6");
+
+ Services.prefs.setBoolPref("network.trr.useGET", false);
+});
+
+add_task(async function test_detected_uri() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.clearUserPref("network.trr.uri");
+ let defaultURI = gDefaultPref.getCharPref("network.trr.uri");
+ gDefaultPref.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.4.5.6`
+ );
+ await new DNSListener("domainA.example.org.", "3.4.5.6");
+ dns.setDetectedTrrURI(
+ `https://foo.example.com:${h2Port}/doh?responseIP=1.2.3.4`
+ );
+ await new DNSListener("domainB.example.org.", "1.2.3.4");
+ gDefaultPref.setCharPref("network.trr.uri", defaultURI);
+});
+
+add_task(async function test_detected_uri_userSet() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=4.5.6.7`
+ );
+ await new DNSListener("domainA.example.org.", "4.5.6.7");
+ // This should be a no-op, since we have a user-set URI
+ dns.setDetectedTrrURI(
+ `https://foo.example.com:${h2Port}/doh?responseIP=1.2.3.4`
+ );
+ await new DNSListener("domainB.example.org.", "4.5.6.7");
+});
+
+add_task(async function test_detected_uri_link_change() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 3);
+ Services.prefs.clearUserPref("network.trr.uri");
+ let defaultURI = gDefaultPref.getCharPref("network.trr.uri");
+ gDefaultPref.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=3.4.5.6`
+ );
+ await new DNSListener("domainA.example.org.", "3.4.5.6");
+ dns.setDetectedTrrURI(
+ `https://foo.example.com:${h2Port}/doh?responseIP=1.2.3.4`
+ );
+ await new DNSListener("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 DNSListener("domainC.example.org.", "3.4.5.6");
+
+ gDefaultPref.setCharPref("network.trr.uri", defaultURI);
+});
+
+add_task(async function test_pref_changes() {
+ Services.prefs.clearUserPref("network.trr.uri");
+ let defaultURI = gDefaultPref.getCharPref("network.trr.uri");
+
+ async function doThenCheckURI(closure, expectedURI, expectChange = false) {
+ let uriChanged;
+ if (expectChange) {
+ uriChanged = observerPromise("network:trr-uri-changed");
+ }
+ closure();
+ if (expectChange) {
+ await uriChanged;
+ }
+ equal(dns.currentTrrURI, expectedURI);
+ }
+
+ // setting the default value of the pref should be reflected in the URI
+ await doThenCheckURI(() => {
+ gDefaultPref.setCharPref(
+ "network.trr.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(() => {
+ 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(
+ () => {
+ 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.uri", defaultURI);
+});
+
+add_task(async function test_async_resolve_with_trr_server_bad_port() {
+ dns.clearCache(true);
+ Services.prefs.setIntPref("network.trr.mode", 2); // TRR-first
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/doh?responseIP=2.2.2.2`
+ );
+
+ let [, , inStatus] = await new DNSListener(
+ "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(async function test_dohrollout_mode() {
+ Services.prefs.clearUserPref("network.trr.mode");
+ Services.prefs.clearUserPref("doh-rollout.mode");
+
+ equal(dns.currentTrrMode, 0);
+
+ async function doThenCheckMode(trrMode, rolloutMode, expectedMode, message) {
+ let modeChanged;
+ if (dns.currentTrrMode != expectedMode) {
+ modeChanged = observerPromise("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(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(dns.currentTrrMode, 2);
+ Services.prefs.clearUserPref("doh-rollout.mode");
+ equal(dns.currentTrrMode, 0);
+});
+
+add_task(async function test_ipv6_trr_fallback() {
+ dns.clearCache(true);
+ let httpserver = new HttpServer();
+ httpserver.registerPathHandler("/content", (metadata, response) => {
+ response.setHeader("Content-Type", "text/plain");
+ response.setHeader("Cache-Control", "no-cache");
+
+ const responseBody = "anybody";
+ response.bodyOutputStream.write(responseBody, responseBody.length);
+ });
+ httpserver.start(-1);
+
+ Services.prefs.setBoolPref("network.captive-portal-service.testMode", true);
+ let url = `http://127.0.0.1:666/doom_port_should_not_be_open`;
+ Services.prefs.setCharPref("network.connectivity-service.IPv6.url", url);
+ let ncs = Cc[
+ "@mozilla.org/network/network-connectivity-service;1"
+ ].getService(Ci.nsINetworkConnectivityService);
+
+ function promiseObserverNotification(topic, 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 });
+ }, topic);
+ });
+ }
+
+ let checks = promiseObserverNotification(
+ "network:connectivity-service:ip-checks-complete"
+ );
+
+ ncs.recheckIPConnectivity();
+
+ await checks;
+ equal(
+ ncs.IPv6,
+ Ci.nsINetworkConnectivityService.NOT_AVAILABLE,
+ "Check IPv6 support (expect NOT_AVAILABLE)"
+ );
+
+ Services.prefs.setIntPref("network.trr.mode", 2);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/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");
+
+ await new DNSListener(
+ "ipv6.host.com",
+ "1:1::2",
+ true,
+ 0,
+ "",
+ false,
+ Ci.nsIDNSService.RESOLVE_DISABLE_IPV4
+ );
+
+ Services.prefs.clearUserPref("network.captive-portal-service.testMode");
+ Services.prefs.clearUserPref("network.connectivity-service.IPv6.url");
+
+ override.clearOverrides();
+ await httpserver.stop();
+});
+
+add_task(async function test_no_retry_without_doh() {
+ // See 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) {
+ Services.prefs.setIntPref("network.trr.mode", 2);
+ Services.prefs.setCharPref(
+ "network.trr.uri",
+ `https://foo.example.com:${h2Port}/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[0x804b000b],
+ 1,
+ "Expecting only one instance of NS_NET_STATUS_RESOLVED_HOST"
+ );
+ equal(
+ statusCounter.statusCount[0x804b0007],
+ 1,
+ "Expecting only one instance of NS_NET_STATUS_CONNECTING_TO"
+ );
+ }
+
+ await test(`http://unknown.ipv4.stuff:666/path`, "0.0.0.0");
+ await test(`http://unknown.ipv6.stuff:666/path`, "::");
+});
+
+// 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() {
+ 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 DNSListener("cached.example.com", "3.3.3.3");
+ Services.prefs.clearUserPref("doh-rollout.mode");
+
+ await new DNSListener("cached.example.com", "127.0.0.1");
+
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+});
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..aff39d98df
--- /dev/null
+++ b/netwerk/test/unit/test_trr_additional_section.js
@@ -0,0 +1,314 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+let processId;
+
+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?bla=some</h1>");
+});
+
+add_task(async function test_parse_additional_section() {
+ 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",
+ [
+ {
+ name: "something.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ [
+ {
+ 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",
+ [
+ {
+ name: "a.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ [
+ {
+ name: "b.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "2.3.4.5",
+ },
+ ]
+ );
+ await trrServer.registerDoHAnswers("b.foo", "A", [
+ {
+ 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",
+ [
+ {
+ name: "xyz.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ [
+ {
+ 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",
+ [
+ {
+ name: "ipv6.foo",
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "2001::a:b:c:d",
+ },
+ ],
+ [
+ {
+ 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",
+ [
+ {
+ name: "ipv6b.foo",
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "2001::a:b:c:d",
+ },
+ ],
+ [
+ {
+ 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",
+ [
+ {
+ name: "multiple.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "9.9.9.9",
+ },
+ ],
+ [
+ {
+ // 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", [
+ {
+ 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",
+ [
+ {
+ name: "second.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ [
+ {
+ 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" });
+});
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..6880017c5c
--- /dev/null
+++ b/netwerk/test/unit/test_trr_case_sensitivity.js
@@ -0,0 +1,143 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+let processId;
+
+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?bla=some</h1>");
+
+ 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", [
+ {
+ 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", [
+ {
+ 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", [
+ {
+ 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", [
+ {
+ 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", [
+ {
+ 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", [
+ {
+ name: "B.test.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "9.9.9.9",
+ },
+ ]);
+ await trrServer.registerDoHAnswers("b.test.com", "A", [
+ {
+ 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", [
+ {
+ 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..8a70f6a9a3
--- /dev/null
+++ b/netwerk/test/unit/test_trr_cname_chain.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";
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+let processId;
+
+function channelOpenPromise(chan) {
+ return new Promise(resolve => {
+ function finish(req, buffer) {
+ resolve([req, buffer]);
+ }
+ chan.asyncOpen(new ChannelListener(finish));
+ });
+}
+
+add_task(async function test_follow_cnames_same_response() {
+ 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?bla=some</h1>");
+
+ 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", [
+ {
+ 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", [
+ {
+ name: "a.foo",
+ ttl: 55,
+ type: "CNAME",
+ flush: false,
+ data: "b.foo",
+ },
+ ]);
+ await trrServer.registerDoHAnswers("b.foo", "A", [
+ {
+ name: "b.foo",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "2.3.4.5",
+ },
+ ]);
+ await new TRRDNSListener("a.foo", { expectedAnswer: "2.3.4.5" });
+
+ await trrServer.stop();
+});
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..179a753fe6
--- /dev/null
+++ b/netwerk/test/unit/test_trr_extended_error.js
@@ -0,0 +1,340 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).QueryInterface(Ci.nsIHttpChannel);
+ return chan;
+}
+
+let processId;
+
+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?bla=some</h1>");
+
+ 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_bogus() {
+ await trrServer.registerDoHAnswers("something.foo", "A", [
+ {
+ 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",
+ [],
+ [
+ {
+ name: ".",
+ type: "OPT",
+ class: "IN",
+ options: [
+ {
+ code: "EDNS_ERROR",
+ extended_error: 6, // DNSSEC_BOGUS
+ text: "DNSSec bogus",
+ },
+ ],
+ },
+ ]
+ );
+
+ // 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",
+ [],
+ [
+ {
+ 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",
+ [],
+ [
+ {
+ 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", [
+ {
+ name: "delay1.com",
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "::a:b:c:d",
+ },
+ ]);
+ await trrServer.registerDoHAnswers(
+ "delay1.com",
+ "A",
+ [],
+ [
+ {
+ name: ".",
+ type: "OPT",
+ class: "IN",
+ options: [
+ {
+ code: "EDNS_ERROR",
+ extended_error: 17, // Filtered
+ text: "Filtered",
+ },
+ ],
+ },
+ ],
+ 200 // delay
+ );
+
+ // 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",
+ [
+ {
+ name: "delay2.com",
+ ttl: 55,
+ type: "AAAA",
+ flush: false,
+ data: "::a:b:c:d",
+ },
+ ],
+ [],
+ 200 // delay
+ );
+ await trrServer.registerDoHAnswers(
+ "delay2.com",
+ "A",
+ [],
+ [
+ {
+ 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", [
+ {
+ name: "delay3.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ]);
+ await trrServer.registerDoHAnswers(
+ "delay3.com",
+ "AAAA",
+ [],
+ [
+ {
+ name: ".",
+ type: "OPT",
+ class: "IN",
+ options: [
+ {
+ code: "EDNS_ERROR",
+ extended_error: 17, // Filtered
+ text: "Filtered",
+ },
+ ],
+ },
+ ],
+ 200 // delay
+ );
+
+ // 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",
+ [
+ {
+ name: "delay4.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ],
+ [],
+ 200 // delay
+ );
+ await trrServer.registerDoHAnswers(
+ "delay4.com",
+ "AAAA",
+ [],
+ [
+ {
+ 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",
+ [],
+ [
+ {
+ 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..4349149a10
--- /dev/null
+++ b/netwerk/test/unit/test_trr_https_fallback.js
@@ -0,0 +1,1090 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+let prefs;
+let h2Port;
+let h3Port;
+let listen;
+let trrServer;
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const mainThread = threadManager.currentThread;
+
+const defaultOriginAttributes = {};
+
+function setup() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = env.get("MOZHTTP2_PORT");
+ Assert.notEqual(h2Port, null);
+ Assert.notEqual(h2Port, "");
+
+ h3Port = env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+
+ // Set to allow the cert presented by our H2 server
+ do_get_profile();
+ prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.security.esni.enabled", false);
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ // the TRR server is on 127.0.0.1
+ prefs.setCharPref("network.trr.bootstrapAddress", "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", 2); // 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.
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+
+ 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);
+
+ // 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, "http2-ca.pem", "CTu,u,u");
+}
+
+setup();
+registerCleanupFunction(async () => {
+ prefs.clearUserPref("network.security.esni.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled.http2");
+ 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.bootstrapAddress");
+ prefs.clearUserPref("network.trr.request-timeout");
+ prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
+ prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+ prefs.clearUserPref("network.dns.echconfig.enabled");
+ prefs.clearUserPref("network.dns.echconfig.fallback_to_origin");
+ prefs.clearUserPref("network.dns.httpssvc.reset_exclustion_list");
+ prefs.clearUserPref("network.http.http3.enabled");
+ prefs.clearUserPref("network.dns.httpssvc.http3_fast_fallback_timeout");
+ if (trrServer) {
+ await trrServer.stop();
+ }
+});
+
+class DNSListener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+ // So we can await this as a promise.
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+
+DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIDNSListener",
+]);
+
+function makeChan(url) {
+ let chan = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true,
+ }).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", [
+ {
+ name: "test.fallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.fallback1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3"] },
+ { 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"] },
+ { 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"] },
+ { 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"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "test.fallback.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ 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.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", [
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.foo1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3"] },
+ { 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"] },
+ { 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"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ]);
+
+ await trrServer.registerDoHAnswers("test.foo.com", "A", [
+ {
+ name: "test.foo.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "test.foo.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ 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", false);
+
+ await trrServer.registerDoHAnswers("test.bar.com", "HTTPS", [
+ {
+ name: "test.bar.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.bar1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3"] },
+ { 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"] },
+ { 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"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "test.bar.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ // 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", [
+ {
+ name: "test.example.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.example1.com",
+ values: [{ key: "alpn", value: ["h2", "h3"] }],
+ },
+ },
+ {
+ name: "test.example.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 3,
+ name: "test.example3.com",
+ values: [{ key: "alpn", value: ["h2", "h3"] }],
+ },
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "test.example.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ 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", [
+ {
+ 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() {
+ 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", [
+ {
+ name: "vulnerable.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ]);
+
+ await trrServer.registerDoHAnswers("vulnerable.com", "HTTPS", [
+ {
+ name: "vulnerable.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "vulnerable1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3"] },
+ { 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"] },
+ { 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"] }],
+ },
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "vulnerable.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ 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", [
+ {
+ name: "test.reset.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.reset1.com",
+ values: [
+ { key: "alpn", value: ["h2", "h3"] },
+ { 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"] },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "test.reset.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ // 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", [
+ {
+ 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.enabled", true);
+
+ await trrServer.registerDoHAnswers("test.h3.com", "HTTPS", [
+ {
+ name: "test.h3.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "www.h3.com",
+ values: [
+ { key: "alpn", value: "h3-27" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ]);
+
+ await trrServer.registerDoHAnswers("www.h3.com", "A", [
+ {
+ name: "www.h3.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "test.h3.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ let chan = makeChan(`https://test.h3.com`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h3");
+ 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.enabled", 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
+ );
+
+ await trrServer.registerDoHAnswers("test.fastfallback.com", "HTTPS", [
+ {
+ name: "test.fastfallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "test.fastfallback1.com",
+ values: [
+ { key: "alpn", value: "h3-27" },
+ { 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.fastfallback2.com", "A", [
+ {
+ name: "test.fastfallback2.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "127.0.0.1",
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "test.fastfallback.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ 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();
+ 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.enabled", true);
+ Services.prefs.setIntPref(
+ "network.dns.httpssvc.http3_fast_fallback_timeout",
+ 0
+ );
+
+ await trrServer.registerDoHAnswers("test.h3.org", "HTTPS", [
+ {
+ name: "test.h3.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "www.h3.org",
+ values: [
+ { key: "alpn", value: "h3-27" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "test.h3.org",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ 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();
+ 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.enabled", 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-27=:" + 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", [
+ {
+ name: "test.h3_excluded.org",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "www.h3_fail.org",
+ values: [
+ { key: "alpn", value: "h3-27" },
+ { 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-27" },
+ { key: "port", value: h3Port },
+ ],
+ },
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "test.h3_excluded.org",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ chan = makeChan(`https://test.h3_excluded.org`);
+ let [req] = await channelOpenPromise(chan);
+ Assert.equal(req.protocolVersion, "h3");
+ 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();
+ 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.enabled", 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-27=:" + h3Port
+ );
+
+ await trrServer.registerDoHAnswers("www.h3_all_excluded.org", "A", [
+ {
+ 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-27=:" + 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", [
+ {
+ 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-27" },
+ { 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-27" },
+ { key: "port", value: h3Port },
+ { key: "echconfig", value: "456..." },
+ ],
+ },
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "www.h3_all_excluded.org",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ // 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", [
+ {
+ 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");
+ let internal = req.QueryInterface(Ci.nsIHttpChannelInternal);
+ Assert.equal(internal.remotePort, h3Port);
+
+ 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..5a398dc113
--- /dev/null
+++ b/netwerk/test/unit/test_trr_httpssvc.js
@@ -0,0 +1,669 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+let prefs;
+let h2Port;
+let listen;
+
+function inChildProcess() {
+ return (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
+ );
+}
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const mainThread = threadManager.currentThread;
+
+const defaultOriginAttributes = {};
+
+function setup() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ // the TRR server is on 127.0.0.1
+ prefs.setCharPref("network.trr.bootstrapAddress", "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", 2); // 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.
+ 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
+ );
+ addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
+}
+
+if (!inChildProcess()) {
+ setup();
+ registerCleanupFunction(() => {
+ prefs.clearUserPref("network.http.spdy.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled.http2");
+ 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.bootstrapAddress");
+ prefs.clearUserPref("network.trr.blacklist-duration");
+ prefs.clearUserPref("network.trr.request-timeout");
+ prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
+ });
+}
+
+class DNSListener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+ // So we can await this as a promise.
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+
+DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIDNSListener",
+]);
+
+add_task(async function testHTTPSSVC() {
+ // use the h2 server as DOH provider
+ if (!inChildProcess()) {
+ prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/httpssvc"
+ );
+ }
+
+ let listenerEsni = new DNSListener();
+ let request = dns.asyncResolve(
+ "test.httpssvc.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listenerEsni,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, inRecord, inStatus] = await listenerEsni;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+ 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() {
+ let trrServer = new TRRServer();
+ registerCleanupFunction(async () => {
+ await trrServer.stop();
+ });
+ 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`
+ );
+ }
+
+ // Test that HTTPS priority = 0 (AliasForm) behaves like a CNAME
+ await trrServer.registerDoHAnswers("test.com", "A", [
+ {
+ name: "test.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 0,
+ name: "something.com",
+ values: [],
+ },
+ },
+ ]);
+ await trrServer.registerDoHAnswers("something.com", "A", [
+ {
+ name: "something.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "1.2.3.4",
+ },
+ ]);
+
+ await new TRRDNSListener("test.com", { expectedAnswer: "1.2.3.4" });
+
+ // Test a chain of HTTPSSVC AliasForm and CNAMEs
+ await trrServer.registerDoHAnswers("x.com", "A", [
+ {
+ name: "x.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 0,
+ name: "y.com",
+ values: [],
+ },
+ },
+ ]);
+ await trrServer.registerDoHAnswers("y.com", "A", [
+ {
+ name: "y.com",
+ type: "CNAME",
+ ttl: 55,
+ class: "IN",
+ flush: false,
+ data: "z.com",
+ },
+ ]);
+ await trrServer.registerDoHAnswers("z.com", "A", [
+ {
+ name: "z.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 0,
+ name: "target.com",
+ values: [],
+ },
+ },
+ ]);
+ await trrServer.registerDoHAnswers("target.com", "A", [
+ {
+ name: "target.com",
+ ttl: 55,
+ type: "A",
+ flush: false,
+ data: "4.3.2.1",
+ },
+ ]);
+
+ await new TRRDNSListener("x.com", { expectedAnswer: "4.3.2.1" });
+
+ // We get a ServiceForm instead of a A answer, CNAME or AliasForm
+ await trrServer.registerDoHAnswers("no-ip-host.com", "A", [
+ {
+ 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" },
+ ],
+ },
+ },
+ ]);
+
+ let [, , 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", "A", [
+ {
+ name: "loop.com",
+ type: "CNAME",
+ ttl: 55,
+ class: "IN",
+ flush: false,
+ data: "loop2.com",
+ },
+ ]);
+ await trrServer.registerDoHAnswers("loop2.com", "A", [
+ {
+ name: "loop2.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 0,
+ name: "loop.com",
+ values: [],
+ },
+ },
+ ]);
+
+ [, , inStatus] = await new TRRDNSListener("loop.com", {
+ expectedSuccess: false,
+ });
+ Assert.ok(
+ !Components.isSuccessCode(inStatus),
+ `${inStatus} should be an error code`
+ );
+
+ // Alias form for .
+ await trrServer.registerDoHAnswers("empty.com", "A", [
+ {
+ 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", [
+ {
+ 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 listener = new DNSListener();
+ let request = dns.asyncResolve(
+ "multi.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, inRecord, inStatus2] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.ok(
+ !Components.isSuccessCode(inStatus2),
+ `${inStatus2} should be an error code`
+ );
+
+ // the svcparam keys are in reverse order
+ await trrServer.registerDoHAnswers("order.com", "HTTPS", [
+ {
+ 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"] },
+ ],
+ },
+ },
+ ]);
+
+ listener = new DNSListener();
+ request = dns.asyncResolve(
+ "order.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ [inRequest, inRecord, inStatus2] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.ok(
+ !Components.isSuccessCode(inStatus2),
+ `${inStatus2} should be an error code`
+ );
+
+ // duplicate svcparam keys
+ await trrServer.registerDoHAnswers("duplicate.com", "HTTPS", [
+ {
+ 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"] },
+ ],
+ },
+ },
+ ]);
+
+ listener = new DNSListener();
+ request = dns.asyncResolve(
+ "duplicate.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ [inRequest, inRecord, inStatus2] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.ok(
+ !Components.isSuccessCode(inStatus2),
+ `${inStatus2} should be an error code`
+ );
+
+ // mandatory svcparam
+ await trrServer.registerDoHAnswers("mandatory.com", "HTTPS", [
+ {
+ 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" },
+ ],
+ },
+ },
+ ]);
+
+ listener = new DNSListener();
+ request = dns.asyncResolve(
+ "mandatory.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ [inRequest, inRecord, inStatus2] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.ok(!Components.isSuccessCode(inStatus2), `${inStatus2} should fail`);
+
+ // mandatory svcparam
+ await trrServer.registerDoHAnswers("mandatory2.com", "HTTPS", [
+ {
+ 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" },
+ ],
+ },
+ },
+ ]);
+
+ listener = new DNSListener();
+ request = dns.asyncResolve(
+ "mandatory2.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ [inRequest, inRecord, inStatus2] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.ok(Components.isSuccessCode(inStatus2), `${inStatus2} should succeed`);
+
+ // alias-mode with . targetName
+ await trrServer.registerDoHAnswers("no-alias.com", "HTTPS", [
+ {
+ name: "no-alias.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 0,
+ name: ".",
+ values: [],
+ },
+ },
+ ]);
+
+ listener = new DNSListener();
+ request = dns.asyncResolve(
+ "no-alias.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ [inRequest, inRecord, inStatus2] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.ok(!Components.isSuccessCode(inStatus2), `${inStatus2} should fail`);
+
+ // service-mode with . targetName
+ await trrServer.registerDoHAnswers("service.com", "HTTPS", [
+ {
+ name: "service.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: ".",
+ values: [{ key: "alpn", value: ["h2", "h3"] }],
+ },
+ },
+ ]);
+
+ listener = new DNSListener();
+ request = dns.asyncResolve(
+ "service.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ [inRequest, inRecord, inStatus2] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.ok(Components.isSuccessCode(inStatus2), `${inStatus2} should work`);
+ let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records;
+ Assert.equal(answer[0].priority, 1);
+ Assert.equal(answer[0].name, "service.com");
+});
diff --git a/netwerk/test/unit/test_trr_nat64.js b/netwerk/test/unit/test_trr_nat64.js
new file mode 100644
index 0000000000..fd48c1cbdf
--- /dev/null
+++ b/netwerk/test/unit/test_trr_nat64.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";
+
+const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
+ Ci.nsINativeDNSResolverOverride
+);
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+
+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, topic, 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;
+}
+
+let processId;
+
+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?bla=some</h1>");
+ dns.clearCache(true);
+ override.addIPOverride("ipv4only.arpa", "fe80::9b2b:c000:00aa");
+ Services.prefs.setCharPref(
+ "network.connectivity-service.nat64-prefix",
+ "ae80::3b1b:c343:1133"
+ );
+
+ let notification = promiseObserverNotification(
+ "network:connectivity-service:dns-checks-complete"
+ );
+ 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", [
+ {
+ 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_proxy.js b/netwerk/test/unit/test_trr_proxy.js
new file mode 100644
index 0000000000..c091239e8b
--- /dev/null
+++ b/netwerk/test/unit/test_trr_proxy.js
@@ -0,0 +1,133 @@
+/* globals dnsResolve */
+
+/* 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.import("resource://testing-common/httpd.js");
+const { MockRegistrar } = ChromeUtils.import(
+ "resource://testing-common/MockRegistrar.jsm"
+);
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+
+trr_test_setup();
+registerCleanupFunction(async () => {
+ trr_clear_prefs();
+});
+
+function FindProxyForURL(url, host) {
+ alert(`PAC resolving: ${host}`);
+ alert(dnsResolve(host));
+ return "DIRECT";
+}
+
+const CID = Components.ID("{5645d2c1-d6d8-4091-b117-fe7ee4027db7}");
+XPCOMUtils.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
+);
+
+add_task(async function test_pac_dnsResolve() {
+ 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 env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ let h2Port = 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`
+ );
+
+ 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();
+});
diff --git a/netwerk/test/unit/test_udp_multicast.js b/netwerk/test/unit/test_udp_multicast.js
new file mode 100644
index 0000000000..76d2c93ba9
--- /dev/null
+++ b/netwerk/test/unit/test_udp_multicast.js
@@ -0,0 +1,114 @@
+// 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;
+
+const ua = Cc["@mozilla.org/network/protocol;1?name=http"].getService(
+ Ci.nsIHttpProtocolHandler
+).userAgent;
+
+var gConverter;
+
+function run_test() {
+ setup();
+ run_next_test();
+}
+
+function setup() {
+ gConverter = Cc[
+ "@mozilla.org/intl/scriptableunicodeconverter"
+ ].createInstance(Ci.nsIScriptableUnicodeConverter);
+ gConverter.charset = "utf8";
+}
+
+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 = gConverter.convertToByteArray(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(socket, 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")
+ );
+});
+
+add_test(() => {
+ 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
+ );
+});
+
+add_test(() => {
+ 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..8f6e351f7e
--- /dev/null
+++ b/netwerk/test/unit/test_udpsocket_offline.js
@@ -0,0 +1,108 @@
+/* -*- 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);
+ }
+
+ 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);
+ }
+
+ run_next_test();
+});
+
+function run_test() {
+ // jshint ignore:line
+ Services.io.offline = true;
+ registerCleanupFunction(() => {
+ Services.io.offline = false;
+ });
+ 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..a791983cb7
--- /dev/null
+++ b/netwerk/test/unit/test_unescapestring.js
@@ -0,0 +1,34 @@
+"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() {
+ var util = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
+
+ for (var i = 0; i < tests.length; ++i) {
+ dump("Test " + i + " (" + tests[i][0] + ", " + tests[i][2] + ")\n");
+ Assert.equal(util.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..0172c034ef
--- /dev/null
+++ b/netwerk/test/unit/test_unix_domain.js
@@ -0,0 +1,705 @@
+// 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 IOService = Cc["@mozilla.org/network/io-service;1"].getService(
+ Ci.nsIIOService
+);
+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("directory-that-does-not-exist");
+ 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(aStream) {
+ 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(aStream) {
+ 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) {
+ IOService.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..dca1343474
--- /dev/null
+++ b/netwerk/test/unit/test_use_httpssvc.js
@@ -0,0 +1,325 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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";
+
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+let prefs;
+let h2Port;
+let listen;
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+].getService(Ci.nsICertOverrideService);
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const mainThread = threadManager.currentThread;
+
+const defaultOriginAttributes = {};
+
+function setup() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ // the TRR server is on 127.0.0.1
+ prefs.setCharPref("network.trr.bootstrapAddress", "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", 2); // 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.
+ Services.prefs.setBoolPref("network.trr.clear-cache-on-pref-change", false);
+
+ Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
+ Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
+
+ // 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, "http2-ca.pem", "CTu,u,u");
+}
+
+setup();
+registerCleanupFunction(() => {
+ prefs.clearUserPref("network.http.spdy.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled.http2");
+ 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.bootstrapAddress");
+ prefs.clearUserPref("network.trr.blacklist-duration");
+ prefs.clearUserPref("network.trr.request-timeout");
+ prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
+ prefs.clearUserPref("network.dns.upgrade_with_https_rr");
+ prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
+});
+
+class DNSListener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+ // So we can await this as a promise.
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+
+DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIDNSListener",
+]);
+
+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]);
+ }
+ 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
+ prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/httpssvc_as_altsvc"
+ );
+ 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;
+ }
+ }
+ 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
+ prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/httpssvc_as_altsvc"
+ );
+ 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
+ prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/httpssvc_as_altsvc"
+ );
+
+ let listener = new DNSListener();
+
+ // Do DNS resolution before creating the channel, so the HTTPSSVC record will
+ // be resolved from the cache.
+ let request = dns.asyncResolve(
+ "test.httpssvc.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, , inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ // 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", [
+ {
+ 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", [
+ {
+ name: "test.fallback.com",
+ ttl: 55,
+ type: "HTTPS",
+ flush: false,
+ data: {
+ priority: 1,
+ name: "foo.example.com",
+ values: [{ key: "port", value: 8888 }],
+ },
+ },
+ ]);
+
+ let listener = new DNSListener();
+
+ let request = dns.asyncResolve(
+ "test.fallback.com",
+ dns.RESOLVE_TYPE_HTTPSSVC,
+ 0,
+ null, // resolverInfo
+ listener,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, inRecord, inStatus] = await listener;
+ Assert.equal(inRequest, request, "correct request was used");
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+
+ 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_websocket_offline.js b/netwerk/test/unit/test_websocket_offline.js
new file mode 100644
index 0000000000..35aafddda8
--- /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_xmlhttprequest.js b/netwerk/test/unit/test_xmlhttprequest.js
new file mode 100644
index 0000000000..06dfbb7b69
--- /dev/null
+++ b/netwerk/test/unit/test_xmlhttprequest.js
@@ -0,0 +1,55 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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/xpcshell.ini b/netwerk/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..d584907489
--- /dev/null
+++ b/netwerk/test/unit/xpcshell.ini
@@ -0,0 +1,517 @@
+[DEFAULT]
+head = head_channels.js head_cache.js head_cache2.js head_cookies.js head_trr.js head_http3.js
+support-files =
+ http2-ca.pem
+ client_cert_chooser.js
+ client_cert_chooser.manifest
+ 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
+
+[test_trr_nat64.js]
+skip-if = os == "android" || socketprocess_networking
+run-sequentially = node server exceptions dont replay well
+[test_nsIBufferedOutputStream_writeFrom_block.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-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_partial_response_entry_size_smart_shrink.js]
+[test_304_responses.js]
+[test_421.js]
+[test_cacheForOfflineUse_no-store.js]
+[test_307_redirect.js]
+[test_NetUtil.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_aboutblank.js]
+[test_auth_jar.js]
+[test_auth_proxy.js]
+[test_authentication.js]
+[test_authpromptwrapper.js]
+[test_auth_dialog_permission.js]
+[test_backgroundfilesaver.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_bug365133.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_bug412945.js]
+[test_bug414122.js]
+[test_bug427957.js]
+[test_bug429347.js]
+[test_bug455311.js]
+[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_bug484684.js]
+[test_bug490095.js]
+[test_bug504014.js]
+[test_bug510359.js]
+[test_bug515583.js]
+[test_bug526789.js]
+[test_bug528292.js]
+[test_bug536324_64bit_content_length.js]
+[test_bug540566.js]
+[test_bug543805.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_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_bug1218029.js]
+[test_udpsocket.js]
+[test_udpsocket_offline.js]
+[test_doomentry.js]
+[test_cacheflags.js]
+[test_cache_jar.js]
+[test_cache-entry-id.js]
+[test_channel_close.js]
+[test_compareURIs.js]
+[test_compressappend.js]
+[test_content_encoding_gzip.js]
+[test_content_sniffer.js]
+[test_cookie_header.js]
+[test_cookiejars.js]
+[test_cookiejars_safebrowsing.js]
+[test_cookies_async_failure.js]
+[test_cookies_persistence.js]
+skip-if = true # Bug 863738
+[test_cookies_privatebrowsing.js]
+[test_cookies_profile_close.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_dns_cancel.js]
+[test_data_protocol.js]
+[test_dns_service.js]
+[test_dns_offline.js]
+skip-if = socketprocess_networking # Bug 1640105
+[test_dns_onion.js]
+[test_dns_originAttributes.js]
+skip-if = socketprocess_networking # Bug 1640105
+[test_dns_localredirect.js]
+[test_dns_proxy_bypass.js]
+[test_dns_disabled.js]
+[test_domain_eviction.js]
+[test_duplicate_headers.js]
+[test_chunked_responses.js]
+[test_content_length_underrun.js]
+[test_event_sink.js]
+[test_eviction.js]
+[test_extract_charset_from_content_type.js]
+[test_fallback_no-cache-entry_canceled.js]
+[test_fallback_no-cache-entry_passing.js]
+[test_fallback_redirect-to-different-origin_canceled.js]
+[test_fallback_redirect-to-different-origin_passing.js]
+[test_fallback_request-error_canceled.js]
+[test_fallback_request-error_passing.js]
+[test_fallback_response-error_canceled.js]
+[test_fallback_response-error_passing.js]
+[test_file_protocol.js]
+[test_filestreams.js]
+[test_freshconnection.js]
+[test_gre_resources.js]
+[test_gzipped_206.js]
+[test_head.js]
+[test_header_Accept-Language.js]
+[test_header_Accept-Language_case.js]
+[test_headers.js]
+[test_hostnameIsLocalIPAddress.js]
+[test_hostnameIsSharedIPAddress.js]
+[test_http_headers.js]
+[test_httpauth.js]
+[test_httpcancel.js]
+[test_httpResponseTimeout.js]
+[test_httpsuspend.js]
+[test_idnservice.js]
+[test_idn_blacklist.js]
+[test_idn_urls.js]
+[test_idna2008.js]
+[test_immutable.js]
+run-sequentially = node server exceptions dont replay well
+[test_localstreams.js]
+[test_large_port.js]
+[test_mismatch_last-modified.js]
+[test_MIME_params.js]
+[test_mozTXTToHTMLConv.js]
+[test_multipart_byteranges.js]
+[test_multipart_streamconv.js]
+[test_multipart_streamconv_missing_lead_boundary.js]
+[test_multipart_streamconv_missing_boundary_lead_dashes.js]
+[test_multipart_streamconv-byte-by-byte.js]
+[test_nestedabout_serialize.js]
+[test_net_addr.js]
+# Bug 732363: test fails on windows for unknown reasons.
+skip-if = os == "win"
+[test_nojsredir.js]
+[test_offline_status.js]
+[test_origin.js]
+[test_anonymous-coalescing.js]
+[test_original_sent_received_head.js]
+[test_parse_content_type.js]
+[test_permmgr.js]
+[test_plaintext_sniff.js]
+skip-if = true # Causes sporatic oranges
+[test_post.js]
+[test_private_necko_channel.js]
+[test_private_cookie_changed.js]
+[test_progress.js]
+[test_protocolproxyservice.js]
+[test_protocolproxyservice-async-filters.js]
+[test_proxy-failover_canceled.js]
+[test_proxy-failover_passing.js]
+[test_proxy-replace_canceled.js]
+[test_proxy-replace_passing.js]
+[test_psl.js]
+[test_range_requests.js]
+[test_readline.js]
+[test_redirect-caching_canceled.js]
+[test_redirect-caching_failure.js]
+[test_redirect-caching_passing.js]
+[test_redirect_canceled.js]
+[test_redirect_failure.js]
+[test_redirect_from_script.js]
+[test_redirect_from_script_after-open_passing.js]
+[test_redirect_passing.js]
+[test_redirect_loop.js]
+[test_redirect_baduri.js]
+[test_redirect_different-protocol.js]
+[test_redirect_protocol_telemetry.js]
+[test_reentrancy.js]
+[test_reopen.js]
+[test_resumable_channel.js]
+[test_resumable_truncate.js]
+[test_safeoutputstream.js]
+[test_schema_2_migration.js]
+[test_schema_3_migration.js]
+[test_schema_10_migration.js]
+[test_simple.js]
+[test_sockettransportsvc_available.js]
+[test_socks.js]
+skip-if = (os == 'mac' && (verify || debug || os_version == '10.14')) || socketprocess_networking #Bug 1140656
+# 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_http2.js]
+run-sequentially = node server exceptions dont replay well
+[test_altsvc.js]
+run-sequentially = node server exceptions dont replay well
+[test_speculative_connect.js]
+[test_standardurl.js]
+[test_standardurl_default_port.js]
+[test_standardurl_port.js]
+[test_streamcopier.js]
+[test_traceable_channel.js]
+[test_unescapestring.js]
+[test_xmlhttprequest.js]
+[test_XHR_redirects.js]
+[test_pinned_app_cache.js]
+[test_offlinecache_custom-directory.js]
+run-sequentially = Hardcoded hash value includes port 4444.
+[test_bug767025.js]
+run-sequentially = Hardcoded hash value includes port 4444.
+[test_bug826063.js]
+[test_bug812167.js]
+[test_tldservice_nextsubdomain.js]
+[test_about_protocol.js]
+[test_bug856978.js]
+[test_unix_domain.js]
+[test_addr_in_use_error.js]
+[test_about_networking.js]
+[test_ping_aboutnetworking.js]
+skip-if = (verify && (os == 'mac'))
+[test_referrer.js]
+[test_referrer_cross_origin.js]
+[test_referrer_policy.js]
+[test_predictor.js]
+[test_signature_extraction.js]
+skip-if = os != "win"
+[test_synthesized_response.js]
+[test_udp_multicast.js]
+[test_redirect_history.js]
+[test_reply_without_content_type.js]
+[test_websocket_offline.js]
+[test_be_conservative.js]
+firefox-appdir = browser
+[test_be_conservative_error_handling.js]
+firefox-appdir = browser
+[test_tls_server.js]
+firefox-appdir = browser
+skip-if = socketprocess_networking
+[test_tls_server_multiple_clients.js]
+skip-if = socketprocess_networking
+[test_1073747.js]
+[test_safeoutputstream_append.js]
+[test_suspend_channel_before_connect.js]
+[test_suspend_channel_on_examine.js]
+[test_suspend_channel_on_modified.js]
+[test_inhibit_caching.js]
+[test_dns_disable_ipv4.js]
+[test_dns_disable_ipv6.js]
+[test_bug1195415.js]
+[test_cookie_blacklist.js]
+[test_getHost.js]
+[test_bug412457.js]
+skip-if = appname == "thunderbird"
+[test_bug464591.js]
+skip-if = appname == "thunderbird"
+[test_alt-data_simple.js]
+[test_alt-data_stream.js]
+[test_alt-data_too_big.js]
+[test_alt-data_overwrite.js]
+[test_alt-data_closeWithStatus.js]
+[test_cache-control_request.js]
+[test_bug1279246.js]
+[test_throttlequeue.js]
+[test_throttlechannel.js]
+[test_throttling.js]
+[test_separate_connections.js]
+[test_trackingProtection_annotateChannels.js]
+[test_race_cache_with_network.js]
+skip-if = (verify && !debug && (os == 'win'))
+[test_rcwn_always_cache_new_content.js]
+[test_channel_priority.js]
+[test_bug1312774_http1.js]
+[test_bug1312782_http1.js]
+[test_bug1355539_http1.js]
+[test_bug1378385_http1.js]
+[test_tls_flags_separate_connections.js]
+[test_tls_flags.js]
+skip-if = (verify && (os == 'linux')) || (os == "android" && processor == "x86_64")
+[test_uri_mutator.js]
+[test_bug1411316_http1.js]
+[test_header_Server_Timing.js]
+run-sequentially = node server exceptions dont replay well
+[test_trr.js]
+# test_trr.js is not working in Thunderbird due to bug 1608066.
+skip-if = appname == "thunderbird"
+[test_ioservice.js]
+[test_substituting_protocol_handler.js]
+[test_proxyconnect.js]
+skip-if = tsan || socketprocess_networking # Bug 1614708
+[test_captive_portal_service.js]
+run-sequentially = node server exceptions dont replay well
+skip-if = socketprocess_networking
+[test_dns_by_type_resolve.js]
+[test_network_connectivity_service.js]
+[test_suspend_channel_on_authRetry.js]
+[test_suspend_channel_on_examine_merged_response.js]
+[test_bug1527293.js]
+[test_stale-while-revalidate_negative.js]
+[test_stale-while-revalidate_positive.js]
+[test_stale-while-revalidate_loop.js]
+[test_stale-while-revalidate_max-age-0.js]
+[test_http1-proxy.js]
+[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_head_request_no_response_body.js]
+[test_disabled_ftp.js]
+[test_cache_204_response.js]
+[test_http3.js]
+# asan - bug 1616239
+# tsan - bug 1622845
+# win - bug 1616238
+# android - bug 1622901
+# mac - bug 1669892
+skip-if = asan || tsan || os == 'win' || os =='android' || os == 'mac'
+[test_http3_421.js]
+skip-if = asan || tsan || os == 'win' || os =='android'
+[test_http3_perf.js]
+skip-if = asan || tsan || os == 'win' || os =='android'
+[test_node_execute.js]
+[test_loadgroup_cancel.js]
+[test_obs-fold.js]
+[test_defaultURI.js]
+[test_port_remapping.js]
+[test_dns_override.js]
+[test_dns_override_for_localhost.js]
+skip-if = socketprocess_networking # Bug 1640105
+[test_no_cookies_after_last_pb_exit.js]
+[test_trr_httpssvc.js]
+skip-if = os == "android"
+run-sequentially = node server exceptions dont replay well
+[test_trr_case_sensitivity.js]
+skip-if = os == "android"
+run-sequentially = node server exceptions dont replay well
+[test_trr_proxy.js]
+[test_trr_cname_chain.js]
+skip-if = os == "android"
+run-sequentially = node server exceptions dont replay well
+[test_http_sfv.js]
+[test_blob_channelname.js]
+[test_altsvc_pref.js]
+skip-if = asan || tsan || os == 'win' || os =='android'
+[test_http3_alt_svc.js]
+skip-if = asan || tsan || os == 'win' || os =='android'
+[test_use_httpssvc.js]
+skip-if = os == "android"
+run-sequentially = node server exceptions dont replay well
+[test_trr_additional_section.js]
+skip-if = os == "android"
+run-sequentially = node server exceptions dont replay well
+[test_trr_extended_error.js]
+skip-if = os == "android"
+run-sequentially = node server exceptions dont replay well
+[test_httpssvc_iphint.js]
+skip-if = os == "android"
+run-sequentially = node server exceptions dont replay well
+[test_multipart_streamconv_empty.js]
+[test_httpssvc_priority.js]
+skip-if = os == "android"
+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_http3_trans_close.js]
+skip-if = asan || tsan || os == 'win' || os =='android'
+[test_brotli_http.js]
+[test_altsvc_http3.js]
+skip-if =
+ true # Bug 1675008
+ asan
+ tsan
+ os == 'win'
+ os =='android'
+[test_http3_fatal_stream_error.js]
+skip-if = asan || tsan || os == 'win' || os =='android' || socketprocess_networking
+run-sequentially = node server exceptions dont replay well
+[test_http3_large_post.js]
+skip-if = asan || tsan || os == 'win' || os =='android'
+[test_http3_error_before_connect.js]
+skip-if = asan || tsan || os == 'win' || os =='android' || socketprocess_networking
+run-sequentially = node server exceptions dont replay well
+[test_http3_server_not_existing.js]
+skip-if = asan || tsan || os == 'win' || os =='android' || socketprocess_networking
+run-sequentially = node server exceptions dont replay well
+[test_http3_fast_fallback.js]
+skip-if = asan || tsan || os == 'win' || os =='android' || socketprocess_networking
+run-sequentially = node server exceptions dont replay well
+[test_cookie_ipv6.js]
+[test_httpssvc_retry_with_ech.js]
+skip-if = asan || tsan || os == 'win' || os =='android'
+run-sequentially = node server exceptions dont replay well
+[test_httpssvc_retry_without_ech.js]
+skip-if = asan || tsan || os == 'win' || os =='android'
+run-sequentially = node server exceptions dont replay well
+[test_httpssvc_https_upgrade.js]
+[test_bug1683176.js]
+skip-if = os == "android" || !debug
+[test_SuperfluousAuth.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..00fd7f8453
--- /dev/null
+++ b/netwerk/test/unit_ipc/child_cookie_header.js
@@ -0,0 +1,92 @@
+/* global NetUtil, ChannelListener */
+
+"use strict";
+
+function inChildProcess() {
+ return (
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
+ );
+}
+
+let URL = null;
+function makeChan() {
+ return NetUtil.newChannel({
+ uri: URL,
+ 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");
+ URL = 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..a6d76b573b
--- /dev/null
+++ b/netwerk/test/unit_ipc/child_dns_by_type_resolve.js
@@ -0,0 +1,55 @@
+"use strict";
+
+const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
+ Ci.nsIDNSService
+);
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(
+ Ci.nsIThreadManager
+);
+const mainThread = threadManager.currentThread;
+
+const defaultOriginAttributes = {};
+
+let test_answer = "bXkgdm9pY2UgaXMgbXkgcGFzc3dvcmQ=";
+let test_answer_addr = "127.0.0.1";
+
+class DNSListener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+ // So we can await this as a promise.
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+
+DNSListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsIDNSListener",
+]);
+
+add_task(async function testTXTResolve() {
+ // use the h2 server as DOH provider
+ let listenerEsni = new DNSListener();
+ let request = dns.asyncResolve(
+ "_esni.example.com",
+ dns.RESOLVE_TYPE_TXT,
+ 0,
+ null, // resolverInfo
+ listenerEsni,
+ mainThread,
+ defaultOriginAttributes
+ );
+
+ let [inRequest, inRecord, inStatus] = await listenerEsni;
+ Assert.equal(inStatus, Cr.NS_OK, "status OK");
+ Assert.equal(inRequest, request, "correct request was used");
+ let answer = inRecord
+ .QueryInterface(Ci.nsIDNSTXTRecord)
+ .getRecordsAsOneString();
+ Assert.equal(answer, test_answer, "got correct answer");
+});
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..c548e725fe
--- /dev/null
+++ b/netwerk/test/unit_ipc/head_channels_clone.js
@@ -0,0 +1,10 @@
+/* import-globals-from ../unit/head_channels.js */
+// Load standard base class for network tests into child process
+//
+
+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+var { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+load("../unit/head_channels.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..deb6b87811
--- /dev/null
+++ b/netwerk/test/unit_ipc/head_trr_clone.js
@@ -0,0 +1,8 @@
+/* import-globals-from ../unit/head_trr.js */
+
+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+var { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+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..d2beb0fa17
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_alt-data_closeWithStatus_wrap.js
@@ -0,0 +1,21 @@
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+// needs to be rooted
+var cacheFlushObserver = {
+ observe() {
+ cacheFlushObserver = null;
+ do_send_remote_message("flushed");
+ },
+};
+
+var currentThread = Services.tm.currentThread;
+
+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..a9fd791031
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_alt-data_cross_process_wrap.js
@@ -0,0 +1,91 @@
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+// 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(url) {
+ ok(url);
+ URL = url; // save this to open the alt data channel later
+ var chan = make_channel(url);
+ var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+ cc.preferAlternativeDataType("text/binary", "", true);
+ chan.asyncOpen(new ChannelListener(readTextData, null));
+}
+
+function make_channel(url, callback, ctx) {
+ return NetUtil.newChannel({ uri: url, 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", "", true);
+ 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..06f5178513
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_alt-data_simple_wrap.js
@@ -0,0 +1,19 @@
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+// 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..8aff7462bb
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cache-entry-id_wrap.js
@@ -0,0 +1,15 @@
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+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..e269e1ee57
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cacheflags_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_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..4ec849c459
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_channel_id.js
@@ -0,0 +1,108 @@
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+/*
+ * 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 == 0) {
+ 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..2bcac3b827
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_channel_priority_wrap.js
@@ -0,0 +1,48 @@
+/* -*- 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";
+
+/* globals HttpServer */
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+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..9838614bd5
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cookie_header_stripped.js
@@ -0,0 +1,93 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const TEST_DOMAIN = "www.example.com";
+XPCOMUtils.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..ffb77df91a
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_cookiejars_wrap.js
@@ -0,0 +1,11 @@
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ // Allow all cookies.
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_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..3d7d1cb8cc
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_dns_by_type_resolve_wrap.js
@@ -0,0 +1,69 @@
+"use strict";
+
+let h2Port;
+let prefs;
+
+function setup() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ // the TRR server is on 127.0.0.1
+ prefs.setCharPref("network.trr.bootstrapAddress", "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", 2); // 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.http.spdy.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled.http2");
+ 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.bootstrapAddress");
+ prefs.clearUserPref("network.trr.blacklist-duration");
+ prefs.clearUserPref("network.trr.request-timeout");
+ prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
+});
+
+function run_test() {
+ prefs.setCharPref(
+ "network.trr.uri",
+ "https://foo.example.com:" + h2Port + "/doh"
+ );
+ 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_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_httpcancel_wrap.js b/netwerk/test/unit_ipc/test_httpcancel_wrap.js
new file mode 100644
index 0000000000..8a33de07bf
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_httpcancel_wrap.js
@@ -0,0 +1,51 @@
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+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_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_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..7f69051304
--- /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..b2c1dba598
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect-caching_failure_wrap.js
@@ -0,0 +1,13 @@
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+//
+// 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..e1ca4e2d68
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_redirect_failure_wrap.js
@@ -0,0 +1,10 @@
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+//
+// 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_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..c51ffd1a9d
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_trackingProtection_annotateChannels_wrap1.js
@@ -0,0 +1,27 @@
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { UrlClassifierTestUtils } = ChromeUtils.import(
+ "resource://testing-common/UrlClassifierTestUtils.jsm"
+);
+
+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..7dd67a54c7
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_trackingProtection_annotateChannels_wrap2.js
@@ -0,0 +1,27 @@
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { UrlClassifierTestUtils } = ChromeUtils.import(
+ "resource://testing-common/UrlClassifierTestUtils.jsm"
+);
+
+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..57116e156f
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_trr_httpssvc_wrap.js
@@ -0,0 +1,81 @@
+"use strict";
+
+let h2Port;
+let prefs;
+
+function setup() {
+ let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ h2Port = 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 = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+ prefs.setBoolPref("network.security.esni.enabled", false);
+ prefs.setBoolPref("network.http.spdy.enabled", true);
+ prefs.setBoolPref("network.http.spdy.enabled.http2", true);
+ // the TRR server is on 127.0.0.1
+ prefs.setCharPref("network.trr.bootstrapAddress", "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", 2); // 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.spdy.enabled");
+ prefs.clearUserPref("network.http.spdy.enabled.http2");
+ 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.bootstrapAddress");
+ prefs.clearUserPref("network.trr.blacklist-duration");
+ prefs.clearUserPref("network.trr.request-timeout");
+ prefs.clearUserPref("network.trr.clear-cache-on-pref-change");
+});
+
+function run_test() {
+ 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");
+ });
+
+ 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.ini b/netwerk/test/unit_ipc/xpcshell.ini
new file mode 100644
index 0000000000..9a10ca3b00
--- /dev/null
+++ b/netwerk/test/unit_ipc/xpcshell.ini
@@ -0,0 +1,113 @@
+[DEFAULT]
+head = head_channels_clone.js head_trr_clone.js
+skip-if = toolkit == 'android'
+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_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-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
+ child_cookie_header.js
+ child_dns_by_type_resolve.js
+
+[test_cookie_header_stripped.js]
+[test_cacheflags_wrap.js]
+[test_cache-entry-id_wrap.js]
+[test_cache_jar_wrap.js]
+[test_channel_close_wrap.js]
+[test_cookiejars_wrap.js]
+[test_dns_cancel_wrap.js]
+[test_dns_service_wrap.js]
+[test_duplicate_headers_wrap.js]
+[test_event_sink_wrap.js]
+[test_head_wrap.js]
+[test_headers_wrap.js]
+[test_httpsuspend_wrap.js]
+[test_post_wrap.js]
+[test_predictor_wrap.js]
+[test_progress_wrap.js]
+[test_redirect-caching_canceled_wrap.js]
+[test_redirect-caching_failure_wrap.js]
+[test_redirect-caching_passing_wrap.js]
+[test_redirect_canceled_wrap.js]
+[test_redirect_failure_wrap.js]
+# Do not test the channel.redirectTo() API under e10s until 827269 is resolved
+[test_redirect_from_script_wrap.js]
+skip-if = true
+[test_redirect_passing_wrap.js]
+[test_redirect_different-protocol_wrap.js]
+[test_reentrancy_wrap.js]
+[test_resumable_channel_wrap.js]
+[test_simple_wrap.js]
+[test_xmlhttprequest_wrap.js]
+[test_XHR_redirects.js]
+[test_redirect_history_wrap.js]
+[test_reply_without_content_type_wrap.js]
+[test_getHost_wrap.js]
+[test_alt-data_simple_wrap.js]
+[test_alt-data_stream_wrap.js]
+[test_alt-data_closeWithStatus_wrap.js]
+[test_original_sent_received_head_wrap.js]
+[test_channel_id.js]
+[test_trackingProtection_annotateChannels_wrap1.js]
+[test_trackingProtection_annotateChannels_wrap2.js]
+[test_channel_priority_wrap.js]
+[test_multipart_streamconv_wrap.js]
+[test_alt-data_cross_process_wrap.js]
+[test_httpcancel_wrap.js]
+[test_dns_by_type_resolve_wrap.js]
+[test_trr_httpssvc_wrap.js]
+skip-if = os == "android"
diff --git a/netwerk/url-classifier/AsyncUrlChannelClassifier.cpp b/netwerk/url-classifier/AsyncUrlChannelClassifier.cpp
new file mode 100644
index 0000000000..e4b5904c36
--- /dev/null
+++ b/netwerk/url-classifier/AsyncUrlChannelClassifier.cpp
@@ -0,0 +1,924 @@
+/* -*- 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();
+
+ 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()); }
+
+URIData::~URIData() { NS_ReleaseOnMainThread("URIData:mURI", mURI.forget()); }
+
+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();
+ ~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;
+ nsCOMPtr<nsIUrlClassifierFeature> mFeature;
+ nsCOMPtr<nsIChannel> mChannel;
+
+ nsTArray<RefPtr<TableData>> mBlocklistTables;
+ nsTArray<RefPtr<TableData>> mEntitylistTables;
+
+ // blocklist + entitylist.
+ nsCString mHostInPrefTables[2];
+};
+
+FeatureData::FeatureData() : mState(eUnclassified) {}
+
+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_WARN_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 == false) {
+ // 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; flash feature always uses the 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_WARN_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_WARN_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_WARN_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..5d8605e909
--- /dev/null
+++ b/netwerk/url-classifier/ChannelClassifierService.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 "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"
+
+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::Unblock() {
+ UC_LOG(("ChannelClassifierService: unblock channel %p", mChannel.get()));
+
+ mDecision = ChannelBlockDecision::Unblocked;
+ 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..97d0646b40
--- /dev/null
+++ b/netwerk/url-classifier/ChannelClassifierService.h
@@ -0,0 +1,78 @@
+/* -*- 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"
+
+namespace mozilla {
+namespace net {
+
+enum class ChannelBlockDecision {
+ Blocked,
+ Unblocked,
+ 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..01f90f1913
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierCommon.cpp
@@ -0,0 +1,659 @@
+/* -*- 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 "ClassifierDummyChannel.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/BasePrincipal.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;
+ 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 = services::GetThirdPartyUtil();
+ 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);
+ NS_ENSURE_SUCCESS(rv, 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);
+ }
+
+ RefPtr<ClassifierDummyChannel> dummyChannel = do_QueryObject(aChannel);
+ if (dummyChannel) {
+ dummyChannel->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) ||
+ IsCryptominingClassificationFlag(aClassificationFlags);
+
+ 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 = services::GetIOService();
+ 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) {
+ if (StaticPrefs::privacy_annotate_channels_strict_list_enabled() &&
+ (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) {
+ if (aFlag & nsIClassifiedChannel::ClassificationFlags::
+ CLASSIFIED_ANY_SOCIAL_TRACKING) {
+ return true;
+ }
+
+ return false;
+}
+
+// static
+bool UrlClassifierCommon::IsCryptominingClassificationFlag(uint32_t aFlag) {
+ if (aFlag &
+ nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_CRYPTOMINING) {
+ return true;
+ }
+
+ if (StaticPrefs::privacy_annotate_channels_strict_list_enabled() &&
+ (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..d40b83edee
--- /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);
+
+ static bool IsSocialTrackingClassificationFlag(uint32_t aFlag);
+
+ static bool IsCryptominingClassificationFlag(uint32_t aFlag);
+
+ // 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.jsm b/netwerk/url-classifier/UrlClassifierExceptionListService.jsm
new file mode 100644
index 0000000000..c614237906
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierExceptionListService.jsm
@@ -0,0 +1,172 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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.UrlClassifierExceptionListService = function() {};
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "RemoteSettings",
+ "resource://services-settings/remote-settings.js"
+);
+
+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) {
+ Cu.reportError(`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 = 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;
+ },
+};
+
+var EXPORTED_SYMBOLS = ["UrlClassifierExceptionListService"];
diff --git a/netwerk/url-classifier/UrlClassifierFeatureBase.cpp b/netwerk/url-classifier/UrlClassifierFeatureBase.cpp
new file mode 100644
index 0000000000..07da1fd073
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureBase.cpp
@@ -0,0 +1,172 @@
+/* -*- 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;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureBase.h b/netwerk/url-classifier/UrlClassifierFeatureBase.h
new file mode 100644
index 0000000000..4ca8763866
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureBase.h
@@ -0,0 +1,79 @@
+/* -*- 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;
+};
+
+} // 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..42d3c44943
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.cpp
@@ -0,0 +1,167 @@
+/* -*- 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"
+
+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()
+ : UrlClassifierFeatureBase(
+ 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..1e1444cbc9
--- /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 UrlClassifierFeatureBase {
+ 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..1cd1fa8f6f
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.cpp
@@ -0,0 +1,197 @@
+/* -*- 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"
+
+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()
+ : UrlClassifierFeatureBase(
+ 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) {
+ if (decision == ChannelBlockDecision::Unblocked) {
+ ContentBlockingNotifier::OnEvent(
+ aChannel, nsIWebProgressListener::STATE_UNBLOCKED_TRACKING_CONTENT,
+ 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..5b154379ae
--- /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 UrlClassifierFeatureBase {
+ 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/UrlClassifierFeatureFactory.cpp b/netwerk/url-classifier/UrlClassifierFeatureFactory.cpp
new file mode 100644
index 0000000000..51ab881683
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureFactory.cpp
@@ -0,0 +1,374 @@
+/* -*- 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 "UrlClassifierFeatureFingerprintingAnnotation.h"
+#include "UrlClassifierFeatureFingerprintingProtection.h"
+#include "UrlClassifierFeatureFlash.h"
+#include "UrlClassifierFeatureLoginReputation.h"
+#include "UrlClassifierFeaturePhishingProtection.h"
+#include "UrlClassifierFeatureSocialTrackingAnnotation.h"
+#include "UrlClassifierFeatureSocialTrackingProtection.h"
+#include "UrlClassifierFeatureTrackingProtection.h"
+#include "UrlClassifierFeatureTrackingAnnotation.h"
+#include "UrlClassifierFeatureCustomTables.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();
+ UrlClassifierFeatureFingerprintingAnnotation::MaybeShutdown();
+ UrlClassifierFeatureFingerprintingProtection::MaybeShutdown();
+ UrlClassifierFeatureFlash::MaybeShutdown();
+ UrlClassifierFeatureLoginReputation::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.
+
+ // 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);
+ }
+
+ // Flash
+ nsTArray<nsCOMPtr<nsIUrlClassifierFeature>> flashFeatures;
+ UrlClassifierFeatureFlash::MaybeCreate(aChannel, flashFeatures);
+ aFeatures.AppendElements(flashFeatures);
+}
+
+/* static */
+void UrlClassifierFeatureFactory::GetPhishingProtectionFeatures(
+ nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures) {
+ UrlClassifierFeaturePhishingProtection::MaybeCreate(aFeatures);
+}
+
+/* static */
+nsIUrlClassifierFeature*
+UrlClassifierFeatureFactory::GetFeatureLoginReputation() {
+ return UrlClassifierFeatureLoginReputation::MaybeGetOrCreate();
+}
+
+/* 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();
+ }
+
+ // 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();
+ }
+
+ // Login reputation
+ feature = UrlClassifierFeatureLoginReputation::GetIfNameMatches(aName);
+ if (feature) {
+ return feature.forget();
+ }
+
+ // We use Flash feature just for document loading.
+ feature = UrlClassifierFeatureFlash::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);
+ }
+
+ // 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);
+ }
+
+ // Login reputation
+ name.Assign(UrlClassifierFeatureLoginReputation::Name());
+ if (!name.IsEmpty()) {
+ aArray.AppendElement(name);
+ }
+
+ // Flash features
+ {
+ nsTArray<nsCString> features;
+ UrlClassifierFeatureFlash::GetFeatureNames(features);
+ aArray.AppendElements(features);
+ }
+
+ // 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},
+};
+
+} // 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..ab5affa656
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureFactory.h
@@ -0,0 +1,59 @@
+/* -*- 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 nsIUrlClassifierFeature* GetFeatureLoginReputation();
+
+ static already_AddRefed<nsIUrlClassifierFeature> GetFeatureByName(
+ const nsACString& aFeatureName);
+
+ 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..e8fb9c704a
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.cpp
@@ -0,0 +1,175 @@
+/* -*- 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"
+
+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()
+ : UrlClassifierFeatureBase(
+ 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..402f74e998
--- /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 UrlClassifierFeatureBase {
+ 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..88515dec17
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.cpp
@@ -0,0 +1,203 @@
+/* -*- 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"
+
+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()
+ : UrlClassifierFeatureBase(
+ 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) {
+ if (decision == ChannelBlockDecision::Unblocked) {
+ ContentBlockingNotifier::OnEvent(
+ aChannel, nsIWebProgressListener::STATE_UNBLOCKED_TRACKING_CONTENT,
+ 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..6bbecc89f4
--- /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 UrlClassifierFeatureBase {
+ 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/UrlClassifierFeatureFlash.cpp b/netwerk/url-classifier/UrlClassifierFeatureFlash.cpp
new file mode 100644
index 0000000000..10310222c6
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureFlash.cpp
@@ -0,0 +1,200 @@
+/* -*- 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 "UrlClassifierFeatureFlash.h"
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/StaticPrefs_plugins.h"
+#include "nsIXULRuntime.h"
+#include "nsScriptSecurityManager.h"
+#include "nsQueryObject.h"
+
+namespace mozilla {
+namespace net {
+
+struct UrlClassifierFeatureFlash::FlashFeature {
+ const char* mName;
+ const char* mBlocklistPrefTables;
+ const char* mEntitylistPrefTables;
+ bool mSubdocumentOnly;
+ nsIHttpChannel::FlashPluginState mFlashPluginState;
+ RefPtr<UrlClassifierFeatureFlash> mFeature;
+};
+
+namespace {
+
+static UrlClassifierFeatureFlash::FlashFeature sFlashFeaturesMap[] = {
+ {"flash-deny", "urlclassifier.flashTable", "urlclassifier.flashExceptTable",
+ false, nsIHttpChannel::FlashPluginDenied},
+ {"flash-allow", "urlclassifier.flashAllowTable",
+ "urlclassifier.flashAllowExceptTable", false,
+ nsIHttpChannel::FlashPluginAllowed},
+ {"flash-deny-subdoc", "urlclassifier.flashSubDocTable",
+ "urlclassifier.flashSubDocExceptTable", true,
+ nsIHttpChannel::FlashPluginDeniedInSubdocuments},
+};
+
+bool IsInitialized() { return !!sFlashFeaturesMap[0].mFeature; }
+
+} // namespace
+
+UrlClassifierFeatureFlash::UrlClassifierFeatureFlash(
+ const UrlClassifierFeatureFlash::FlashFeature& aFlashFeature)
+ : UrlClassifierFeatureBase(
+ nsDependentCString(aFlashFeature.mName),
+ nsDependentCString(aFlashFeature.mBlocklistPrefTables),
+ nsDependentCString(aFlashFeature.mEntitylistPrefTables),
+ ""_ns, // aPrefBlocklistHosts
+ ""_ns, // aPrefEntitylistHosts
+ ""_ns, // aPrefBlocklistTableName
+ ""_ns, // aPrefEntitylistTableName
+ ""_ns) // aPrefExceptionHosts
+ ,
+ mFlashPluginState(aFlashFeature.mFlashPluginState) {
+ static_assert(nsIHttpChannel::FlashPluginDeniedInSubdocuments ==
+ nsIHttpChannel::FlashPluginLastValue,
+ "nsIHttpChannel::FlashPluginLastValue is out-of-sync!");
+}
+
+/* static */
+void UrlClassifierFeatureFlash::GetFeatureNames(nsTArray<nsCString>& aArray) {
+ for (const FlashFeature& flashFeature : sFlashFeaturesMap) {
+ aArray.AppendElement(nsDependentCString(flashFeature.mName));
+ }
+}
+
+/* static */
+void UrlClassifierFeatureFlash::MaybeInitialize() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (IsInitialized()) {
+ return;
+ }
+
+ for (FlashFeature& flashFeature : sFlashFeaturesMap) {
+ MOZ_ASSERT(!flashFeature.mFeature);
+ flashFeature.mFeature = new UrlClassifierFeatureFlash(flashFeature);
+ flashFeature.mFeature->InitializePreferences();
+ }
+}
+
+/* static */
+void UrlClassifierFeatureFlash::MaybeShutdown() {
+ if (!IsInitialized()) {
+ return;
+ }
+
+ for (FlashFeature& flashFeature : sFlashFeaturesMap) {
+ MOZ_ASSERT(flashFeature.mFeature);
+ flashFeature.mFeature->ShutdownPreferences();
+ flashFeature.mFeature = nullptr;
+ }
+}
+
+/* static */
+void UrlClassifierFeatureFlash::MaybeCreate(
+ nsIChannel* aChannel,
+ nsTArray<nsCOMPtr<nsIUrlClassifierFeature>>& aFeatures) {
+ const auto fnIsFlashBlockingEnabled = [] {
+ return StaticPrefs::plugins_flashBlock_enabled() && !FissionAutostart();
+ };
+
+ // All disabled.
+ if (!fnIsFlashBlockingEnabled()) {
+ return;
+ }
+
+ // We use Flash feature just for document loading.
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ ExtContentPolicyType contentPolicyType =
+ loadInfo->GetExternalContentPolicyType();
+
+ if (contentPolicyType != ExtContentPolicy::TYPE_DOCUMENT &&
+ contentPolicyType != ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ return;
+ }
+
+ // Only allow plugins for documents from an HTTP/HTTPS origin.
+ if (StaticPrefs::plugins_http_https_only()) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
+ if (!httpChannel) {
+ return;
+ }
+ }
+
+ MaybeInitialize();
+
+ for (const FlashFeature& flashFeature : sFlashFeaturesMap) {
+ MOZ_ASSERT(flashFeature.mFeature);
+ if (!flashFeature.mSubdocumentOnly ||
+ contentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ aFeatures.AppendElement(flashFeature.mFeature);
+ }
+ }
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureFlash::GetIfNameMatches(const nsACString& aName) {
+ MaybeInitialize();
+
+ for (const FlashFeature& flashFeature : sFlashFeaturesMap) {
+ MOZ_ASSERT(flashFeature.mFeature);
+ if (aName.Equals(flashFeature.mName)) {
+ nsCOMPtr<nsIUrlClassifierFeature> self = flashFeature.mFeature.get();
+ return self.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureFlash::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(("UrlClassifierFeatureFlash::ProcessChannel - annotating channel %p",
+ 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->NotifyFlashPluginStateChanged(mFlashPluginState);
+ }
+
+ RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(aChannel);
+ if (httpChannel) {
+ httpChannel->SetFlashPluginState(mFlashPluginState);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureFlash::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);
+
+ // Here we return the channel's URI always.
+ *aURIType = aListType == nsIUrlClassifierFeature::blocklist
+ ? nsIUrlClassifierFeature::URIType::blocklistURI
+ : nsIUrlClassifierFeature::URIType::entitylistURI;
+ return aChannel->GetURI(aURI);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureFlash.h b/netwerk/url-classifier/UrlClassifierFeatureFlash.h
new file mode 100644
index 0000000000..ac91f9c59b
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureFlash.h
@@ -0,0 +1,51 @@
+/* -*- 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_UrlClassifierFeatureFlash_h
+#define mozilla_UrlClassifierFeatureFlash_h
+
+#include "UrlClassifierFeatureBase.h"
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierFeatureFlash final : public UrlClassifierFeatureBase {
+ public:
+ struct FlashFeature;
+
+ static void GetFeatureNames(nsTArray<nsCString>& aNames);
+
+ static void MaybeShutdown();
+
+ static void MaybeCreate(
+ nsIChannel* aChannel,
+ nsTArray<nsCOMPtr<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 UrlClassifierFeatureFlash(const FlashFeature& aFlashFeature);
+
+ static void MaybeInitialize();
+
+ nsIHttpChannel::FlashPluginState mFlashPluginState;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_UrlClassifierFeatureFlash_h
diff --git a/netwerk/url-classifier/UrlClassifierFeatureLoginReputation.cpp b/netwerk/url-classifier/UrlClassifierFeatureLoginReputation.cpp
new file mode 100644
index 0000000000..916255c67a
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureLoginReputation.cpp
@@ -0,0 +1,102 @@
+/* -*- 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 "UrlClassifierFeatureLoginReputation.h"
+
+#include "mozilla/StaticPrefs_browser.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+#define LOGIN_REPUTATION_FEATURE_NAME "login-reputation"
+
+#define PREF_PASSWORD_ALLOW_TABLE "urlclassifier.passwordAllowTable"
+
+StaticRefPtr<UrlClassifierFeatureLoginReputation> gFeatureLoginReputation;
+
+} // namespace
+
+UrlClassifierFeatureLoginReputation::UrlClassifierFeatureLoginReputation()
+ : UrlClassifierFeatureBase(nsLiteralCString(LOGIN_REPUTATION_FEATURE_NAME),
+ ""_ns, // blocklist tables
+ nsLiteralCString(PREF_PASSWORD_ALLOW_TABLE),
+ ""_ns, // blocklist pref
+ ""_ns, // entitylist pref
+ ""_ns, // blocklist pref table name
+ ""_ns, // entitylist pref table name
+ ""_ns) // exception host pref
+{}
+
+/* static */ const char* UrlClassifierFeatureLoginReputation::Name() {
+ return StaticPrefs::browser_safebrowsing_passwords_enabled()
+ ? LOGIN_REPUTATION_FEATURE_NAME
+ : "";
+}
+
+/* static */
+void UrlClassifierFeatureLoginReputation::MaybeShutdown() {
+ UC_LOG_LEAK(("UrlClassifierFeatureLoginReputation::MaybeShutdown"));
+
+ if (gFeatureLoginReputation) {
+ gFeatureLoginReputation->ShutdownPreferences();
+ gFeatureLoginReputation = nullptr;
+ }
+}
+
+/* static */
+nsIUrlClassifierFeature*
+UrlClassifierFeatureLoginReputation::MaybeGetOrCreate() {
+ if (!StaticPrefs::browser_safebrowsing_passwords_enabled()) {
+ return nullptr;
+ }
+
+ if (!gFeatureLoginReputation) {
+ gFeatureLoginReputation = new UrlClassifierFeatureLoginReputation();
+ gFeatureLoginReputation->InitializePreferences();
+ }
+
+ return gFeatureLoginReputation;
+}
+
+/* static */
+already_AddRefed<nsIUrlClassifierFeature>
+UrlClassifierFeatureLoginReputation::GetIfNameMatches(const nsACString& aName) {
+ if (!aName.EqualsLiteral(LOGIN_REPUTATION_FEATURE_NAME)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIUrlClassifierFeature> self = MaybeGetOrCreate();
+ return self.forget();
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureLoginReputation::ProcessChannel(
+ nsIChannel* aChannel, const nsTArray<nsCString>& aList,
+ const nsTArray<nsCString>& aHashes, bool* aShouldContinue) {
+ MOZ_CRASH(
+ "UrlClassifierFeatureLoginReputation::ProcessChannel should never be "
+ "called");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UrlClassifierFeatureLoginReputation::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);
+ MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist,
+ "UrlClassifierFeatureLoginReputation is meant to be used just to "
+ "entitylist URLs");
+ *aURIType = nsIUrlClassifierFeature::URIType::entitylistURI;
+ return aChannel->GetURI(aURI);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/url-classifier/UrlClassifierFeatureLoginReputation.h b/netwerk/url-classifier/UrlClassifierFeatureLoginReputation.h
new file mode 100644
index 0000000000..31fe7b5b0a
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureLoginReputation.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_UrlClassifierFeatureLoginReputation_h
+#define mozilla_net_UrlClassifierFeatureLoginReputation_h
+
+#include "UrlClassifierFeatureBase.h"
+
+class nsIChannel;
+
+namespace mozilla {
+namespace net {
+
+class UrlClassifierFeatureLoginReputation final
+ : public UrlClassifierFeatureBase {
+ public:
+ static const char* Name();
+
+ static void MaybeShutdown();
+
+ static nsIUrlClassifierFeature* MaybeGetOrCreate();
+
+ 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:
+ UrlClassifierFeatureLoginReputation();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_UrlClassifierFeatureLoginReputation_h
diff --git a/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp b/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp
new file mode 100644
index 0000000000..5c0e3bf4fe
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp
@@ -0,0 +1,126 @@
+/* -*- 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"
+
+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..d7a7ab9211
--- /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>& aNames);
+
+ 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..5987ed8d10
--- /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"
+
+class nsIURI;
+
+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..d34209ba39
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.cpp
@@ -0,0 +1,175 @@
+/* -*- 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"
+
+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()
+ : UrlClassifierFeatureBase(
+ 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..52da361d51
--- /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 UrlClassifierFeatureBase {
+ 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..c9cb61d2b1
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.cpp
@@ -0,0 +1,199 @@
+/* -*- 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"
+
+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()
+ : UrlClassifierFeatureBase(
+ 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) {
+ if (decision == ChannelBlockDecision::Unblocked) {
+ ContentBlockingNotifier::OnEvent(
+ aChannel, nsIWebProgressListener::STATE_UNBLOCKED_TRACKING_CONTENT,
+ 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..aa7f709f51
--- /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 UrlClassifierFeatureBase {
+ 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..bebd506324
--- /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()
+ : UrlClassifierFeatureBase(
+ 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..b08c59e354
--- /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 UrlClassifierFeatureBase {
+ 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..9c181ee592
--- /dev/null
+++ b/netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.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 "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"
+
+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()
+ : UrlClassifierFeatureBase(
+ 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) {
+ if (decision == ChannelBlockDecision::Unblocked) {
+ ContentBlockingNotifier::OnEvent(
+ aChannel, nsIWebProgressListener::STATE_UNBLOCKED_TRACKING_CONTENT,
+ 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..e78aeb2577
--- /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 UrlClassifierFeatureBase {
+ 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..03a02f0ebe
--- /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'],
+ 'jsm': 'resource://gre/modules/UrlClassifierExceptionListService.jsm',
+ 'constructor': 'UrlClassifierExceptionListService',
+ },
+]
diff --git a/netwerk/url-classifier/moz.build b/netwerk/url-classifier/moz.build
new file mode 100644
index 0000000000..9bdbbd63db
--- /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.jsm",
+]
+
+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",
+ "UrlClassifierFeatureFactory.cpp",
+ "UrlClassifierFeatureFingerprintingAnnotation.cpp",
+ "UrlClassifierFeatureFingerprintingProtection.cpp",
+ "UrlClassifierFeatureFlash.cpp",
+ "UrlClassifierFeatureLoginReputation.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..067aed88f7
--- /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, nullptr, 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..815e8d6728
--- /dev/null
+++ b/netwerk/url-classifier/nsIChannelClassifierService.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 "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;
+
+ // Ask UrlClassifier to unblock the load.
+ // This is similar to allow(), the only difference is that the unblocked channel
+ // is still considered as a tracking channel, so classifier will notify UI
+ // content blocking event for the channel.
+ void unblock();
+
+ // Ask UrlClassifier to allow the load.
+ // This is similar to unblock(), the only difference is that the allowed channel
+ // is not considered as a tracking channel anymore. UI will not receive content
+ // blocking event for the 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..d99152330e
--- /dev/null
+++ b/netwerk/url-classifier/nsIURIClassifier.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"
+#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 nsISerialEventTarget
+ * Event target for constructing actor in content process.
+ * The event target should be tied to Docgroup/Tabgroup by
+ * using EventTargetFor
+ *
+ * @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 nsISerialEventTarget aEventTarget,
+ 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/moz.build b/netwerk/wifi/moz.build
new file mode 100644
index 0000000000..5f388350f7
--- /dev/null
+++ b/netwerk/wifi/moz.build
@@ -0,0 +1,56 @@
+# -*- 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":
+ UNIFIED_SOURCES += [
+ "nsWifiScannerMac.cpp",
+ ]
+ SOURCES += [
+ "osx_corewlan.mm",
+ ]
+ # osx_corewlan.mm has warnings about scanForNetworksWithParameters,
+ # bssidData and rssi. These are APIs that were removed in 10.7, so we need
+ # to accept the warnings when targeting the newer SDKs.
+ SOURCES["osx_corewlan.mm"].flags += ["-Wno-error=objc-method-access"]
+elif CONFIG["OS_ARCH"] in ("DragonFly", "FreeBSD"):
+ UNIFIED_SOURCES += [
+ "nsWifiScannerFreeBSD.cpp",
+ ]
+elif CONFIG["OS_ARCH"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "nsWifiScannerWin.cpp",
+ "win_wifiScanner.cpp",
+ "win_wlanLibrary.cpp",
+ ]
+elif CONFIG["OS_ARCH"] == "SunOS":
+ CXXFLAGS += CONFIG["GLIB_CFLAGS"]
+ UNIFIED_SOURCES += [
+ "nsWifiScannerSolaris.cpp",
+ ]
+
+if CONFIG["NECKO_WIFI_DBUS"]:
+ UNIFIED_SOURCES += [
+ "nsWifiScannerDBus.cpp",
+ ]
+ CXXFLAGS += ["-Wno-error=shadow"]
+
+if CONFIG["NECKO_WIFI_DBUS"]:
+ CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"]
+
+FINAL_LIBRARY = "xul"
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..aaa318d255
--- /dev/null
+++ b/netwerk/wifi/nsIWifiMonitor.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 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.
+ */
+ void startWatching(in nsIWifiListener aListener);
+
+ /*
+ * 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..62687c520e
--- /dev/null
+++ b/netwerk/wifi/nsWifiAccessPoint.cpp
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsMemory.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;
+}
+
+// Helper functions:
+
+bool AccessPointsEqual(nsCOMArray<nsWifiAccessPoint>& a,
+ nsCOMArray<nsWifiAccessPoint>& b) {
+ if (a.Count() != b.Count()) {
+ LOG(("AccessPoint lists have different lengths\n"));
+ return false;
+ }
+
+ for (int32_t i = 0; i < a.Count(); i++) {
+ LOG(("++ Looking for %s\n", a[i]->mSsid));
+ bool found = false;
+ for (int32_t j = 0; j < b.Count(); j++) {
+ LOG((" %s->%s | %s->%s\n", a[i]->mSsid, b[j]->mSsid, a[i]->mMac,
+ b[j]->mMac));
+ if (!strcmp(a[i]->mSsid, b[j]->mSsid) &&
+ !strcmp(a[i]->mMac, b[j]->mMac) && a[i]->mSignal == b[j]->mSignal) {
+ found = true;
+ }
+ }
+ if (!found) return false;
+ }
+ LOG((" match!\n"));
+ return true;
+}
+
+void ReplaceArray(nsCOMArray<nsWifiAccessPoint>& a,
+ nsCOMArray<nsWifiAccessPoint>& b) {
+ a.Clear();
+
+ // better way to copy?
+ for (int32_t i = 0; i < b.Count(); i++) {
+ a.AppendObject(b[i]);
+ }
+
+ b.Clear();
+}
diff --git a/netwerk/wifi/nsWifiAccessPoint.h b/netwerk/wifi/nsWifiAccessPoint.h
new file mode 100644
index 0000000000..7c1bc8f936
--- /dev/null
+++ b/netwerk/wifi/nsWifiAccessPoint.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 __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];
+ int mSignal;
+ char mSsid[33];
+ 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;
+ }
+ }
+};
+
+// Helper functions
+
+bool AccessPointsEqual(nsCOMArray<nsWifiAccessPoint>& a,
+ nsCOMArray<nsWifiAccessPoint>& b);
+void ReplaceArray(nsCOMArray<nsWifiAccessPoint>& a,
+ nsCOMArray<nsWifiAccessPoint>& b);
+
+#endif
diff --git a/netwerk/wifi/nsWifiMonitor.cpp b/netwerk/wifi/nsWifiMonitor.cpp
new file mode 100644
index 0000000000..b886c3c318
--- /dev/null
+++ b/netwerk/wifi/nsWifiMonitor.cpp
@@ -0,0 +1,232 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+
+LazyLogModule gWifiMonitorLog("WifiMonitor");
+
+NS_IMPL_ISUPPORTS(nsWifiMonitor, nsIRunnable, nsIObserver, nsIWifiMonitor)
+
+nsWifiMonitor::nsWifiMonitor()
+ : mKeepGoing(true),
+ mThreadComplete(false),
+ mReentrantMonitor("nsWifiMonitor.mReentrantMonitor") {
+ nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+ if (obsSvc) obsSvc->AddObserver(this, "xpcom-shutdown", false);
+
+ LOG(("@@@@@ wifimonitor created\n"));
+}
+
+NS_IMETHODIMP
+nsWifiMonitor::Observe(nsISupports* subject, const char* topic,
+ const char16_t* data) {
+ if (!strcmp(topic, "xpcom-shutdown")) {
+ LOG(("Shutting down\n"));
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mKeepGoing = false;
+ mon.Notify();
+ mThread = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWifiMonitor::StartWatching(nsIWifiListener* aListener) {
+ LOG(("nsWifiMonitor::StartWatching %p thread %p listener %p\n", this,
+ mThread.get(), aListener));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aListener) return NS_ERROR_NULL_POINTER;
+ if (!mKeepGoing) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = NS_OK;
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ if (mThreadComplete) {
+ // generally there is just one thread for the lifetime of the service,
+ // but if DoScan returns with an error before shutdown (i.e. !mKeepGoing)
+ // then we will respawn the thread.
+ LOG(("nsWifiMonitor::StartWatching %p restarting thread\n", this));
+ mThreadComplete = false;
+ mThread = nullptr;
+ }
+
+ if (!mThread) {
+ rv = NS_NewNamedThread("Wifi Monitor", getter_AddRefs(mThread), this);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ mListeners.AppendElement(
+ nsWifiListener(new nsMainThreadPtrHolder<nsIWifiListener>(
+ "nsIWifiListener", aListener)));
+
+ // tell ourselves that we have a new watcher.
+ mon.Notify();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWifiMonitor::StopWatching(nsIWifiListener* aListener) {
+ LOG(("nsWifiMonitor::StopWatching %p thread %p listener %p\n", this,
+ mThread.get(), aListener));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aListener) return NS_ERROR_NULL_POINTER;
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ for (uint32_t i = 0; i < mListeners.Length(); i++) {
+ if (mListeners[i].mListener == aListener) {
+ mListeners.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+typedef nsTArray<nsMainThreadPtrHandle<nsIWifiListener>> WifiListenerArray;
+
+class nsPassErrorToWifiListeners final : public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ nsPassErrorToWifiListeners(UniquePtr<WifiListenerArray>&& aListeners,
+ nsresult aResult)
+ : mListeners(std::move(aListeners)), mResult(aResult) {}
+
+ private:
+ ~nsPassErrorToWifiListeners() = default;
+ UniquePtr<WifiListenerArray> mListeners;
+ nsresult mResult;
+};
+
+NS_IMPL_ISUPPORTS(nsPassErrorToWifiListeners, nsIRunnable)
+
+NS_IMETHODIMP nsPassErrorToWifiListeners::Run() {
+ LOG(("About to send error to the wifi listeners\n"));
+ for (size_t i = 0; i < mListeners->Length(); i++) {
+ (*mListeners)[i]->OnError(mResult);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWifiMonitor::Run() {
+ LOG(("@@@@@ wifi monitor run called\n"));
+
+ nsresult rv = DoScan();
+ LOG(("@@@@@ wifi monitor run::doscan complete %" PRIx32 "\n",
+ static_cast<uint32_t>(rv)));
+
+ UniquePtr<WifiListenerArray> currentListeners;
+ bool doError = false;
+
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ if (mKeepGoing && NS_FAILED(rv)) {
+ doError = true;
+ currentListeners = MakeUnique<WifiListenerArray>(mListeners.Length());
+ for (uint32_t i = 0; i < mListeners.Length(); i++)
+ currentListeners->AppendElement(mListeners[i].mListener);
+ }
+ mThreadComplete = true;
+ }
+
+ if (doError) {
+ nsCOMPtr<nsIEventTarget> target = GetMainThreadEventTarget();
+ if (!target) return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIRunnable> runnable(
+ new nsPassErrorToWifiListeners(std::move(currentListeners), rv));
+ if (!runnable) return NS_ERROR_OUT_OF_MEMORY;
+
+ target->Dispatch(runnable, NS_DISPATCH_SYNC);
+ }
+
+ LOG(("@@@@@ wifi monitor run complete\n"));
+ return NS_OK;
+}
+
+class nsCallWifiListeners final : public nsIRunnable {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIRUNNABLE
+
+ nsCallWifiListeners(WifiListenerArray&& aListeners,
+ nsTArray<RefPtr<nsIWifiAccessPoint>>&& aAccessPoints)
+ : mListeners(std::move(aListeners)),
+ mAccessPoints(std::move(aAccessPoints)) {}
+
+ private:
+ ~nsCallWifiListeners() = default;
+ const WifiListenerArray mListeners;
+ const nsTArray<RefPtr<nsIWifiAccessPoint>> mAccessPoints;
+};
+
+NS_IMPL_ISUPPORTS(nsCallWifiListeners, nsIRunnable)
+
+NS_IMETHODIMP nsCallWifiListeners::Run() {
+ LOG(("About to send data to the wifi listeners\n"));
+ for (auto& listener : mListeners) {
+ listener->OnChange(mAccessPoints);
+ }
+ return NS_OK;
+}
+
+nsresult nsWifiMonitor::CallWifiListeners(
+ const nsCOMArray<nsWifiAccessPoint>& aAccessPoints,
+ bool aAccessPointsChanged) {
+ WifiListenerArray currentListeners;
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ currentListeners.SetCapacity(mListeners.Length());
+
+ for (auto& listener : mListeners) {
+ if (!listener.mHasSentData || aAccessPointsChanged) {
+ listener.mHasSentData = true;
+ currentListeners.AppendElement(listener.mListener);
+ }
+ }
+ }
+
+ if (!currentListeners.IsEmpty()) {
+ uint32_t resultCount = aAccessPoints.Count();
+ nsTArray<RefPtr<nsIWifiAccessPoint>> accessPoints(resultCount);
+
+ for (uint32_t i = 0; i < resultCount; i++) {
+ accessPoints.AppendElement(aAccessPoints[i]);
+ }
+
+ nsCOMPtr<nsIThread> thread = do_GetMainThread();
+ if (!thread) return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIRunnable> runnable(new nsCallWifiListeners(
+ std::move(currentListeners), std::move(accessPoints)));
+ if (!runnable) return NS_ERROR_OUT_OF_MEMORY;
+
+ thread->Dispatch(runnable, NS_DISPATCH_SYNC);
+ }
+
+ return NS_OK;
+}
diff --git a/netwerk/wifi/nsWifiMonitor.h b/netwerk/wifi/nsWifiMonitor.h
new file mode 100644
index 0000000000..645600af33
--- /dev/null
+++ b/netwerk/wifi/nsWifiMonitor.h
@@ -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/. */
+
+#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"
+
+#ifdef XP_WIN
+# include "win_wifiScanner.h"
+#endif
+
+extern mozilla::LazyLogModule gWifiMonitorLog;
+#define LOG(args) MOZ_LOG(gWifiMonitorLog, mozilla::LogLevel::Debug, args)
+
+class nsWifiAccessPoint;
+
+#define kDefaultWifiScanInterval 5 /* seconds */
+
+class nsWifiListener {
+ public:
+ explicit nsWifiListener(nsMainThreadPtrHolder<nsIWifiListener>* aListener) {
+ mListener = aListener;
+ mHasSentData = false;
+ }
+ ~nsWifiListener() = default;
+
+ nsMainThreadPtrHandle<nsIWifiListener> mListener;
+ bool mHasSentData;
+};
+
+class nsWifiMonitor final : nsIRunnable, nsIWifiMonitor, nsIObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWIFIMONITOR
+ NS_DECL_NSIRUNNABLE
+ NS_DECL_NSIOBSERVER
+
+ nsWifiMonitor();
+
+ private:
+ ~nsWifiMonitor() = default;
+
+ nsresult DoScan();
+
+ nsresult CallWifiListeners(const nsCOMArray<nsWifiAccessPoint>& aAccessPoints,
+ bool aAccessPointsChanged);
+
+ mozilla::Atomic<bool> mKeepGoing;
+ mozilla::Atomic<bool> mThreadComplete;
+ nsCOMPtr<nsIThread> mThread;
+
+ nsTArray<nsWifiListener> mListeners;
+
+ mozilla::ReentrantMonitor mReentrantMonitor;
+
+#ifdef XP_WIN
+ mozilla::UniquePtr<WinWifiScanner> mWinWifiScanner;
+#endif
+};
+
+#endif
diff --git a/netwerk/wifi/nsWifiScannerDBus.cpp b/netwerk/wifi/nsWifiScannerDBus.cpp
new file mode 100644
index 0000000000..b9b3761ce7
--- /dev/null
+++ b/netwerk/wifi/nsWifiScannerDBus.cpp
@@ -0,0 +1,374 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWifiScannerDBus.h"
+#include "mozilla/DBusHelpers.h"
+#include "nsWifiAccessPoint.h"
+
+namespace mozilla {
+
+nsWifiScannerDBus::nsWifiScannerDBus(
+ nsCOMArray<nsWifiAccessPoint>* aAccessPoints)
+ : mAccessPoints(aAccessPoints) {
+ MOZ_ASSERT(mAccessPoints);
+
+ mConnection =
+ already_AddRefed<DBusConnection>(dbus_bus_get(DBUS_BUS_SYSTEM, nullptr));
+
+ if (mConnection) {
+ dbus_connection_setup_with_g_main(mConnection, nullptr);
+ dbus_connection_set_exit_on_disconnect(mConnection, false);
+ }
+
+ MOZ_COUNT_CTOR(nsWifiScannerDBus);
+}
+
+nsWifiScannerDBus::~nsWifiScannerDBus() { MOZ_COUNT_DTOR(nsWifiScannerDBus); }
+
+nsresult nsWifiScannerDBus::Scan() {
+ if (!mConnection) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return SendGetDevices();
+}
+
+// http://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html
+// Refer to function dbus_connection_send_with_reply_and_block.
+static const uint32_t DBUS_DEFAULT_TIMEOUT = -1;
+
+nsresult nsWifiScannerDBus::SendGetDevices() {
+ RefPtr<DBusMessage> msg =
+ already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager",
+ "org.freedesktop.NetworkManager", "GetDevices"));
+ if (!msg) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ RefPtr<DBusMessage> reply =
+ already_AddRefed<DBusMessage>(dbus_connection_send_with_reply_and_block(
+ mConnection, msg, DBUS_DEFAULT_TIMEOUT, &err));
+ if (dbus_error_is_set(&err)) {
+ dbus_error_free(&err);
+ return NS_ERROR_FAILURE;
+ }
+
+ return IdentifyDevices(reply);
+}
+
+nsresult nsWifiScannerDBus::SendGetDeviceType(const char* aPath) {
+ RefPtr<DBusMessage> msg = already_AddRefed<DBusMessage>(
+ dbus_message_new_method_call("org.freedesktop.NetworkManager", aPath,
+ "org.freedesktop.DBus.Properties", "Get"));
+ if (!msg) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusMessageIter argsIter;
+ dbus_message_iter_init_append(msg, &argsIter);
+
+ const char* paramInterface = "org.freedesktop.NetworkManager.Device";
+ if (!dbus_message_iter_append_basic(&argsIter, DBUS_TYPE_STRING,
+ &paramInterface)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const char* paramDeviceType = "DeviceType";
+ if (!dbus_message_iter_append_basic(&argsIter, DBUS_TYPE_STRING,
+ &paramDeviceType)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ RefPtr<DBusMessage> reply =
+ already_AddRefed<DBusMessage>(dbus_connection_send_with_reply_and_block(
+ mConnection, msg, DBUS_DEFAULT_TIMEOUT, &err));
+ if (dbus_error_is_set(&err)) {
+ dbus_error_free(&err);
+ return NS_ERROR_FAILURE;
+ }
+
+ return IdentifyDeviceType(reply, aPath);
+}
+
+nsresult nsWifiScannerDBus::SendGetAccessPoints(const char* aPath) {
+ RefPtr<DBusMessage> msg =
+ already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ "org.freedesktop.NetworkManager", aPath,
+ "org.freedesktop.NetworkManager.Device.Wireless", "GetAccessPoints"));
+ if (!msg) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ RefPtr<DBusMessage> reply =
+ already_AddRefed<DBusMessage>(dbus_connection_send_with_reply_and_block(
+ mConnection, msg, DBUS_DEFAULT_TIMEOUT, &err));
+ if (dbus_error_is_set(&err)) {
+ dbus_error_free(&err);
+ // In the GetAccessPoints case, if there are no access points, error is set.
+ // We don't want to error out here.
+ return NS_OK;
+ }
+
+ return IdentifyAccessPoints(reply);
+}
+
+nsresult nsWifiScannerDBus::SendGetAPProperties(const char* aPath) {
+ RefPtr<DBusMessage> msg =
+ already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ "org.freedesktop.NetworkManager", aPath,
+ "org.freedesktop.DBus.Properties", "GetAll"));
+ if (!msg) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusMessageIter argsIter;
+ dbus_message_iter_init_append(msg, &argsIter);
+
+ const char* param = "org.freedesktop.NetworkManager.AccessPoint";
+ if (!dbus_message_iter_append_basic(&argsIter, DBUS_TYPE_STRING, &param)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ RefPtr<DBusMessage> reply =
+ already_AddRefed<DBusMessage>(dbus_connection_send_with_reply_and_block(
+ mConnection, msg, DBUS_DEFAULT_TIMEOUT, &err));
+ if (dbus_error_is_set(&err)) {
+ dbus_error_free(&err);
+ return NS_ERROR_FAILURE;
+ }
+
+ return IdentifyAPProperties(reply);
+}
+
+nsresult nsWifiScannerDBus::IdentifyDevices(DBusMessage* aMsg) {
+ DBusMessageIter iter;
+ nsresult rv = GetDBusIterator(aMsg, &iter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char* devicePath;
+ do {
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) {
+ return NS_ERROR_FAILURE;
+ }
+
+ dbus_message_iter_get_basic(&iter, &devicePath);
+ if (!devicePath) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = SendGetDeviceType(devicePath);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (dbus_message_iter_next(&iter));
+
+ return NS_OK;
+}
+
+nsresult nsWifiScannerDBus::IdentifyDeviceType(DBusMessage* aMsg,
+ const char* aDevicePath) {
+ DBusMessageIter args;
+ if (!dbus_message_iter_init(aMsg, &args)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_VARIANT) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusMessageIter variantIter;
+ dbus_message_iter_recurse(&args, &variantIter);
+ if (dbus_message_iter_get_arg_type(&variantIter) != DBUS_TYPE_UINT32) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t deviceType;
+ dbus_message_iter_get_basic(&variantIter, &deviceType);
+
+ // 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;
+ nsresult rv = NS_OK;
+ if (deviceType == NM_DEVICE_TYPE_WIFI) {
+ rv = SendGetAccessPoints(aDevicePath);
+ }
+
+ return rv;
+}
+
+nsresult nsWifiScannerDBus::IdentifyAccessPoints(DBusMessage* aMsg) {
+ DBusMessageIter iter;
+ nsresult rv = GetDBusIterator(aMsg, &iter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char* path;
+ do {
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) {
+ return NS_ERROR_FAILURE;
+ }
+ dbus_message_iter_get_basic(&iter, &path);
+ if (!path) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = SendGetAPProperties(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (dbus_message_iter_next(&iter));
+
+ return NS_OK;
+}
+
+nsresult nsWifiScannerDBus::IdentifyAPProperties(DBusMessage* aMsg) {
+ DBusMessageIter arr;
+ nsresult rv = GetDBusIterator(aMsg, &arr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsWifiAccessPoint> ap = new nsWifiAccessPoint();
+ do {
+ DBusMessageIter dict;
+ dbus_message_iter_recurse(&arr, &dict);
+
+ do {
+ const char* key;
+ dbus_message_iter_get_basic(&dict, &key);
+ if (!key) {
+ return NS_ERROR_FAILURE;
+ }
+ dbus_message_iter_next(&dict);
+
+ DBusMessageIter variant;
+ dbus_message_iter_recurse(&dict, &variant);
+
+ if (!strncmp(key, "Ssid", strlen("Ssid"))) {
+ nsresult rv = StoreSsid(&variant, ap);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+
+ if (!strncmp(key, "HwAddress", strlen("HwAddress"))) {
+ nsresult rv = SetMac(&variant, ap);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+
+ if (!strncmp(key, "Strength", strlen("Strength"))) {
+ if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_BYTE) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint8_t strength; // in %
+ dbus_message_iter_get_basic(&variant, &strength);
+ int signal_strength = (strength / 2) - 100; // strength to dB
+ ap->setSignal(signal_strength);
+ }
+ } while (dbus_message_iter_next(&dict));
+ } while (dbus_message_iter_next(&arr));
+
+ mAccessPoints->AppendObject(ap);
+ return NS_OK;
+}
+
+nsresult nsWifiScannerDBus::StoreSsid(DBusMessageIter* aVariant,
+ nsWifiAccessPoint* aAp) {
+ if (dbus_message_iter_get_arg_type(aVariant) != DBUS_TYPE_ARRAY) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DBusMessageIter variantMember;
+ dbus_message_iter_recurse(aVariant, &variantMember);
+
+ const uint32_t MAX_SSID_LEN = 32;
+ char ssid[MAX_SSID_LEN];
+ memset(ssid, '\0', ArrayLength(ssid));
+ uint32_t i = 0;
+ do {
+ if (dbus_message_iter_get_arg_type(&variantMember) != DBUS_TYPE_BYTE) {
+ return NS_ERROR_FAILURE;
+ }
+
+ dbus_message_iter_get_basic(&variantMember, &ssid[i]);
+ i++;
+ } while (dbus_message_iter_next(&variantMember) && i < MAX_SSID_LEN);
+
+ aAp->setSSID(ssid, i);
+ return NS_OK;
+}
+
+nsresult nsWifiScannerDBus::SetMac(DBusMessageIter* aVariant,
+ nsWifiAccessPoint* aAp) {
+ if (dbus_message_iter_get_arg_type(aVariant) != DBUS_TYPE_STRING) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // hwAddress format is XX:XX:XX:XX:XX:XX. Need to convert to XXXXXX format.
+ char* hwAddress;
+ dbus_message_iter_get_basic(aVariant, &hwAddress);
+ if (!hwAddress) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const uint32_t MAC_LEN = 6;
+ uint8_t macAddress[MAC_LEN];
+ char* token = strtok(hwAddress, ":");
+ for (uint32_t i = 0; i < ArrayLength(macAddress); i++) {
+ if (!token) {
+ return NS_ERROR_FAILURE;
+ }
+ macAddress[i] = strtoul(token, nullptr, 16);
+ token = strtok(nullptr, ":");
+ }
+ aAp->setMac(macAddress);
+ return NS_OK;
+}
+
+nsresult nsWifiScannerDBus::GetDBusIterator(DBusMessage* aMsg,
+ DBusMessageIter* aIterArray) {
+ DBusMessageIter iter;
+ if (!dbus_message_iter_init(aMsg, &iter)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
+ return NS_ERROR_FAILURE;
+ }
+
+ dbus_message_iter_recurse(&iter, aIterArray);
+ return NS_OK;
+}
+
+} // namespace mozilla
+
+nsresult nsWifiMonitor::DoScan() {
+ nsCOMArray<nsWifiAccessPoint> accessPoints;
+ mozilla::nsWifiScannerDBus wifiScanner(&accessPoints);
+ nsCOMArray<nsWifiAccessPoint> lastAccessPoints;
+
+ while (mKeepGoing) {
+ accessPoints.Clear();
+ nsresult rv = wifiScanner.Scan();
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool accessPointsChanged =
+ !AccessPointsEqual(accessPoints, lastAccessPoints);
+ ReplaceArray(lastAccessPoints, accessPoints);
+
+ rv = CallWifiListeners(lastAccessPoints, accessPointsChanged);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("waiting on monitor\n"));
+ mozilla::ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mon.Wait(PR_SecondsToInterval(kDefaultWifiScanInterval));
+ }
+
+ return NS_OK;
+}
diff --git a/netwerk/wifi/nsWifiScannerDBus.h b/netwerk/wifi/nsWifiScannerDBus.h
new file mode 100644
index 0000000000..91e802db47
--- /dev/null
+++ b/netwerk/wifi/nsWifiScannerDBus.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 NSWIFIAPSCANNERDBUS_H_
+#define NSWIFIAPSCANNERDBUS_H_
+
+#include "nsCOMArray.h"
+
+#define DBUS_API_SUBJECT_TO_CHANGE
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+class nsWifiAccessPoint;
+
+namespace mozilla {
+
+class nsWifiScannerDBus final {
+ public:
+ explicit nsWifiScannerDBus(nsCOMArray<nsWifiAccessPoint>* aAccessPoints);
+ ~nsWifiScannerDBus();
+
+ nsresult Scan();
+
+ private:
+ nsresult SendGetDevices();
+ nsresult SendGetDeviceType(const char* aPath);
+ nsresult SendGetAccessPoints(const char* aPath);
+ nsresult SendGetAPProperties(const char* aPath);
+ nsresult IdentifyDevices(DBusMessage* aMsg);
+ nsresult IdentifyDeviceType(DBusMessage* aMsg, const char* aDevicePath);
+ nsresult IdentifyAccessPoints(DBusMessage* aMsg);
+ nsresult IdentifyAPProperties(DBusMessage* aMsg);
+ nsresult StoreSsid(DBusMessageIter* aVariant, nsWifiAccessPoint* aAp);
+ nsresult SetMac(DBusMessageIter* aVariant, nsWifiAccessPoint* aAp);
+ nsresult GetDBusIterator(DBusMessage* aMsg, DBusMessageIter* aIterArray);
+
+ RefPtr<DBusConnection> mConnection;
+ nsCOMArray<nsWifiAccessPoint>* mAccessPoints;
+};
+
+} // namespace mozilla
+
+#endif // NSWIFIAPSCANNERDBUS_H_
diff --git a/netwerk/wifi/nsWifiScannerFreeBSD.cpp b/netwerk/wifi/nsWifiScannerFreeBSD.cpp
new file mode 100644
index 0000000000..83ec847bdf
--- /dev/null
+++ b/netwerk/wifi/nsWifiScannerFreeBSD.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/. */
+
+// 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 "nsWifiAccessPoint.h"
+
+using namespace mozilla;
+
+static nsresult FreeBSDGetAccessPointData(
+ nsCOMArray<nsWifiAccessPoint>& 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.AppendObject(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;
+}
+
+nsresult nsWifiMonitor::DoScan() {
+ // Regularly get the access point data.
+
+ nsCOMArray<nsWifiAccessPoint> lastAccessPoints;
+ nsCOMArray<nsWifiAccessPoint> accessPoints;
+
+ do {
+ nsresult rv = FreeBSDGetAccessPointData(accessPoints);
+ if (NS_FAILED(rv)) return rv;
+
+ bool accessPointsChanged =
+ !AccessPointsEqual(accessPoints, lastAccessPoints);
+ ReplaceArray(lastAccessPoints, accessPoints);
+
+ rv = CallWifiListeners(lastAccessPoints, accessPointsChanged);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // wait for some reasonable amount of time. pref?
+ LOG(("waiting on monitor\n"));
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mon.Wait(PR_SecondsToInterval(kDefaultWifiScanInterval));
+ } while (mKeepGoing);
+
+ return NS_OK;
+}
diff --git a/netwerk/wifi/nsWifiScannerMac.cpp b/netwerk/wifi/nsWifiScannerMac.cpp
new file mode 100644
index 0000000000..4b8757fd10
--- /dev/null
+++ b/netwerk/wifi/nsWifiScannerMac.cpp
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <mach-o/dyld.h>
+#include <dlfcn.h>
+#include <unistd.h>
+
+#include "osx_wifi.h"
+
+#include "nsCOMArray.h"
+#include "nsWifiMonitor.h"
+#include "nsWifiAccessPoint.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+
+using namespace mozilla;
+
+// defined in osx_corewlan.mm
+// basically replaces accesspoints in the passed reference
+// it lives in a separate file so that we can use objective c.
+extern nsresult GetAccessPointsFromWLAN(
+ nsCOMArray<nsWifiAccessPoint>& accessPoints);
+
+nsresult nsWifiMonitor::DoScan() {
+ // Regularly get the access point data.
+
+ nsCOMArray<nsWifiAccessPoint> lastAccessPoints;
+ nsCOMArray<nsWifiAccessPoint> accessPoints;
+
+ do {
+ nsresult rv = GetAccessPointsFromWLAN(accessPoints);
+ if (NS_FAILED(rv)) return rv;
+
+ bool accessPointsChanged =
+ !AccessPointsEqual(accessPoints, lastAccessPoints);
+ ReplaceArray(lastAccessPoints, accessPoints);
+
+ rv = CallWifiListeners(lastAccessPoints, accessPointsChanged);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // wait for some reasonable amount of time. pref?
+ LOG(("waiting on monitor\n"));
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mon.Wait(PR_SecondsToInterval(kDefaultWifiScanInterval));
+ } while (mKeepGoing);
+
+ return NS_OK;
+}
diff --git a/netwerk/wifi/nsWifiScannerSolaris.cpp b/netwerk/wifi/nsWifiScannerSolaris.cpp
new file mode 100644
index 0000000000..c9d83048a8
--- /dev/null
+++ b/netwerk/wifi/nsWifiScannerSolaris.cpp
@@ -0,0 +1,142 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWifiMonitor.h"
+#include "nsWifiAccessPoint.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.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 (!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;
+}
+
+static void do_dladm(nsCOMArray<nsWifiAccessPoint>& 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.AppendObject(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);
+}
+
+nsresult nsWifiMonitor::DoScan() {
+ // Regularly get the access point data.
+
+ nsCOMArray<nsWifiAccessPoint> lastAccessPoints;
+ nsCOMArray<nsWifiAccessPoint> accessPoints;
+
+ while (mKeepGoing) {
+ accessPoints.Clear();
+ do_dladm(accessPoints);
+
+ bool accessPointsChanged =
+ !AccessPointsEqual(accessPoints, lastAccessPoints);
+ ReplaceArray(lastAccessPoints, accessPoints);
+
+ nsresult rv = CallWifiListeners(lastAccessPoints, accessPointsChanged);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(("waiting on monitor\n"));
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mon.Wait(PR_SecondsToInterval(kDefaultWifiScanInterval));
+ }
+
+ return NS_OK;
+}
diff --git a/netwerk/wifi/nsWifiScannerWin.cpp b/netwerk/wifi/nsWifiScannerWin.cpp
new file mode 100644
index 0000000000..dba286f516
--- /dev/null
+++ b/netwerk/wifi/nsWifiScannerWin.cpp
@@ -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 "nsWifiMonitor.h"
+
+// moz headers (alphabetical)
+#include "nsCOMArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWifiAccessPoint.h"
+#include "win_wifiScanner.h"
+
+using namespace mozilla;
+
+/**
+ * `nsWifiMonitor` is declared in the cross-platform nsWifiMonitor.h and
+ * is mostly defined in the cross-platform nsWifiMonitor.cpp. This function
+ * is implemented in various platform-specific files but the implementation
+ * is almost identical in each file. We relegate the Windows-specific
+ * work to the `WinWifiScanner` class and deal with non-Windows-specific
+ * issues like calling listeners here. Hopefully this file can be merged
+ * with the other implementations of `nsWifiMonitor::DoScan` since a lot
+ * of the code is identical
+ */
+nsresult nsWifiMonitor::DoScan() {
+ if (!mWinWifiScanner) {
+ mWinWifiScanner = MakeUnique<WinWifiScanner>();
+ if (!mWinWifiScanner) {
+ // TODO: Probably return OOM error
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // Regularly get the access point data.
+
+ nsCOMArray<nsWifiAccessPoint> lastAccessPoints;
+ nsCOMArray<nsWifiAccessPoint> accessPoints;
+
+ do {
+ accessPoints.Clear();
+ nsresult rv = mWinWifiScanner->GetAccessPointsFromWLAN(accessPoints);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool accessPointsChanged =
+ !AccessPointsEqual(accessPoints, lastAccessPoints);
+ ReplaceArray(lastAccessPoints, accessPoints);
+
+ rv = CallWifiListeners(lastAccessPoints, accessPointsChanged);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // wait for some reasonable amount of time. pref?
+ LOG(("waiting on monitor\n"));
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ if (mKeepGoing) {
+ mon.Wait(PR_SecondsToInterval(kDefaultWifiScanInterval));
+ }
+ } while (mKeepGoing);
+
+ return NS_OK;
+}
diff --git a/netwerk/wifi/osx_corewlan.mm b/netwerk/wifi/osx_corewlan.mm
new file mode 100644
index 0000000000..e526d98b9e
--- /dev/null
+++ b/netwerk/wifi/osx_corewlan.mm
@@ -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 <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"
+
+nsresult GetAccessPointsFromWLAN(nsCOMArray<nsWifiAccessPoint>& accessPoints) {
+ NS_OBJC_BEGIN_TRY_ABORT_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] scanForNetworksWithParameters: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)]) {
+ NSData* data = [anObject bssidData];
+ 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;
+ }
+ }
+ }
+
+ // [CWInterface rssiValue] is available on OS X 10.7 and up (and
+ // [CWInterface rssi] is deprecated).
+ int signal = 0;
+ if ([anObject respondsToSelector:@selector(rssiValue)]) {
+ signal = (int)((NSInteger)[anObject rssiValue]);
+ } else {
+ signal = [[anObject rssi] intValue];
+ }
+
+ ap->setMac(macData);
+ ap->setSignal(signal);
+ ap->setSSID([[anObject ssid] UTF8String], 32);
+
+ accessPoints.AppendObject(ap);
+ }
+ } @catch (NSException*) {
+ [pool release];
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ [pool release];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NS_ERROR_NOT_AVAILABLE);
+}
diff --git a/netwerk/wifi/osx_wifi.h b/netwerk/wifi/osx_wifi.h
new file mode 100644
index 0000000000..3a177f9643
--- /dev/null
+++ b/netwerk/wifi/osx_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/win_wifiScanner.cpp b/netwerk/wifi/win_wifiScanner.cpp
new file mode 100644
index 0000000000..ac7d8fc2b7
--- /dev/null
+++ b/netwerk/wifi/win_wifiScanner.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 "nsWifiAccessPoint.h"
+#include "win_wifiScanner.h"
+
+// Moz headers (alphabetical)
+#include "win_wlanLibrary.h"
+
+#define DOT11_BSS_TYPE_UNUSED static_cast<DOT11_BSS_TYPE>(0)
+
+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();
+}
+
+WinWifiScanner::WinWifiScanner() {
+ // 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");
+ }
+}
+
+WinWifiScanner::~WinWifiScanner() {}
+
+nsresult WinWifiScanner::GetAccessPointsFromWLAN(
+ nsCOMArray<nsWifiAccessPoint>& accessPoints) {
+ accessPoints.Clear();
+
+ // NOTE: We do not try to load the WLAN library if we previously failed
+ // to load it. See the note in WinWifiScanner 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.AppendObject(ap);
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/netwerk/wifi/win_wifiScanner.h b/netwerk/wifi/win_wifiScanner.h
new file mode 100644
index 0000000000..d19bda6fc3
--- /dev/null
+++ b/netwerk/wifi/win_wifiScanner.h
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#pragma once
+
+// Moz headers (alphabetical)
+#include "mozilla/UniquePtr.h"
+#include "nsCOMArray.h"
+#include "win_wlanLibrary.h"
+
+class nsWifiAccessPoint;
+
+class WinWifiScanner final {
+ public:
+ WinWifiScanner();
+ ~WinWifiScanner();
+
+ /**
+ * 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(nsCOMArray<nsWifiAccessPoint>& accessPoints);
+
+ private:
+ mozilla::UniquePtr<WinWLANLibrary> mWlanLibrary;
+};
diff --git a/netwerk/wifi/win_wlanLibrary.cpp b/netwerk/wifi/win_wlanLibrary.cpp
new file mode 100644
index 0000000000..8e5ad00b0c
--- /dev/null
+++ b/netwerk/wifi/win_wlanLibrary.cpp
@@ -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/. */
+
+#include "win_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;
+}
+
+WinWLANLibrary::WinWLANLibrary()
+ : mWlanLibrary(nullptr),
+ mWlanHandle(nullptr),
+ mWlanEnumInterfacesPtr(nullptr),
+ mWlanGetNetworkBssListPtr(nullptr),
+ mWlanFreeMemoryPtr(nullptr),
+ mWlanCloseHandlePtr(nullptr),
+ mWlanOpenHandlePtr(nullptr),
+ mWlanRegisterNotificationPtr(nullptr),
+ mWlanScanPtr(nullptr) {}
+
+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..864e3f71a3
--- /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();
+ bool Initialize();
+
+ HMODULE mWlanLibrary;
+ HANDLE mWlanHandle;
+ decltype(::WlanEnumInterfaces)* mWlanEnumInterfacesPtr;
+ decltype(::WlanGetNetworkBssList)* mWlanGetNetworkBssListPtr;
+ decltype(::WlanFreeMemory)* mWlanFreeMemoryPtr;
+ decltype(::WlanCloseHandle)* mWlanCloseHandlePtr;
+ decltype(::WlanOpenHandle)* mWlanOpenHandlePtr;
+ decltype(::WlanRegisterNotification)* mWlanRegisterNotificationPtr;
+ decltype(::WlanScan)* mWlanScanPtr;
+};
+
+class ScopedWLANObject {
+ public:
+ ScopedWLANObject(const WinWLANLibrary& library, void* object)
+ : mLibrary(library), mObject(object) {}
+
+ ~ScopedWLANObject() { (*(mLibrary.GetWlanFreeMemoryPtr()))(mObject); }
+
+ private:
+ const WinWLANLibrary& mLibrary;
+ void* mObject;
+};